Extract binary installation and removal to own class
The functionality to install binaries might be useful for other installers. Create API for that by extracting this functionality from the LibraryInstaller class.pull/5100/head
parent
a8e9df55dc
commit
b1ec99faed
|
@ -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