diff --git a/doc/06-config.md b/doc/06-config.md index f07b8ea57..05c0c3592 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -102,6 +102,15 @@ downloads. When the garbage collection is periodically ran, this is the maximum size the cache will be able to use. Older (less used) files will be removed first until the cache fits. +## bin-compat + +Defaults to `auto`. Determines the compatibility of the binaries to be installed. +If it is `auto` then Composer tries to automatically guess which compatibility mode +to use. If it is `nosymlink` then the binaries will be compatible with Unix-based +operating systems (useful for cases when symlinks are not available). +If its value is `full` then both .bat files for Windows and scripts for Unix-based +operating systems will be installed for each binary. + ## prepend-autoloader Defaults to `true`. If `false`, the Composer autoloader will not be prepended to diff --git a/res/composer-schema.json b/res/composer-schema.json index d427bfcb5..ec7d5869e 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -186,6 +186,10 @@ "type": ["string", "integer"], "description": "The cache max size for the files cache, defaults to \"300MiB\"." }, + "bin-compat": { + "enum": ["auto", "nosymlink", "full"], + "description": "The compatibility of the binaries, defaults to \"auto\" (automatically guessed) and can be \"nosymlink\" (compatible with Unix-based systems) or \"full\" (compatible with both Windows and Unix-based systems)." + }, "discard-changes": { "type": ["string", "boolean"], "description": "The default style of handling dirty updates, defaults to false and can be any of true, false or \"stash\"." diff --git a/src/Composer/Command/ConfigCommand.php b/src/Composer/Command/ConfigCommand.php index b1a1a9fa4..f65191f2b 100644 --- a/src/Composer/Command/ConfigCommand.php +++ b/src/Composer/Command/ConfigCommand.php @@ -307,6 +307,10 @@ EOT function ($val) { return preg_match('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $val) > 0; }, function ($val) { return $val; }, ), + 'bin-compat' => array( + function ($val) { return in_array($val, array('auto', 'nosymlink', 'full')); }, + function ($val) { return $val; } + ), 'discard-changes' => array( function ($val) { return in_array($val, array('stash', 'true', 'false', '1', '0'), true); }, function ($val) { diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 7d9d7f1a3..91f31b084 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -36,6 +36,7 @@ class Config 'cache-ttl' => 15552000, // 6 months 'cache-files-ttl' => null, // fallback to cache-ttl 'cache-files-maxsize' => '300MiB', + 'bin-compat' => 'auto', 'discard-changes' => false, 'autoloader-suffix' => null, 'optimize-autoloader' => false, @@ -218,6 +219,17 @@ class Config case 'home': return rtrim($this->process($this->config[$key], $flags), '/\\'); + case 'bin-compat': + $value = $this->getComposerEnv('COMPOSER_BIN_COMPAT') ?: $this->config[$key]; + + if (!in_array($value, array('auto', 'nosymlink', 'full'))) { + throw new \RuntimeException( + "Invalid value for 'bin-compat': {$value}. Expected auto, nosymlink, full" + ); + } + + return $value; + case 'discard-changes': if ($env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES')) { if (!in_array($env, array('stash', 'true', 'false', '1', '0'), true)) { diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 6d4c57a71..769769c90 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -18,6 +18,7 @@ use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; use Composer\Util\Filesystem; use Composer\Util\ProcessExecutor; +use Composer\Util\Symlink; /** * Package installation manager. @@ -34,6 +35,7 @@ class LibraryInstaller implements InstallerInterface protected $io; protected $type; protected $filesystem; + protected $binCompat; /** * Initializes library installer. @@ -53,6 +55,7 @@ class LibraryInstaller implements InstallerInterface $this->filesystem = $filesystem ?: new Filesystem(); $this->vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/'); $this->binDir = rtrim($composer->getConfig()->get('bin-dir'), '/'); + $this->binCompat = $composer->getConfig()->get('bin-compat'); } /** @@ -219,38 +222,53 @@ class LibraryInstaller implements InstallerInterface $this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file'); continue; } - if (defined('PHP_WINDOWS_VERSION_BUILD')) { - // add unixy support for cygwin and similar environments - if ('.bat' !== substr($binPath, -4)) { - file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link)); - @chmod($link, 0777 & ~umask()); - $link .= '.bat'; - if (file_exists($link)) { - $this->io->writeError(' Skipped installation of bin '.$bin.'.bat proxy for package '.$package->getName().': a .bat proxy was already installed'); - } + + if ($this->binCompat === "auto") { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->installFullBinaries($binPath, $link, $bin, $package); + } else { + $this->installSymlinkBinaries($binPath, $link); } - if (!file_exists($link)) { - file_put_contents($link, $this->generateWindowsProxyCode($binPath, $link)); - } - } else { - $cwd = getcwd(); - try { - // under linux symlinks are not always supported for example - // when using it in smbfs mounted folder - $relativeBin = $this->filesystem->findShortestPath($link, $binPath); - chdir(dirname($link)); - if (false === @symlink($relativeBin, $link)) { - throw new \ErrorException(); - } - } catch (\ErrorException $e) { - file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link)); - } - chdir($cwd); + } elseif ($this->binCompat === "nosymlink") { + $this->installUnixyProxyBinaries($binPath, $link); + } elseif ($this->binCompat === "full") { + $this->installFullBinaries($binPath, $link, $bin, $package); } @chmod($link, 0777 & ~umask()); } } + protected function installFullBinaries($binPath, $link, $bin, PackageInterface $package) + { + // add unixy support for cygwin and similar environments + if ('.bat' !== substr($binPath, -4)) { + $this->installUnixyProxyBinaries($binPath, $link); + @chmod($link, 0777 & ~umask()); + $link .= '.bat'; + if (file_exists($link)) { + $this->io->writeError(' Skipped installation of bin '.$bin.'.bat proxy for package '.$package->getName().': a .bat proxy was already installed'); + } + } + if (!file_exists($link)) { + file_put_contents($link, $this->generateWindowsProxyCode($binPath, $link)); + } + } + + protected function installSymlinkBinaries($binPath, $link) + { + try { + $symlink = new Symlink($this->filesystem); + $symlink->symlinkBin($binPath, $link); + } catch (\ErrorException $e) { + $this->installUnixyProxyBinaries($binPath, $link); + } + } + + protected function installUnixyProxyBinaries($binPath, $link) + { + file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link)); + } + protected function removeBinaries(PackageInterface $package) { $binaries = $this->getBinaries($package); diff --git a/src/Composer/Util/Symlink.php b/src/Composer/Util/Symlink.php new file mode 100644 index 000000000..9735e0223 --- /dev/null +++ b/src/Composer/Util/Symlink.php @@ -0,0 +1,54 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\Config; + +/** + * @author Kocsis Máté + */ +class Symlink +{ + protected $filesystem; + + /** + * Initializes the symlinking utility. + * + * @param Filesystem $filesystem + */ + public function __construct(Filesystem $filesystem = null) + { + $this->filesystem = $filesystem ?: new Filesystem(); + } + + /** + * Creates a symlink for a binary file at a given path. + * + * @param string $binPath The path of the binary file to be symlinked + * @param string $link The path where the symlink should be created + * @throws \ErrorException + */ + public function symlinkBin($binPath, $link) + { + $cwd = getcwd(); + + $relativeBin = $this->filesystem->findShortestPath($link, $binPath); + chdir(dirname($link)); + $result = @symlink($relativeBin, $link); + + chdir($cwd); + if ($result === false) { + throw new \ErrorException(); + } + } +}