Merge pull request #5100 from helhum/master
Extract binary installation and removal to own classpull/5116/head
commit
6527bb4166
|
@ -0,0 +1,205 @@
|
|||
<?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\Installer;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\Silencer;
|
||||
|
||||
/**
|
||||
* Package installation manager.
|
||||
*
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
* @author Helmut Hummel <info@helhum.io>
|
||||
*/
|
||||
class LibraryBinariesHandler
|
||||
{
|
||||
protected $binDir;
|
||||
protected $binCompat;
|
||||
protected $io;
|
||||
protected $filesystem;
|
||||
|
||||
/**
|
||||
* LibraryBinaryHandler constructor.
|
||||
*
|
||||
* @param string $binDir
|
||||
* @param string $binCompat
|
||||
* @param IOInterface $io
|
||||
* @param Filesystem $filesystem
|
||||
*/
|
||||
public function __construct($binDir, $binCompat, IOInterface $io, Filesystem $filesystem = null)
|
||||
{
|
||||
$this->binDir = $binDir;
|
||||
$this->binCompat = $binCompat;
|
||||
$this->io = $io;
|
||||
$this->filesystem = $filesystem ?: new Filesystem();
|
||||
}
|
||||
|
||||
public function installBinaries(PackageInterface $package, $installPath)
|
||||
{
|
||||
$binaries = $package->getBinaries();
|
||||
if (!$binaries) {
|
||||
return;
|
||||
}
|
||||
foreach ($binaries as $bin) {
|
||||
$binPath = $installPath.'/'.$bin;
|
||||
if (!file_exists($binPath)) {
|
||||
$this->io->writeError(' <warning>Skipped installation of bin '.$bin.' for package '.$package->getName().': file not found in package</warning>');
|
||||
continue;
|
||||
}
|
||||
|
||||
// in case a custom installer returned a relative path for the
|
||||
// $package, we can now safely turn it into a absolute path (as we
|
||||
// already checked the binary's existence). The following helpers
|
||||
// will require absolute paths to work properly.
|
||||
$binPath = realpath($binPath);
|
||||
|
||||
$this->initializeBinDir();
|
||||
$link = $this->binDir.'/'.basename($bin);
|
||||
if (file_exists($link)) {
|
||||
if (is_link($link)) {
|
||||
// likely leftover from a previous install, make sure
|
||||
// that the target is still executable in case this
|
||||
// is a fresh install of the vendor.
|
||||
Silencer::call('chmod', $link, 0777 & ~umask());
|
||||
}
|
||||
$this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file');
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->binCompat === "auto") {
|
||||
if (Platform::isWindows()) {
|
||||
$this->installFullBinaries($binPath, $link, $bin, $package);
|
||||
} else {
|
||||
$this->installSymlinkBinaries($binPath, $link);
|
||||
}
|
||||
} elseif ($this->binCompat === "full") {
|
||||
$this->installFullBinaries($binPath, $link, $bin, $package);
|
||||
}
|
||||
Silencer::call('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)
|
||||
{
|
||||
if (!$this->filesystem->relativeSymlink($binPath, $link)) {
|
||||
$this->installUnixyProxyBinaries($binPath, $link);
|
||||
}
|
||||
}
|
||||
|
||||
protected function installUnixyProxyBinaries($binPath, $link)
|
||||
{
|
||||
file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link));
|
||||
}
|
||||
|
||||
public function removeBinaries(PackageInterface $package)
|
||||
{
|
||||
$binaries = $package->getBinaries();
|
||||
if (!$binaries) {
|
||||
return;
|
||||
}
|
||||
foreach ($binaries as $bin) {
|
||||
$link = $this->binDir.'/'.basename($bin);
|
||||
if (is_link($link) || file_exists($link)) {
|
||||
$this->filesystem->unlink($link);
|
||||
}
|
||||
if (file_exists($link.'.bat')) {
|
||||
$this->filesystem->unlink($link.'.bat');
|
||||
}
|
||||
}
|
||||
|
||||
// attempt removing the bin dir in case it is left empty
|
||||
if ((is_dir($this->binDir)) && ($this->filesystem->isDirEmpty($this->binDir))) {
|
||||
Silencer::call('rmdir', $this->binDir);
|
||||
}
|
||||
}
|
||||
|
||||
protected function initializeBinDir()
|
||||
{
|
||||
$this->filesystem->ensureDirectoryExists($this->binDir);
|
||||
$this->binDir = realpath($this->binDir);
|
||||
}
|
||||
|
||||
protected function generateWindowsProxyCode($bin, $link)
|
||||
{
|
||||
$binPath = $this->filesystem->findShortestPath($link, $bin);
|
||||
if ('.bat' === substr($bin, -4) || '.exe' === substr($bin, -4)) {
|
||||
$caller = 'call';
|
||||
} else {
|
||||
$handle = fopen($bin, 'r');
|
||||
$line = fgets($handle);
|
||||
fclose($handle);
|
||||
if (preg_match('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', $line, $match)) {
|
||||
$caller = trim($match[1]);
|
||||
} else {
|
||||
$caller = 'php';
|
||||
}
|
||||
}
|
||||
|
||||
return "@ECHO OFF\r\n".
|
||||
"setlocal DISABLEDELAYEDEXPANSION\r\n".
|
||||
"SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape($binPath), '"')."\r\n".
|
||||
"{$caller} \"%BIN_TARGET%\" %*\r\n";
|
||||
}
|
||||
|
||||
protected function generateUnixyProxyCode($bin, $link)
|
||||
{
|
||||
$binPath = $this->filesystem->findShortestPath($link, $bin);
|
||||
|
||||
$binDir = ProcessExecutor::escape(dirname($binPath));
|
||||
$binFile = basename($binPath);
|
||||
|
||||
$proxyCode = <<<PROXY
|
||||
#!/usr/bin/env sh
|
||||
|
||||
dir=$(d=\${0%[/\\\\]*}; cd "\$d"; cd $binDir && pwd)
|
||||
|
||||
# See if we are running in Cygwin by checking for cygpath program
|
||||
if command -v 'cygpath' >/dev/null 2>&1; then
|
||||
# Cygwin paths start with /cygdrive/ which will break windows PHP,
|
||||
# so we need to translate the dir path to windows format. However
|
||||
# we could be using cygwin PHP which does not require this, so we
|
||||
# test if the path to PHP starts with /cygdrive/ rather than /usr/bin
|
||||
if [[ $(which php) == /cygdrive/* ]]; then
|
||||
dir=$(cygpath -m "\$dir");
|
||||
fi
|
||||
fi
|
||||
|
||||
dir=$(echo \$dir | sed 's/ /\ /g')
|
||||
"\${dir}/$binFile" "$@"
|
||||
|
||||
PROXY;
|
||||
|
||||
return $proxyCode;
|
||||
}
|
||||
}
|
|
@ -17,8 +17,6 @@ use Composer\IO\IOInterface;
|
|||
use Composer\Repository\InstalledRepositoryInterface;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Platform;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\Silencer;
|
||||
|
||||
/**
|
||||
|
@ -37,16 +35,18 @@ class LibraryInstaller implements InstallerInterface
|
|||
protected $type;
|
||||
protected $filesystem;
|
||||
protected $binCompat;
|
||||
protected $libraryBinariesHandler;
|
||||
|
||||
/**
|
||||
* Initializes library installer.
|
||||
*
|
||||
* @param IOInterface $io
|
||||
* @param Composer $composer
|
||||
* @param string $type
|
||||
* @param Filesystem $filesystem
|
||||
* @param IOInterface $io
|
||||
* @param Composer $composer
|
||||
* @param string $type
|
||||
* @param Filesystem $filesystem
|
||||
* @param LibraryBinariesHandler $libraryBinariesHandler
|
||||
*/
|
||||
public function __construct(IOInterface $io, Composer $composer, $type = 'library', Filesystem $filesystem = null)
|
||||
public function __construct(IOInterface $io, Composer $composer, $type = 'library', Filesystem $filesystem = null, LibraryBinariesHandler $libraryBinariesHandler = null)
|
||||
{
|
||||
$this->composer = $composer;
|
||||
$this->downloadManager = $composer->getDownloadManager();
|
||||
|
@ -55,8 +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');
|
||||
$this->libraryBinariesHandler = $libraryBinariesHandler ?: new LibraryBinariesHandler(rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $this->io, $this->filesystem);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,11 +84,11 @@ class LibraryInstaller implements InstallerInterface
|
|||
|
||||
// remove the binaries if it appears the package files are missing
|
||||
if (!is_readable($downloadPath) && $repo->hasPackage($package)) {
|
||||
$this->removeBinaries($package);
|
||||
$this->libraryBinariesHandler->removeBinaries($package);
|
||||
}
|
||||
|
||||
$this->installCode($package);
|
||||
$this->installBinaries($package);
|
||||
$this->libraryBinariesHandler->installBinaries($package, $this->getInstallPath($package));
|
||||
if (!$repo->hasPackage($package)) {
|
||||
$repo->addPackage(clone $package);
|
||||
}
|
||||
|
@ -106,9 +105,9 @@ class LibraryInstaller implements InstallerInterface
|
|||
|
||||
$this->initializeVendorDir();
|
||||
|
||||
$this->removeBinaries($initial);
|
||||
$this->libraryBinariesHandler->removeBinaries($initial);
|
||||
$this->updateCode($initial, $target);
|
||||
$this->installBinaries($target);
|
||||
$this->libraryBinariesHandler->installBinaries($target, $this->getInstallPath($target));
|
||||
$repo->removePackage($initial);
|
||||
if (!$repo->hasPackage($target)) {
|
||||
$repo->addPackage(clone $target);
|
||||
|
@ -125,7 +124,7 @@ class LibraryInstaller implements InstallerInterface
|
|||
}
|
||||
|
||||
$this->removeCode($package);
|
||||
$this->removeBinaries($package);
|
||||
$this->libraryBinariesHandler->removeBinaries($package);
|
||||
$repo->removePackage($package);
|
||||
|
||||
$downloadPath = $this->getPackageBasePath($package);
|
||||
|
@ -204,168 +203,9 @@ class LibraryInstaller implements InstallerInterface
|
|||
$this->downloadManager->remove($package, $downloadPath);
|
||||
}
|
||||
|
||||
protected function getBinaries(PackageInterface $package)
|
||||
{
|
||||
return $package->getBinaries();
|
||||
}
|
||||
|
||||
protected function installBinaries(PackageInterface $package)
|
||||
{
|
||||
$binaries = $this->getBinaries($package);
|
||||
if (!$binaries) {
|
||||
return;
|
||||
}
|
||||
foreach ($binaries as $bin) {
|
||||
$binPath = $this->getInstallPath($package).'/'.$bin;
|
||||
if (!file_exists($binPath)) {
|
||||
$this->io->writeError(' <warning>Skipped installation of bin '.$bin.' for package '.$package->getName().': file not found in package</warning>');
|
||||
continue;
|
||||
}
|
||||
|
||||
// in case a custom installer returned a relative path for the
|
||||
// $package, we can now safely turn it into a absolute path (as we
|
||||
// already checked the binary's existence). The following helpers
|
||||
// will require absolute paths to work properly.
|
||||
$binPath = realpath($binPath);
|
||||
|
||||
$this->initializeBinDir();
|
||||
$link = $this->binDir.'/'.basename($bin);
|
||||
if (file_exists($link)) {
|
||||
if (is_link($link)) {
|
||||
// likely leftover from a previous install, make sure
|
||||
// that the target is still executable in case this
|
||||
// is a fresh install of the vendor.
|
||||
Silencer::call('chmod', $link, 0777 & ~umask());
|
||||
}
|
||||
$this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file');
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->binCompat === "auto") {
|
||||
if (Platform::isWindows()) {
|
||||
$this->installFullBinaries($binPath, $link, $bin, $package);
|
||||
} else {
|
||||
$this->installSymlinkBinaries($binPath, $link);
|
||||
}
|
||||
} elseif ($this->binCompat === "full") {
|
||||
$this->installFullBinaries($binPath, $link, $bin, $package);
|
||||
}
|
||||
Silencer::call('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)
|
||||
{
|
||||
if (!$this->filesystem->relativeSymlink($binPath, $link)) {
|
||||
$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);
|
||||
if (!$binaries) {
|
||||
return;
|
||||
}
|
||||
foreach ($binaries as $bin) {
|
||||
$link = $this->binDir.'/'.basename($bin);
|
||||
if (is_link($link) || file_exists($link)) {
|
||||
$this->filesystem->unlink($link);
|
||||
}
|
||||
if (file_exists($link.'.bat')) {
|
||||
$this->filesystem->unlink($link.'.bat');
|
||||
}
|
||||
}
|
||||
|
||||
// attempt removing the bin dir in case it is left empty
|
||||
if ((is_dir($this->binDir)) && ($this->filesystem->isDirEmpty($this->binDir))) {
|
||||
Silencer::call('rmdir', $this->binDir);
|
||||
}
|
||||
}
|
||||
|
||||
protected function initializeVendorDir()
|
||||
{
|
||||
$this->filesystem->ensureDirectoryExists($this->vendorDir);
|
||||
$this->vendorDir = realpath($this->vendorDir);
|
||||
}
|
||||
|
||||
protected function initializeBinDir()
|
||||
{
|
||||
$this->filesystem->ensureDirectoryExists($this->binDir);
|
||||
$this->binDir = realpath($this->binDir);
|
||||
}
|
||||
|
||||
protected function generateWindowsProxyCode($bin, $link)
|
||||
{
|
||||
$binPath = $this->filesystem->findShortestPath($link, $bin);
|
||||
if ('.bat' === substr($bin, -4) || '.exe' === substr($bin, -4)) {
|
||||
$caller = 'call';
|
||||
} else {
|
||||
$handle = fopen($bin, 'r');
|
||||
$line = fgets($handle);
|
||||
fclose($handle);
|
||||
if (preg_match('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', $line, $match)) {
|
||||
$caller = trim($match[1]);
|
||||
} else {
|
||||
$caller = 'php';
|
||||
}
|
||||
}
|
||||
|
||||
return "@ECHO OFF\r\n".
|
||||
"setlocal DISABLEDELAYEDEXPANSION\r\n".
|
||||
"SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape($binPath), '"')."\r\n".
|
||||
"{$caller} \"%BIN_TARGET%\" %*\r\n";
|
||||
}
|
||||
|
||||
protected function generateUnixyProxyCode($bin, $link)
|
||||
{
|
||||
$binPath = $this->filesystem->findShortestPath($link, $bin);
|
||||
|
||||
$binDir = ProcessExecutor::escape(dirname($binPath));
|
||||
$binFile = basename($binPath);
|
||||
|
||||
$proxyCode = <<<PROXY
|
||||
#!/usr/bin/env sh
|
||||
|
||||
dir=$(d=\${0%[/\\\\]*}; cd "\$d"; cd $binDir && pwd)
|
||||
|
||||
# See if we are running in Cygwin by checking for cygpath program
|
||||
if command -v 'cygpath' >/dev/null 2>&1; then
|
||||
# Cygwin paths start with /cygdrive/ which will break windows PHP,
|
||||
# so we need to translate the dir path to windows format. However
|
||||
# we could be using cygwin PHP which does not require this, so we
|
||||
# test if the path to PHP starts with /cygdrive/ rather than /usr/bin
|
||||
if [[ $(which php) == /cygdrive/* ]]; then
|
||||
dir=$(cygpath -m "\$dir");
|
||||
fi
|
||||
fi
|
||||
|
||||
dir=$(echo \$dir | sed 's/ /\ /g')
|
||||
"\${dir}/$binFile" "$@"
|
||||
|
||||
PROXY;
|
||||
|
||||
return $proxyCode;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,22 +143,22 @@ class LibraryInstallerTest extends TestCase
|
|||
$target = $this->createPackageMock();
|
||||
|
||||
$initial
|
||||
->expects($this->once())
|
||||
->expects($this->any())
|
||||
->method('getPrettyName')
|
||||
->will($this->returnValue('package1'));
|
||||
|
||||
$initial
|
||||
->expects($this->once())
|
||||
->expects($this->any())
|
||||
->method('getTargetDir')
|
||||
->will($this->returnValue('oldtarget'));
|
||||
|
||||
$target
|
||||
->expects($this->once())
|
||||
->expects($this->any())
|
||||
->method('getPrettyName')
|
||||
->will($this->returnValue('package1'));
|
||||
|
||||
$target
|
||||
->expects($this->once())
|
||||
->expects($this->any())
|
||||
->method('getTargetDir')
|
||||
->will($this->returnValue('newtarget'));
|
||||
|
||||
|
|
Loading…
Reference in New Issue