Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Link command WIP

  • Loading branch information...
commit f48887b7147f4a2763877d76e7915878cd57fc2b 1 parent e5faa8c
@fprochazka fprochazka authored
View
166 src/Composer/Command/LinkCommand.php
@@ -0,0 +1,166 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Command;
+
+use Symfony\Component\Console\Input\InputInterface;
+use Composer\Util\ProcessExecutor;
+use Composer\Repository\LocalLinksRepository;
+use Composer\Factory;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Composer\Installer;
+use Composer\Json\JsonFile;
+
+/**
+ * @author Jérémy Romey <jeremy@free-agent.fr>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @author Filip Procházka <filip.prochazka@kdyby.org>
+ */
+class LinkCommand extends RequireCommand
+{
+ protected function configure()
+ {
+ $this
+ ->setName('link')
+ ->setDescription('Installs package from a local filesystem using a symbolic link or marks current package as link')
+ ->setDefinition(array(
+ new InputArgument('package', InputArgument::OPTIONAL, 'Local path to a required package or package name that is registered as a link.'),
+ new InputOption('remove', 'r', InputOption::VALUE_NONE, "Instead of creating a link, it removes it.")
+ ))
+ ->setHelp(<<<EOT
+The link command can either install a dependency by linking it, or mark current package as a link available for local install.
+If you wanna link a package, it should be managed by vcs.
+
+You mark a package as a link by entering to a directory, that contains your package and calling
+ <comment>cd /some/local/path/redis-client</comment>
+ <comment>%command.full_name%</comment>
+
+When you have package marked as a link, you can install it wherever you want. Composer will then create a symlink for the dependency, instead of copying it
+ <comment>%command.full_name% jack/redis-client</comment>
+
+Package can be also installed by passing a local filesystem path. The package will be first marked as a link and then installed
+ <comment>%command.full_name% /some/local/path/redis-client</comment>
+
+By specifying the <comment>--remove</comment> option, you can remove the package from list of local links
+ <comment>cd /some/local/path/redis-client</comment>
+ <comment>%command.full_name% --remove</comment>
+
+When you request installation of a package, that is not yet in <info>composer.json</info> it will be added as a dependency.
+Also, composer will not change the version of the linked package. You have to do that manually using your vcs.
+EOT
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $package = $input->getArgument('package');
+ if (preg_match('~^\w[\w.-]*/\w[\w.-]*$~i', $package)) {
+ return $this->installFromLocalLink($input, $output, $package);
+
+ } elseif ($package) {
+ $packageDir = realpath($package);
+ if (false === $packageDir || !is_dir($packageDir)) {
+ $output->writeln('<error>Package directory ' . $input->getArgument('package') . ' not found.</error>');
+ return 1;
+ }
+
+ $command = sprintf("%s link", escapeshellarg($_SERVER['PHP_SELF'])); // todo: will this work in phar?
+ $out = NULL;
+ $process = new ProcessExecutor();
+ if (0 !== $process->execute($command, $out, $packageDir)) {
+ $output->writeln('<error>Cannot create link from ' . $packageDir . '.</error>');
+ $output->writeln('<error>' . $process->getErrorOutput() . '</error>');
+ return 1;
+ }
+ $output->writeln('<info>' . trim($out) . '</info>');
+
+ $jsonFile = new JsonFile($packageDir . '/composer.json');
+ $packageConfig = $jsonFile->read();
+ return $this->installFromLocalLink($input, $output, $packageConfig['name']);
+
+ } else {
+ return $this->createLocalLink($input, $output);
+ }
+ }
+
+ private function installFromLocalLink(InputInterface $input, OutputInterface $output, $packageName)
+ {
+ // ensure the requirements
+ $requires = array($packageName . ' dev-master');
+ $requirements = $this->updateComposerConfig($input, $output, $requires);
+// $output->writeln('');
+// $output->writeln('Installing package <comment>' . $packageName . '</comment> using a local link.');
+
+ // Update packages
+ $composer = $this->getComposer();
+ $io = $this->getIO();
+ $install = Installer::create($io, $composer);
+
+ $install
+ ->setVerbose($input->getOption('verbose'))
+ ->setDevMode(false)
+ ->setUpdate(true)
+ ->setUpdateWhitelist($requirements)
+ ->setPreferLinks($requires)
+ ;
+
+ return $install->run() ? 0 : 1;
+ }
+
+ private function createLocalLink(InputInterface $input, OutputInterface $output)
+ {
+ // create local links repo
+ $composer = $this->getComposer();
+ $config = $composer->getConfig();
+ $linksRepo = new LocalLinksRepository($config, $this->getIO());
+ $rootPackage = clone $composer->getPackage();
+
+ if ($input->getOption('remove')) {
+ if (false == $linksRepo->hasPackage($rootPackage)) {
+ $output->writeln('Package <comment>' . $rootPackage->getPrettyName() . '</comment> is not a local link</info>.');
+ $output->writeln('');
+ return 0;
+ }
+
+ $linksRepo->removePackage($rootPackage);
+ $output->writeln('Package <comment>' . $rootPackage->getPrettyName() . '</comment> is no longer a local link.');
+ $output->writeln('');
+ return 0;
+ }
+
+ // register package
+ $rootPackage->setSourceUrl(realpath(dirname($config->get('vendor-dir'))));
+
+ if ($linksRepo->hasPackage($rootPackage)) {
+ $output->writeln('Package <comment>' . $rootPackage->getPrettyName() . '</comment> is already a local link pointing at <info>' . $rootPackage->getSourceUrl() . '</info>.');
+ $output->writeln('');
+ return 0;
+ }
+
+ $output->writeln('Creating a local link from package <comment>' . $rootPackage->getPrettyName() . '</comment> at <info>' . $rootPackage->getSourceUrl() . '</info>.');
+ $output->writeln('');
+
+ try {
+ $linksRepo->addPackage($rootPackage);
+
+ } catch (\InvalidArgumentException $e) {
+ $output->writeln('<error>' . $e->getMessage() . '</error>');
+ return 1;
+ }
+
+ return 0;
+ }
+
+}
View
74 src/Composer/Command/RequireCommand.php
@@ -50,29 +50,52 @@ protected function configure()
protected function execute(InputInterface $input, OutputInterface $output)
{
+ $requirements = $this->updateComposerConfig($input, $output, $input->getArgument('packages'));
+
+ if ($input->getOption('no-update')) {
+ return 0;
+ }
+
+ // Update packages
+ $composer = $this->getComposer();
+ $io = $this->getIO();
+ $install = Installer::create($io, $composer);
+
+ $install
+ ->setVerbose($input->getOption('verbose'))
+ ->setPreferSource($input->getOption('prefer-source'))
+ ->setDevMode($input->getOption('dev'))
+ ->setUpdate(true)
+ ->setUpdateWhitelist($requirements);
+ ;
+
+ return $install->run() ? 0 : 1;
+ }
+
+ protected function readComposerConfig()
+ {
$factory = new Factory;
$file = $factory->getComposerFile();
if (!file_exists($file)) {
- $output->writeln('<error>'.$file.' not found.</error>');
-
- return 1;
+ throw new \RuntimeException($file . ' not found.');
}
if (!is_readable($file)) {
- $output->writeln('<error>'.$file.' is not readable.</error>');
-
- return 1;
+ throw new \RuntimeException($file . ' is not readable.');
}
- $dialog = $this->getHelperSet()->get('dialog');
+ return new JsonFile($file);
+ }
- $json = new JsonFile($file);
- $composer = $json->read();
+ protected function updateComposerConfig(InputInterface $input, OutputInterface $output, $requires = array())
+ {
+ $json = $this->readComposerConfig();
+ $composerConfig = $json->read();
- $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'));
+ $requireKey = $input->hasOption('dev') && $input->getOption('dev') ? 'require-dev' : 'require';
+ $baseRequirements = array_key_exists($requireKey, $composerConfig) ? $composerConfig[$requireKey] : array();
- $requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
- $baseRequirements = array_key_exists($requireKey, $composer) ? $composer[$requireKey] : array();
+ $requirements = $this->determineRequirements($input, $output, $requires);
$requirements = $this->formatRequirements($requirements);
if (!$this->updateFileCleanly($json, $baseRequirements, $requirements, $requireKey)) {
@@ -80,33 +103,16 @@ protected function execute(InputInterface $input, OutputInterface $output)
$baseRequirements[$package] = $version;
}
- $composer[$requireKey] = $baseRequirements;
- $json->write($composer);
+ $composerConfig[$requireKey] = $baseRequirements;
+ $json->write($composerConfig);
}
- $output->writeln('<info>'.$file.' has been updated</info>');
-
- if ($input->getOption('no-update')) {
- return 0;
- }
-
- // Update packages
- $composer = $this->getComposer();
- $io = $this->getIO();
- $install = Installer::create($io, $composer);
-
- $install
- ->setVerbose($input->getOption('verbose'))
- ->setPreferSource($input->getOption('prefer-source'))
- ->setDevMode($input->getOption('dev'))
- ->setUpdate(true)
- ->setUpdateWhitelist($requirements);
- ;
+ $output->writeln('<info>' . $json->getPath() . ' has been updated</info>');
- return $install->run() ? 0 : 1;
+ return $requirements;
}
- private function updateFileCleanly($json, array $base, array $new, $requireKey)
+ protected function updateFileCleanly(JsonFile $json, array $base, array $new, $requireKey)
{
$contents = file_get_contents($json->getPath());
View
8 src/Composer/Console/Application.php
@@ -87,8 +87,8 @@ public function doRun(InputInterface $input, OutputInterface $output)
}
/**
- * @param bool $required
- * @return \Composer\Composer
+ * @param bool $required
+ * @return Composer
*/
public function getComposer($required = true)
{
@@ -137,6 +137,10 @@ protected function getDefaultCommands()
$commands[] = new Command\SelfUpdateCommand();
}
+ if (function_exists('symlink')) {
+ $commands[] = new Command\LinkCommand();
+ }
+
return $commands;
}
View
2  src/Composer/Downloader/DownloadManager.php
@@ -103,6 +103,8 @@ public function getDownloaderForInstalledPackage(PackageInterface $package)
$downloader = $this->getDownloader($package->getDistType());
} elseif ('source' === $installationSource) {
$downloader = $this->getDownloader($package->getSourceType());
+ } elseif ('link' === $installationSource) {
+ $downloader = $this->getDownloader($package->getSourceType());
} else {
throw new \InvalidArgumentException(
'Package '.$package.' seems not been installed properly'
View
4 src/Composer/Downloader/DownloaderInterface.php
@@ -23,9 +23,9 @@
interface DownloaderInterface
{
/**
- * Returns installation source (either source or dist).
+ * Returns installation source (either source, dist or link).
*
- * @return string "source" or "dist"
+ * @return string "source", "dist" or "link"
*/
public function getInstallationSource();
View
117 src/Composer/Downloader/LinkDownloader.php
@@ -0,0 +1,117 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Downloader;
+
+use Composer\IO\IOInterface;
+use Composer\Package\PackageInterface;
+use Composer\Package\Version\VersionParser;
+use Composer\Util\Filesystem;
+
+/**
+ * Base downloader for files
+ *
+ * @author Kirill chEbba Chebunin <iam@chebba.org>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @author François Pluchino <francois.pluchino@opendisplay.com>
+ * @author Filip Procházka <filip.prochazka@kdyby.org>
+ */
+class LinkDownloader implements DownloaderInterface
+{
+ protected $io;
+ protected $filesystem;
+
+ /**
+ * Constructor.
+ *
+ * @param IOInterface $io The IO instance
+ * @param Filesystem $filesystem
+ */
+ public function __construct(IOInterface $io, Filesystem $filesystem = null)
+ {
+ $this->io = $io;
+ $this->filesystem = $filesystem ?: new Filesystem();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getInstallationSource()
+ {
+ return 'link';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function download(PackageInterface $package, $path)
+ {
+ $url = $package->getDistUrl();
+ if (!$url) {
+ throw new \InvalidArgumentException('The given package is missing url information');
+ }
+
+ // ensure vendor directory exists
+ $this->filesystem->ensureDirectoryExists(dirname($path));
+
+ $this->io->write(" - Linking <info>" . $package->getName() . "</info>");
+
+ try {
+ $fileName = $this->getFileName($package, $path);
+ $this->filesystem->link($url, $fileName);
+
+ } catch (\Exception $e) {
+ // clean up
+ $this->filesystem->removeDirectory($path);
+ throw $e;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function update(PackageInterface $initial, PackageInterface $target, $path)
+ {
+ // It's a symlink! Do not change it, just
+ try {
+ $this->filesystem->ensureHealthyLink($path);
+
+ } catch (\Exception $e) {
+ $this->filesystem->removeDirectory($path);
+ throw $e;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function remove(PackageInterface $package, $path)
+ {
+ $this->io->write(" - Removing <info>" . $package->getName() . "</info>");
+ if (!$this->filesystem->removeDirectory($path)) {
+ throw new \RuntimeException('Could not unlink ' . $path . ', aborting.');
+ }
+ }
+
+ /**
+ * Gets file name for specific package
+ *
+ * @param PackageInterface $package package instance
+ * @param string $path download path
+ * @return string file name
+ */
+ protected function getFileName(PackageInterface $package, $path)
+ {
+ return $path . '/' . pathinfo($package->getDistUrl(), PATHINFO_BASENAME);
+ }
+
+}
View
11 src/Composer/Downloader/VcsDownloader.php
@@ -66,6 +66,17 @@ public function update(PackageInterface $initial, PackageInterface $target, $pat
throw new \InvalidArgumentException('Package '.$target->getPrettyName().' is missing reference information');
}
+ // manually symlinked package, do not touch
+ if (is_link($path)) {
+ try {
+ $this->filesystem->ensureHealthyLink($path);
+
+ } catch (\Exception $e) {
+ $this->filesystem->removeDirectory($path);
+ throw $e;
+ }
+ }
+
$name = $target->getName();
if ($initial->getPrettyVersion() == $target->getPrettyVersion()) {
if ($target->getSourceType() === 'svn') {
View
14 src/Composer/Factory.php
@@ -148,7 +148,7 @@ public function createComposer(IOInterface $io, $localConfig = null)
$rm = $this->createRepositoryManager($io, $config);
// load local repository
- $this->addLocalRepository($rm, $vendorDir);
+ $this->addLocalRepository($rm, $config);
// load package
$loader = new Package\Loader\RootPackageLoader($rm, $config);
@@ -201,18 +201,21 @@ protected function createRepositoryManager(IOInterface $io, Config $config)
$rm->setRepositoryClass('git', 'Composer\Repository\VcsRepository');
$rm->setRepositoryClass('svn', 'Composer\Repository\VcsRepository');
$rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository');
+ $rm->setRepositoryClass('link', 'Composer\Repository\LocalLinksRepository');
return $rm;
}
/**
* @param Repository\RepositoryManager $rm
- * @param string $vendorDir
+ * @param Config $config
*/
- protected function addLocalRepository(RepositoryManager $rm, $vendorDir)
+ protected function addLocalRepository(RepositoryManager $rm, Config $config)
{
- $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json')));
- $rm->setLocalDevRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed_dev.json')));
+ $composerDir = $config->get('vendor-dir') . '/composer';
+ $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($composerDir.'/installed.json')));
+ $rm->setLocalDevRepository(new Repository\InstalledFilesystemRepository(new JsonFile($composerDir.'/installed_dev.json')));
+ $rm->setLocalLinksRepository(new Repository\LocalLinksRepository($config));
}
/**
@@ -229,6 +232,7 @@ public function createDownloadManager(IOInterface $io)
$dm->setDownloader('tar', new Downloader\TarDownloader($io));
$dm->setDownloader('phar', new Downloader\PharDownloader($io));
$dm->setDownloader('file', new Downloader\FileDownloader($io));
+ $dm->setDownloader('link', new Downloader\LinkDownloader($io));
return $dm;
}
View
29 src/Composer/Installer.php
@@ -96,6 +96,7 @@ class Installer
protected $update = false;
protected $runScripts = true;
protected $updateWhitelist = null;
+ protected $preferLinks = array();
/**
* @var array
@@ -266,6 +267,23 @@ protected function doInstall($localRepo, $installedRepo, $aliases, $devMode = fa
foreach ($links as $link) {
$request->install($link->getTarget(), $link->getConstraint());
}
+
+ // request installation as links
+ $localLinksRepository = $this->repositoryManager->getLocalLinksRepository();
+
+ foreach ($this->preferLinks as $link => $void) {
+ list($packageName, $packageVersion) = explode(" ", $link, 2);
+ $linkedPackage = $localLinksRepository->findPackage($packageName, $packageVersion);
+ if (null === $linkedPackage) {
+ $this->io->write('<error>Source for link ' . $link . ' not found.</error>');
+ return false;
+ }
+
+ $constraint = new VersionConstraint('=', $linkedPackage->getVersion());
+ $request->install($linkedPackage->getName(), $constraint);
+ }
+ $this->preferLinks = array(); // todo: really?
+
} elseif ($this->locker->isLocked($devMode)) {
$installFromLock = true;
$this->io->write('<info>Installing '.($devMode ? 'dev ': '').'dependencies from lock file</info>');
@@ -727,6 +745,17 @@ public function setUpdateWhitelist(array $packages)
}
/**
+ * @param array $packages
+ * @return Installer
+ */
+ public function setPreferLinks(array $packages)
+ {
+ $this->preferLinks = array_flip(array_map('strtolower', $packages));
+
+ return $this;
+ }
+
+ /**
* Disables custom installers.
*
* Call this if you want to ensure that third-party code never gets
View
8 src/Composer/Package/PackageInterface.php
@@ -97,16 +97,16 @@ public function getTargetDir();
public function getExtra();
/**
- * Sets source from which this package was installed (source/dist).
+ * Sets source from which this package was installed (source/dist/link).
*
- * @param string $type source/dist
+ * @param string $type source/dist/link
*/
public function setInstallationSource($type);
/**
- * Returns source from which this package was installed (source/dist).
+ * Returns source from which this package was installed (source/dist/link).
*
- * @param string $type source/dist
+ * @param string $type source/dist/link
*/
public function getInstallationSource();
View
4 src/Composer/Repository/ArrayRepository.php
@@ -154,6 +154,10 @@ public function getPackages()
*/
public function count()
{
+ if (null === $this->packages) {
+ $this->initialize();
+ }
+
return count($this->packages);
}
View
219 src/Composer/Repository/LocalLinksRepository.php
@@ -0,0 +1,219 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Repository;
+
+use Composer\Json\JsonFile;
+use Composer\Package\Loader\ArrayLoader;
+use Composer\Package\Dumper\ArrayDumper;
+use Composer\Package\Version\VersionParser;
+use Composer\Factory;
+use Composer\IO\NullIO;
+use Composer\IO\IOInterface;
+use Composer\Package\PackageInterface;
+use Composer\Config;
+
+/**
+ * Package repository.
+ *
+ * @author Filip Procházka <filip.prochazka@kdyby.org>
+ */
+class LocalLinksRepository implements WritableRepositoryInterface
+{
+ private $config;
+ private $io;
+
+ private $linksJson;
+ private $packages;
+
+ public function __construct(Config $config = null, IOInterface $io = null)
+ {
+ $this->config = $config ?: Factory::createConfig();
+ $this->io = $io ?: new NullIO();
+
+ $this->linksJson = new JsonFile($this->config->get('home') . '/links.json');
+ }
+
+ public function addPackage(PackageInterface $package)
+ {
+ if (null === $this->packages) {
+ $this->reload();
+ }
+
+ $packageName = $package->getName();
+ if (empty($packageName) || $packageName === '__root__') {
+ throw new \InvalidArgumentException("Package has no name specified.");
+ }
+
+ if (isset($this->packages[$packageName])) {
+ return;
+ }
+
+ $sourcePath = realpath(str_replace('file://', '', $package->getSourceUrl()));
+ if ($sourcePath === false) {
+ throw new \InvalidArgumentException("Package " . $package->getPrettyName() . " must have a source url specified, that is in local filesystem.");
+ }
+
+ $this->packages[$packageName] = $sourcePath;
+ $this->write();
+ }
+
+ public function removePackage(PackageInterface $package)
+ {
+ if (null === $this->packages) {
+ $this->reload();
+ }
+
+ unset($this->packages[$package->getName()]);
+ $this->write();
+ }
+
+ /**
+ * @internal
+ */
+ public function reload()
+ {
+ if (!$this->linksJson->exists()) {
+ return;
+ }
+
+ $this->packages = array();
+ foreach ($this->linksJson->read() as $package) {
+ $this->packages[$package['package']] = $package['path'];
+ }
+ }
+
+ /**
+ * @internal
+ */
+ public function write()
+ {
+ if (null === $this->packages) {
+ return;
+ }
+
+ $data = array();
+ foreach ($this->packages as $packageName => $sourcePath) {
+ $data[] = array('package' => $packageName, 'path' => $sourcePath);
+ }
+
+ $this->linksJson->write($data);
+ }
+
+ public function hasPackage(PackageInterface $package)
+ {
+ if (null === $this->packages) {
+ $this->reload();
+ }
+
+ return isset($this->packages[$package->getName()]);
+ }
+
+ public function findPackage($name, $version)
+ {
+ if (null === $this->packages) {
+ $this->reload();
+ }
+
+ // normalize name
+ $name = strtolower($name);
+
+ if (!isset($this->packages[$name])) {
+ return;
+ }
+
+ // normalize version & name
+ $versionParser = new VersionParser();
+ $version = $versionParser->normalize($version);
+
+ foreach ($this->packageVersions($name) as $package) {
+ if ($name === $package->getName() && $version === $package->getVersion()) {
+ $package->setInstallationSource('link');
+// $package->setSourceType('link');
+ return $package;
+ }
+ }
+ }
+
+ public function findPackages($name, $version = null)
+ {
+ if (null === $this->packages) {
+ $this->reload();
+ }
+
+ // normalize name
+ $name = strtolower($name);
+
+ $packages = array();
+
+ if (!isset($this->packages[$name])) {
+ return $packages;
+ }
+
+ // normalize version
+ if (null !== $version) {
+ $versionParser = new VersionParser();
+ $version = $versionParser->normalize($version);
+ }
+
+ foreach ($this->packageVersions($name) as $package) {
+ if ($package->getName() === $name && (null === $version || $version === $package->getVersion())) {
+ $package->setInstallationSource('link');
+// $package->setSourceType('link');
+ $packages[] = $package;
+ }
+ }
+
+ return $packages;
+ }
+
+ public function getPackages()
+ {
+ if (null === $this->packages) {
+ $this->reload();
+ }
+
+ $packages = array();
+
+ foreach ($this->packages as $packageName => $sourcePath) {
+ $packages[] = $this->findPackages($packageName);
+ }
+
+ return call_user_func_array('array_merge', $packages);
+ }
+
+ public function count()
+ {
+ if (null === $this->packages) {
+ $this->reload();
+ }
+
+ return count($this->packages);
+ }
+
+ private function packageVersions($name)
+ {
+ $repoConf = array('url' => $this->packages[$name], 'type' => 'vcs');
+ $vcsRepo = new VcsRepository($repoConf, $this->io, $this->config);
+
+ $packages = array();
+ foreach ($vcsRepo->getPackages() as $package) {
+ /** @var \Composer\Package\MemoryPackage $package */
+ $package = clone $package;
+ $package->setRepository($this);
+ $packages[] = $package;
+ }
+
+ return $packages;
+ }
+
+}
View
18 src/Composer/Repository/RepositoryManager.php
@@ -26,6 +26,7 @@ class RepositoryManager
{
private $localRepository;
private $localDevRepository;
+ private $localLinksRepository;
private $repositories = array();
private $repositoryClasses = array();
private $io;
@@ -164,6 +165,23 @@ public function getLocalDevRepository()
}
/**
+ * @param RepositoryInterface $repository repository instance
+ */
+ public function setLocalLinksRepository(RepositoryInterface $repository)
+ {
+ $this->repositories[] = $repository;
+ $this->localLinksRepository = $repository;
+ }
+
+ /**
+ * @return RepositoryInterface
+ */
+ public function getLocalLinksRepository()
+ {
+ return $this->localLinksRepository;
+ }
+
+ /**
* Returns all local repositories for the project.
*
* @return array[WritableRepositoryInterface]
View
50 src/Composer/Util/Filesystem.php
@@ -27,7 +27,10 @@ public function __construct(ProcessExecutor $executor = null)
public function removeDirectory($directory)
{
- if (!is_dir($directory)) {
+ if (is_link($directory)) {
+ return unlink($directory);
+
+ } elseif (!is_dir($directory)) {
return true;
}
@@ -47,17 +50,19 @@ public function removeDirectory($directory)
public function ensureDirectoryExists($directory)
{
- if (!is_dir($directory)) {
- if (file_exists($directory)) {
- throw new \RuntimeException(
- $directory.' exists and is not a directory.'
- );
- }
- if (!mkdir($directory, 0777, true)) {
- throw new \RuntimeException(
- $directory.' does not exist and could not be created.'
- );
- }
+ if (is_dir($directory)) {
+ return;
+ }
+
+ if (file_exists($directory)) {
+ throw new \RuntimeException(
+ $directory . ' exists and is not a directory.'
+ );
+ }
+ if (!mkdir($directory, 0777, true)) {
+ throw new \RuntimeException(
+ $directory . ' does not exist and could not be created.'
+ );
}
}
@@ -80,6 +85,27 @@ public function rename($source, $target)
}
}
+ public function link($source, $target)
+ {
+ $source = realpath($source);
+ if (!$source) {
+ throw new \InvalidArgumentException(sprintf("(%) is not a local filesystem path". func_get_arg(0)));
+ }
+
+ symlink($source, $target);
+
+ if (!is_link($target) || !file_exists($target)) {
+ throw new \RuntimeException(sprintf('Could link "%s" to "%s".', $source, $target));
+ }
+ }
+
+ public function ensureHealthyLink($target)
+ {
+ if (!is_link($target) || !file_exists($target)) {
+ throw new \RuntimeException(sprintf('Link "%s" is broken.', $target));
+ }
+ }
+
/**
* Returns the shortest path from $from to $to
*
Please sign in to comment.
Something went wrong with that request. Please try again.