From 2acb0330570f64151174df8e954b4b2bb113bae3 Mon Sep 17 00:00:00 2001 From: Till Klampaeckel Date: Fri, 24 Aug 2012 10:57:38 +0200 Subject: [PATCH] Initial feature-dist * extends BaseDumper, implements interface * put $keys into BaseDumper * WIP WIP WIP WIP * BaseDumper for utilities * interface to enforce 'dump()' * feature: * supports git * supports zip output * basic test to cover feature * add @todo for later * add vendor namespace to package name * add extension to getFilename() so we don't need to switch in there (HT, @naderman) * add extension (obviously 'zip' in ZipDumper) * create archive in destination dir (provided by __construct()) * condensed ZipDumper * moved code to BaseDumper (hopefully easier re-use) * use ProcessExecutor from BaseDumper * fix assignments in __construct() * allow injection of ProcessExecutor * fix parameters * fix regex * write in 'system temp dir' * update test case (oh look, a duplicate regex) * move working directory related to BaseDumper * add quotes * place holder for these methods * use PharData to create zip/tar when necessary * add placeholder calls * add call to package() using PharData * finish downloadHg(), downloadSvn() * put to use * make BaseDumper abstract (to force extension) * make BaseDumper implement Interface (makes for less code in the implementation) new functionality for dumping as .tar.gz tar instead of tar.gz, new abstract dumpertest class creates a local git repo instead of fetching a remote one more oo-ish version of it no constructor * refactor tests to be less linux-specific (used Composer\Util to wrap calls) * make filename only the version * various cs fixes (idention, tabs/spaces, doc blocks, etc.) * fixed a typo'd exception name * refactored downloading: * removed download*() methods * added dep on Composer\Factory to setup a DownloadManager instance * update CS with feedback from @stof * ArrayDumper doesn't extend BaseDumper anymore (hence no conflict on the interface) * move keys from BaseDumper back to ArrayDumper * interface now declares dump() to always return void Apparently I had to update the lock. CS fixes (tabs for spaces) Bugfix: sprintf() was missing. Fix docblock for @stof. ;) Pull in lock from master. Update lock one more time (hope it still merges). whitespace Revert ArrayDumper static keys --- src/Composer/Package/Dumper/BaseDumper.php | 166 ++++++++++++++++++ .../Package/Dumper/DumperInterface.php | 29 +++ src/Composer/Package/Dumper/TarDumper.php | 57 ++++++ src/Composer/Package/Dumper/ZipDumper.php | 56 ++++++ .../Test/Package/Dumper/DumperTest.php | 97 ++++++++++ .../Test/Package/Dumper/TarDumperTest.php | 43 +++++ .../Test/Package/Dumper/ZipDumperTest.php | 43 +++++ 7 files changed, 491 insertions(+) create mode 100644 src/Composer/Package/Dumper/BaseDumper.php create mode 100644 src/Composer/Package/Dumper/DumperInterface.php create mode 100644 src/Composer/Package/Dumper/TarDumper.php create mode 100644 src/Composer/Package/Dumper/ZipDumper.php create mode 100644 tests/Composer/Test/Package/Dumper/DumperTest.php create mode 100644 tests/Composer/Test/Package/Dumper/TarDumperTest.php create mode 100644 tests/Composer/Test/Package/Dumper/ZipDumperTest.php diff --git a/src/Composer/Package/Dumper/BaseDumper.php b/src/Composer/Package/Dumper/BaseDumper.php new file mode 100644 index 000000000..093e5a3f9 --- /dev/null +++ b/src/Composer/Package/Dumper/BaseDumper.php @@ -0,0 +1,166 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Dumper; + +use Composer\Package\PackageInterface; +use Composer\Util\ProcessExecutor; +use Composer\Downloader\GitDownloader; +use Composer\Downloader\HgDownloader; +use Composer\Downloader\SvnDownloader; +use Composer\IO\NullIO; +use Composer\Factory; + +/** + * @author Till Klampaeckel + */ +abstract class BaseDumper implements DumperInterface +{ + /** + * Format: zip or tar. + * @var string + */ + protected $format = ''; + + /** + * Path to where to dump the export to. + * @var mixed|null + */ + protected $path; + + /** + * @var ProcessExecutor + */ + protected $process; + + /** + * Working directory. + * @var string + */ + protected $temp; + + /** + * @param mixed $path + * @param ProcessExecutor|null $process + * + * @throws \InvalidArgumentException + */ + public function __construct($path = null, ProcessExecutor $process = null) + { + if (!empty($path)) { + if (!is_writable($path)) { + throw new \InvalidArgumentException("Not authorized to write to '{$path}'"); + } + $this->path = $path; + } + $this->process = $process ?: new ProcessExecutor(); + $this->temp = sys_get_temp_dir(); + } + + /** + * @return \Composer\Downloader\DownloadManager + */ + public function getDownloadManager() + { + $factory = new Factory; + $dm = $factory->createDownloadManager(new NullIO()); + return $dm; + } + + /** + * @param PackageInterface $package + * @param string $extension + * + * @return string + * @throws \InvalidArgumentException When unknown 'format' is encountered. + */ + public function getFilename(PackageInterface $package, $extension) + { + $name = $package->getPrettyVersion(); + $fileName = sprintf('%s.%s', $name, $extension); + return $fileName; + } + + /** + * @param PackageInterface $package + * + * @return string + * @throws \RuntimeException + */ + protected function getAndEnsureWorkDirectory(PackageInterface $package) + { + $workDir = sprintf('%s/%s/%s', $this->temp, $this->format, $package->getName()); + if (!file_exists($workDir)) { + mkdir($workDir, 0777, true); + } + if (!file_exists($workDir)) { + throw new \RuntimeException("Could not find '{$workDir}' directory."); + } + return $workDir; + } + + /** + * Package the given directory into an archive. + * + * The format is most likely \Phar::TAR or \Phar::ZIP. + * + * @param string $filename + * @param string $workDir + * @param int $format + * + * @throws \RuntimeException + */ + protected function package($filename, $workDir, $format) + { + try { + $phar = new \PharData($filename, null, null, $format); + $phar->buildFromDirectory($workDir); + } catch (\UnexpectedValueException $e) { + $message = "Original PHAR exception: " . (string) $e; + $message .= PHP_EOL . PHP_EOL; + $message .= sprintf("Could not create archive '%s' from '%s'.", $filename, $workDir); + throw new \RuntimeException($message); + } + } + + /** + * @param string $fileName + * @param string $sourceRef + * @param string $workDir + */ + protected function packageGit($fileName, $sourceRef, $workDir) + { + $command = sprintf( + 'git archive --format %s --output %s %s', + $this->format, + escapeshellarg(sprintf('%s/%s', $this->path, $fileName)), + $sourceRef + ); + $this->process->execute($command, $output, $workDir); + } + + /** + * @param string $fileName + * @param string $sourceRef + * @param string $workDir + */ + protected function packageHg($fileName, $sourceRef, $workDir) + { + $format = ($this->format == 'tarball')?'tar':$this->format; + $command = sprintf( + 'hg archive --rev %s --type %s %s', + $sourceRef, + $format, + escapeshellarg(sprintf('%s/%s', $this->path, $fileName)) + ); + $this->process->execute($command, $output, $workDir); + } +} diff --git a/src/Composer/Package/Dumper/DumperInterface.php b/src/Composer/Package/Dumper/DumperInterface.php new file mode 100644 index 000000000..03a0aa5f9 --- /dev/null +++ b/src/Composer/Package/Dumper/DumperInterface.php @@ -0,0 +1,29 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace Composer\Package\Dumper; + +use Composer\Package\PackageInterface; + +/** + * @author Till Klampaeckel + */ +interface DumperInterface +{ + /** + * Return value depends on implementation - e.g. generating a tar or zip the + * method currently returns void, the ArrayDumper returns an array. + * + * @param PackageInterface $package + * + * @return void + */ + public function dump(PackageInterface $package); +} diff --git a/src/Composer/Package/Dumper/TarDumper.php b/src/Composer/Package/Dumper/TarDumper.php new file mode 100644 index 000000000..4de76c7ab --- /dev/null +++ b/src/Composer/Package/Dumper/TarDumper.php @@ -0,0 +1,57 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Dumper; + +use Composer\Package\Dumper\BaseDumper; +use Composer\Package\Dumper\DumperInterface; +use Composer\Package\PackageInterface; +use Composer\Util\ProcessExecutor; + +/** + * @author Ulf Härnhammar + */ +class TarDumper extends BaseDumper +{ + protected $format = 'tar'; + + /** + * @param PackageInterface $package + * @throws \InvalidArgumentException + */ + public function dump(PackageInterface $package) + { + $workDir = $this->getAndEnsureWorkDirectory($package); + + $fileName = $this->getFilename($package, 'tar'); + $sourceType = $package->getSourceType(); + $sourceRef = $package->getSourceReference(); + + $dm = $this->getDownloadManager(); + $dm->download($package, $workDir, true); + + switch ($sourceType) { + case 'git': + $this->packageGit($fileName, $sourceRef, $workDir); + break; + case 'hg': + $this->packageHg($fileName, $sourceRef, $workDir); + break; + case 'svn': + $dir = $workDir . (substr($sourceRef, 0, 1) !== '/')?'/':'' . $sourceRef; + $this->package($fileName, $dir, \Phar::TAR); + break; + default: + throw new \InvalidArgumentException( + "Unable to handle repositories of type '{$sourceType}'."); + } + } +} diff --git a/src/Composer/Package/Dumper/ZipDumper.php b/src/Composer/Package/Dumper/ZipDumper.php new file mode 100644 index 000000000..6ab55839d --- /dev/null +++ b/src/Composer/Package/Dumper/ZipDumper.php @@ -0,0 +1,56 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Dumper; + +use Composer\Package\Dumper\BaseDumper; +use Composer\Package\Dumper\DumperInterface; +use Composer\Package\PackageInterface; +use Composer\Util\ProcessExecutor; + +/** + * @author Till Klampaeckel + */ +class ZipDumper extends BaseDumper +{ + protected $format = 'zip'; + + /** + * @param PackageInterface $package + * @throws \InvalidArgumentException + */ + public function dump(PackageInterface $package) + { + $workDir = $this->getAndEnsureWorkDirectory($package); + + $fileName = $this->getFilename($package, 'zip'); + $sourceType = $package->getSourceType(); + $sourceRef = $package->getSourceReference(); + + $dm = $this->getDownloadManager(); + $dm->download($package, $workDir, true); + + switch ($sourceType) { + case 'git': + $this->packageGit($fileName, $sourceRef, $workDir); + break; + case 'hg': + $this->packageHg($fileName, $sourceRef, $workDir); + break; + case 'svn': + $dir = $workDir . (substr($sourceRef, 0, 1) !== '/')?'/':'' . $sourceRef; + $this->package($fileName, $dir, \Phar::ZIP); + break; + default: + throw new \InvalidArgumentException("Unable to handle repositories of type '{$sourceType}'."); + } + } +} \ No newline at end of file diff --git a/tests/Composer/Test/Package/Dumper/DumperTest.php b/tests/Composer/Test/Package/Dumper/DumperTest.php new file mode 100644 index 000000000..6db54ea69 --- /dev/null +++ b/tests/Composer/Test/Package/Dumper/DumperTest.php @@ -0,0 +1,97 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Package\Dumper; + +use Composer\Package\MemoryPackage; +use Composer\Util\Filesystem; +use Composer\Util\ProcessExecutor; + +abstract class DumperTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Composer\Util\Filesystem + */ + protected $fs; + + /** + * @var \Composer\Util\ProcessExecutor + */ + protected $process; + + /** + * @var string + */ + protected $testdir = ''; + + public function setUp() + { + $this->fs = new Filesystem; + $this->process = new ProcessExecutor; + $this->testdir = sys_get_temp_dir() . '/composer_dumpertest_git_repository' . mt_rand(); + } + + protected function getTestDir() + { + return $this->testdir; + } + + /** + * Create local git repository to run tests against! + */ + protected function setupGitRepo() + { + $td = $this->getTestDir(); + + $this->fs->removeDirectory($td); + $this->fs->ensureDirectoryExists($td); + + $currentWorkDir = getcwd(); + chdir($td); + + $result = $this->process->execute("git init -q"); + if ($result > 0) { + throw new \RuntimeException( + "Could not init: " . $this->process->getErrorOutput()); + } + $result = file_put_contents('b', 'a'); + if (false === $result) { + throw new \RuntimeException("Could not save file."); + } + $result = $this->process->execute("git add b && git commit -m 'commit b' -q"); + if ($result > 0) { + throw new \RuntimeException( + "Could not init: " . $this->process->getErrorOutput()); + } + chdir($currentWorkDir); + } + + protected function removeGitRepo() + { + $td = $this->getTestDir(); + $this->fs->removeDirectory($td); + } + + protected function setupPackage() + { + $td = $this->getTestDir(); + $package = new MemoryPackage('dumpertest/dumpertest', 'master', 'master'); + $package->setSourceUrl("file://$td"); + $package->setSourceReference('master'); + $package->setSourceType('git'); + return $package; + } + + protected function getPackageFileName(MemoryPackage $package) + { + return $package->getVersion(); + } +} diff --git a/tests/Composer/Test/Package/Dumper/TarDumperTest.php b/tests/Composer/Test/Package/Dumper/TarDumperTest.php new file mode 100644 index 000000000..cd9917732 --- /dev/null +++ b/tests/Composer/Test/Package/Dumper/TarDumperTest.php @@ -0,0 +1,43 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Package\Dumper; + +use Composer\Package\Dumper\TarDumper; + +class TarDumperTest extends DumperTest +{ + public function testThis() + { + $this->setupGitRepo(); + $package = $this->setupPackage(); + $name = $this->getPackageFileName($package); + + $temp = sys_get_temp_dir(); + $tar = new TarDumper($temp); + $tar->dump($package); + + $dist = sprintf('%s/%s.tar', + $temp, $name + ); + $this->assertFileExists($dist); + unlink($dist); + $this->removeGitRepo(); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testException() + { + new TarDumper("/totally-random-" . time()); + } +} diff --git a/tests/Composer/Test/Package/Dumper/ZipDumperTest.php b/tests/Composer/Test/Package/Dumper/ZipDumperTest.php new file mode 100644 index 000000000..2fe71eb69 --- /dev/null +++ b/tests/Composer/Test/Package/Dumper/ZipDumperTest.php @@ -0,0 +1,43 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Package\Dumper; + +use Composer\Package\Dumper\ZipDumper; + +class ZipDumperTest extends DumperTest +{ + public function testThis() + { + $this->setupGitRepo(); + $package = $this->setupPackage(); + $name = $this->getPackageFileName($package); + + $temp = sys_get_temp_dir(); + $zip = new ZipDumper($temp); + $zip->dump($package); + + $dist = sprintf('%s/%s.zip', + $temp, $name + ); + $this->assertFileExists($dist); + unlink($dist); + $this->removeGitRepo(); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testException() + { + new ZipDumper("/totally-random-" . time()); + } +}