Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

WIP: Link command #1017

Open
wants to merge 2 commits into from
@fprochazka

First of all, this pullrequest is a work in progress.

I'm determined to finish this, but I've got stuck for three days now. I have no clue how to force the composer to use the package, that has installation-source: link that I've provided. The DependencyResolver is just a gibberish to me.

If you guys could just push me in the right direction I'd be really happy to finish this. #601

@travisbot

This pull request fails (merged f48887b into ab38ee3).

@jmather

Does this alter the composer.json at all? Also -- where are you at on this? What needs to be done still?

@stof stof commented on the diff
src/Composer/Command/LinkCommand.php
((57 lines not shown))
+ <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) {
@stof
stof added a note

This should simply be if as the previous if returns

You really care about the coding standard, even if it doesn't work?

I was looking for the equivalent of npm link for composer & found this. It made me LOL. That's totally @stof's MO :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@stof stof commented on the diff
src/Composer/Command/LinkCommand.php
((65 lines not shown))
+
+ 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;
@stof
stof added a note

please lowercase null

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@stof stof commented on the diff
src/Composer/Command/LinkCommand.php
((78 lines not shown))
+
+ $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 {
@stof
stof added a note

same here. Using else is not needed as the if returns

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@stof stof commented on the diff
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);
@stof
stof added a note

This is not enough on windows.

Btw, this class should probably extend the one from Filesystem component (which supports removing symlinks properly for instance)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@fprochazka

The state didn't changed... It's so long from I've written it, that I don't think I even remember what this does. I'm currently thinking about closing this PR, because somebody else, who understands the "Solver mess" could do it much faster than me.

@mneuhaus

This seems like it could be a way to solve my situation described in #1299, so +1 from me :)
Any idea/eta when or if this will be finished?

@fprochazka

No idea... it's way too complex for me to solve it on my own.

@chEbba

I had an idea to have a local system repository in #915 this is not a unique idea for package managers / dependency resolvers, ex. maven use such repository.
So i think this ideas can be merged:

  • Local system repository
  • Command to add package to this repository with --link flag
  • After package is installed as a dependency in another project use link command to change copy to symlink
  • Mark packages as "linked" in local repositories (local & devlocal)
  • Is installation type "link" really needed or it can be one of dist/source? And i think dir downloader from #1266 is ok
  • I don't see a pretty way to link package on installation because you need to define somewhere which packages should be linked and which should not . But some --use-links flag may be added for install/update commands.

BTW. I think this feature can be useful, but the most of problems that people want to solve with it can be solved (and should be soved) in another ways.
The main problem in composer-user community is a versioning: ton of packages with dev-stability which depends on dev packages.
More over. Problems with repeated composer update is a worflow problem and not the composer one.

@Petah

Any progress?

@glen-84

I also really need this, see my explanation here.

BTW, on Windows you need admin privs in order to create links, how will this be handled?

I hope this gets added soon!

@stof stof commented on the diff
src/Composer/Installer.php
@@ -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));
@stof
stof added a note

as all you are doing is iterating, you don't really need to flip it and then ignore values

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@stof stof commented on the diff
src/Composer/Repository/LocalLinksRepository.php
((28 lines not shown))
+ * @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');
@stof
stof added a note

should links really be shared between projects ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@glen-84

@Seldaek, are you able to help @HosipLan with this?

@Seldaek
Owner

Able is one thing but I just don't have enough time these days to work on new stuff. I try to keep on top of new PRs/issues but it already takes me too much time.

@nvanheuverzwijn

me want :+1:
Is there anything we can do to help with this feature ? This would be REALLY useful !

@webmozart

I'll put myself on the list to say I'd love this :) :+1:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 20, 2012
  1. @fprochazka
  2. @fprochazka

    Link command WIP

    fprochazka authored
This page is out of date. Refresh to see the latest.
View
4 src/Composer/Command/CreateProjectCommand.php
@@ -84,7 +84,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$input->getOption('repository-url'),
$input->getOption('no-custom-installers'),
$input->getOption('no-scripts')
- );
+ ) ? 0 : 1;
}
public function installProject(IOInterface $io, $packageName, $directory = null, $version = null, $preferSource = false, $installDevPackages = false, $repositoryUrl = null, $disableCustomInstallers = false, $noScripts = false)
@@ -155,7 +155,7 @@ public function installProject(IOInterface $io, $packageName, $directory = null,
$installer->disableCustomInstallers();
}
- $installer->run();
+ return $installer->run();
}
protected function createDownloadManager(IOInterface $io)
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) {
@stof
stof added a note

This should simply be if as the previous if returns

You really care about the coding standard, even if it doesn't work?

I was looking for the equivalent of npm link for composer & found this. It made me LOL. That's totally @stof's MO :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ $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;
@stof
stof added a note

please lowercase null

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ $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 {
@stof
stof added a note

same here. Using else is not needed as the if returns

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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));
@stof
stof added a note

as all you are doing is iterating, you don't really need to flip it and then ignore values

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ 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');
@stof
stof added a note

should links really be shared between projects ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ }
+
+ 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);
@stof
stof added a note

This is not enough on windows.

Btw, this class should probably extend the one from Filesystem component (which supports removing symlinks properly for instance)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ } 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
*
Something went wrong with that request. Please try again.