diff --git a/doc/05-repositories.md b/doc/05-repositories.md index ce023810a..14b18337e 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -485,6 +485,40 @@ Check [the satis GitHub repository](https://github.com/composer/satis) and the [Satis article](articles/handling-private-packages-with-satis.md) for more information. +### Artifact + +There are some cases, when there is no ability to have one of the previously +mentioned repository types online, even the VCS one. Typical example could be +cross-organisation library exchange through built artifacts. Of course, most +of the times they are private. To simplify maintenance, one can simply specify +repository of type `artifact` with a folder containing ZIP archives of those +private packages: + + { + "repositories": [ + { + "type": "artifact", + "url": "path/to/directory/with/zips/" + } + ], + "require": { + "private-vendor-one/core": "15.6.2", + "private-vendor-two/connectivity": "*", + "acme-corp/parser": "10.3.5" + } + } + +Each zip artifact is just a ZIP archive with `composer.json` in root folder: + + $ tar -tf acme-corp-parser-10.3.5.zip + composer.json + ... + +If there is two archives with different versions of a package, they would be +imported both. If archive with newer version would be put to artifact folder and +`update` command would be triggered, that version would replace previous, at it + logically seems. + ## Disabling Packagist You can disable the default Packagist repository by adding this to your diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 8ea15cde8..fe993cfa8 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -287,6 +287,7 @@ class Factory $rm->setRepositoryClass('git', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('svn', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('artifact', 'Composer\Repository\ArtifactRepository'); return $rm; } diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php new file mode 100644 index 000000000..0175c85f3 --- /dev/null +++ b/src/Composer/Repository/ArtifactRepository.php @@ -0,0 +1,109 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Package\Loader\ArrayLoader; + +/** + * @author Serge Smertin + */ +class ArtifactRepository extends ArrayRepository +{ + /** @var LoaderInterface */ + protected $loader; + + protected $lookup; + + public function __construct(array $repoConfig, IOInterface $io) + { + $this->loader = new ArrayLoader(); + $this->lookup = $repoConfig['url']; + $this->io = $io; + } + + protected function initialize() + { + parent::initialize(); + + if (!extension_loaded('zip')) { + $msg = 'In order to use artifact repository, ' . + 'you need to have zip extension enabled'; + $this->io->write($msg); + return; + } + + $this->scanDirectory($this->lookup); + } + + private function scanDirectory($path) + { + $io = $this->io; + foreach (new \RecursiveDirectoryIterator($path) as $file) { + /* @var $file \SplFileInfo */ + if (!$file->isFile()) { + continue; + } + + $package = $this->getComposerInformation($file); + if (!$package) { + if ($io->isVerbose()) { + $msg = "File {$file->getBasename()} doesn't seem to hold a package"; + $io->write($msg); + } + continue; + } + + if ($io->isVerbose()) { + $template = 'Found package %s (%s) in file %s'; + $msg = sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename()); + $io->write($msg); + } + + $this->addPackage($package); + } + } + + private function getComposerInformation(\SplFileInfo $file) + { + $zip = new \ZipArchive(); + $zip->open($file->getPathname()); + + if (0 == $zip->numFiles) { + return false; + } + + $foundFileIndex = $zip->locateName('composer.json', \ZipArchive::FL_NODIR); + if (false === $foundFileIndex) { + return false; + } + + $configurationFileName = $zip->getNameIndex($foundFileIndex); + + $composerFile = "zip://{$file->getPathname()}#$configurationFileName"; + $json = file_get_contents($composerFile); + + $package = JsonFile::parseJson($json, $composerFile); + $package['dist'] = array( + 'type' => 'zip', + 'url' => $file->getRealPath(), + 'reference' => $file->getBasename(), + 'shasum' => sha1_file($file->getRealPath()) + ); + + $package = $this->loader->load($package); + + return $package; + } +} diff --git a/tests/Composer/Test/Repository/ArtifactRepositoryTest.php b/tests/Composer/Test/Repository/ArtifactRepositoryTest.php new file mode 100644 index 000000000..ac1bcbbe4 --- /dev/null +++ b/tests/Composer/Test/Repository/ArtifactRepositoryTest.php @@ -0,0 +1,42 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Test\TestCase; +use Composer\IO\NullIO; +use Composer\Config; +use Composer\Package\BasePackage; + +class ArtifactRepositoryTest extends TestCase +{ + public function testExtractsConfigsFromZipArchives() + { + $expectedPackages = array( + 'vendor0/package0-0.0.1', + 'composer/composer-1.0.0-alpha6', + 'vendor1/package2-4.3.2', + ); + + $coordinates = array('type' => 'artifact', 'url' => __DIR__ . '/Fixtures/artifacts'); + $repo = new ArtifactRepository($coordinates, new NullIO(), new Config()); + + $foundPackages = array_map(function(BasePackage $package) { + return "{$package->getPrettyName()}-{$package->getPrettyVersion()}"; + }, $repo->getPackages()); + + sort($expectedPackages); + sort($foundPackages); + + $this->assertSame($expectedPackages, $foundPackages); + } +} diff --git a/tests/Composer/Test/Repository/Fixtures/artifacts/composer-1.0.0-alpha6.zip b/tests/Composer/Test/Repository/Fixtures/artifacts/composer-1.0.0-alpha6.zip new file mode 100644 index 000000000..e94843eb6 Binary files /dev/null and b/tests/Composer/Test/Repository/Fixtures/artifacts/composer-1.0.0-alpha6.zip differ diff --git a/tests/Composer/Test/Repository/Fixtures/artifacts/not-an-artifact.zip b/tests/Composer/Test/Repository/Fixtures/artifacts/not-an-artifact.zip new file mode 100644 index 000000000..3e788dcc2 Binary files /dev/null and b/tests/Composer/Test/Repository/Fixtures/artifacts/not-an-artifact.zip differ diff --git a/tests/Composer/Test/Repository/Fixtures/artifacts/package0.zip b/tests/Composer/Test/Repository/Fixtures/artifacts/package0.zip new file mode 100644 index 000000000..855c6a64d Binary files /dev/null and b/tests/Composer/Test/Repository/Fixtures/artifacts/package0.zip differ diff --git a/tests/Composer/Test/Repository/Fixtures/artifacts/package2.zip b/tests/Composer/Test/Repository/Fixtures/artifacts/package2.zip new file mode 100644 index 000000000..671058059 Binary files /dev/null and b/tests/Composer/Test/Repository/Fixtures/artifacts/package2.zip differ