class BinaryInstaller
Utility to handle installation of package "bin"/binaries
@author Jordi Boggiano <j.boggiano@seld.be> @author Konstantin Kudryashov <ever.zet@gmail.com> @author Helmut Hummel <info@helhum.io>
Hierarchy
- class \Composer\Installer\BinaryInstaller
Expanded class hierarchy of BinaryInstaller
2 files declare their use of BinaryInstaller
- EventDispatcher.php in vendor/
composer/ composer/ src/ Composer/ EventDispatcher/ EventDispatcher.php - Installer.php in vendor/
composer/ installers/ src/ Composer/ Installers/ Installer.php
File
-
vendor/
composer/ composer/ src/ Composer/ Installer/ BinaryInstaller.php, line 30
Namespace
Composer\InstallerView source
class BinaryInstaller {
/** @var string */
protected $binDir;
/** @var string */
protected $binCompat;
/** @var IOInterface */
protected $io;
/** @var Filesystem */
protected $filesystem;
/** @var string|null */
private $vendorDir;
/**
* @param Filesystem $filesystem
*/
public function __construct(IOInterface $io, string $binDir, string $binCompat, ?Filesystem $filesystem = null, ?string $vendorDir = null) {
$this->binDir = $binDir;
$this->binCompat = $binCompat;
$this->io = $io;
$this->filesystem = $filesystem ?: new Filesystem();
$this->vendorDir = $vendorDir;
}
public function installBinaries(PackageInterface $package, string $installPath, bool $warnOnOverwrite = true) : void {
$binaries = $this->getBinaries($package);
if (!$binaries) {
return;
}
Platform::workaroundFilesystemIssues();
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;
}
if (is_dir($binPath)) {
$this->io
->writeError(' <warning>Skipped installation of bin ' . $bin . ' for package ' . $package->getName() . ': found a directory at that path</warning>');
continue;
}
if (!$this->filesystem
->isAbsolutePath($binPath)) {
// 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)) {
if ($warnOnOverwrite) {
$this->io
->writeError(' Skipped installation of bin ' . $bin . ' for package ' . $package->getName() . ': name conflicts with an existing file');
}
continue;
}
if (realpath($link) === realpath($binPath)) {
// It is a linked binary from a previous installation, which can be replaced with a proxy file
$this->filesystem
->unlink($link);
}
}
$binCompat = $this->binCompat;
if ($binCompat === "auto" && (Platform::isWindows() || Platform::isWindowsSubsystemForLinux())) {
$binCompat = 'full';
}
if ($binCompat === "full") {
$this->installFullBinaries($binPath, $link, $bin, $package);
}
else {
$this->installUnixyProxyBinaries($binPath, $link);
}
Silencer::call('chmod', $binPath, 0777 & ~umask());
}
}
public function removeBinaries(PackageInterface $package) : void {
$this->initializeBinDir();
$binaries = $this->getBinaries($package);
if (!$binaries) {
return;
}
foreach ($binaries as $bin) {
$link = $this->binDir . '/' . basename($bin);
if (is_link($link) || file_exists($link)) {
// still checking for symlinks here for legacy support
$this->filesystem
->unlink($link);
}
if (is_file($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);
}
}
public static function determineBinaryCaller(string $bin) : string {
if ('.bat' === substr($bin, -4) || '.exe' === substr($bin, -4)) {
return 'call';
}
$handle = fopen($bin, 'r');
$line = fgets($handle);
fclose($handle);
if (Preg::isMatchStrictGroups('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', (string) $line, $match)) {
return trim($match[1]);
}
return 'php';
}
/**
* @return string[]
*/
protected function getBinaries(PackageInterface $package) : array {
return $package->getBinaries();
}
protected function installFullBinaries(string $binPath, string $link, string $bin, PackageInterface $package) : void {
// add unixy support for cygwin and similar environments
if ('.bat' !== substr($binPath, -4)) {
$this->installUnixyProxyBinaries($binPath, $link);
$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));
Silencer::call('chmod', $link, 0777 & ~umask());
}
}
protected function installUnixyProxyBinaries(string $binPath, string $link) : void {
file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link));
Silencer::call('chmod', $link, 0777 & ~umask());
}
protected function initializeBinDir() : void {
$this->filesystem
->ensureDirectoryExists($this->binDir);
$this->binDir = realpath($this->binDir);
}
protected function generateWindowsProxyCode(string $bin, string $link) : string {
$binPath = $this->filesystem
->findShortestPath($link, $bin);
$caller = self::determineBinaryCaller($bin);
// if the target is a php file, we run the unixy proxy file
// to ensure that _composer_autoload_path gets defined, instead
// of running the binary directly
if ($caller === 'php') {
return "@ECHO OFF\r\n" . "setlocal DISABLEDELAYEDEXPANSION\r\n" . "SET BIN_TARGET=%~dp0/" . trim(ProcessExecutor::escape(basename($link, '.bat')), '"\'') . "\r\n" . "SET COMPOSER_RUNTIME_BIN_DIR=%~dp0\r\n" . "{$caller} \"%BIN_TARGET%\" %*\r\n";
}
return "@ECHO OFF\r\n" . "setlocal DISABLEDELAYEDEXPANSION\r\n" . "SET BIN_TARGET=%~dp0/" . trim(ProcessExecutor::escape($binPath), '"\'') . "\r\n" . "SET COMPOSER_RUNTIME_BIN_DIR=%~dp0\r\n" . "{$caller} \"%BIN_TARGET%\" %*\r\n";
}
protected function generateUnixyProxyCode(string $bin, string $link) : string {
$binPath = $this->filesystem
->findShortestPath($link, $bin);
$binDir = ProcessExecutor::escape(dirname($binPath));
$binFile = basename($binPath);
$binContents = (string) file_get_contents($bin, false, null, 0, 500);
// For php files, we generate a PHP proxy instead of a shell one,
// which allows calling the proxy with a custom php process
if (Preg::isMatch('{^(#!.*\\r?\\n)?[\\r\\n\\t ]*<\\?php}', $binContents, $match)) {
// carry over the existing shebang if present, otherwise add our own
$proxyCode = $match[1] === null ? '#!/usr/bin/env php' : trim($match[1]);
$binPathExported = $this->filesystem
->findShortestPathCode($link, $bin, false, true);
$streamProxyCode = $streamHint = '';
$globalsCode = '$GLOBALS[\'_composer_bin_dir\'] = __DIR__;' . "\n";
$phpunitHack1 = $phpunitHack2 = '';
// Don't expose autoload path when vendor dir was not set in custom installers
if ($this->vendorDir !== null) {
// ensure comparisons work accurately if the CWD is a symlink, as $link is realpath'd already
$vendorDirReal = realpath($this->vendorDir);
if ($vendorDirReal === false) {
$vendorDirReal = $this->vendorDir;
}
$globalsCode .= '$GLOBALS[\'_composer_autoload_path\'] = ' . $this->filesystem
->findShortestPathCode($link, $vendorDirReal . '/autoload.php', false, true) . ";\n";
}
// Add workaround for PHPUnit process isolation
if ($this->filesystem
->normalizePath($bin) === $this->filesystem
->normalizePath($this->vendorDir . '/phpunit/phpunit/phpunit')) {
// workaround issue on PHPUnit 6.5+ running on PHP 8+
$globalsCode .= '$GLOBALS[\'__PHPUNIT_ISOLATION_EXCLUDE_LIST\'] = $GLOBALS[\'__PHPUNIT_ISOLATION_BLACKLIST\'] = array(realpath(' . $binPathExported . '));' . "\n";
// workaround issue on all PHPUnit versions running on PHP <8
$phpunitHack1 = "'phpvfscomposer://'.";
$phpunitHack2 = '
$data = str_replace(\'__DIR__\', var_export(dirname($this->realpath), true), $data);
$data = str_replace(\'__FILE__\', var_export($this->realpath, true), $data);';
}
if (trim($match[0]) !== '<?php') {
$streamHint = ' using a stream wrapper to prevent the shebang from being output on PHP<8' . "\n *";
$streamProxyCode = <<<STREAMPROXY
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private \$handle;
private \$position;
private \$realpath;
public function stream_open(\$path, \$mode, \$options, &\$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
\$opened_path = substr(\$path, 17);
\$this->realpath = realpath(\$opened_path) ?: \$opened_path;
\$opened_path = {<span class="php-variable">$phpunitHack1</span>}\$this->realpath;
\$this->handle = fopen(\$this->realpath, \$mode);
\$this->position = 0;
return (bool) \$this->handle;
}
public function stream_read(\$count)
{
\$data = fread(\$this->handle, \$count);
if (\$this->position === 0) {
\$data = preg_replace('{^#!.*\\r?\\n}', '', \$data);
}{<span class="php-variable">$phpunitHack2</span>}
\$this->position += strlen(\$data);
return \$data;
}
public function stream_cast(\$castAs)
{
return \$this->handle;
}
public function stream_close()
{
fclose(\$this->handle);
}
public function stream_lock(\$operation)
{
return \$operation ? flock(\$this->handle, \$operation) : true;
}
public function stream_seek(\$offset, \$whence)
{
if (0 === fseek(\$this->handle, \$offset, \$whence)) {
\$this->position = ftell(\$this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return \$this->position;
}
public function stream_eof()
{
return feof(\$this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option(\$option, \$arg1, \$arg2)
{
return true;
}
public function url_stat(\$path, \$flags)
{
\$path = substr(\$path, 17);
if (file_exists(\$path)) {
return stat(\$path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . {<span class="php-variable">$binPathExported</span>});
}
}
STREAMPROXY;
}
return $proxyCode . "\n" . <<<PROXY
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path ({<span class="php-variable">$binPath</span>})
*{<span class="php-variable">$streamHint</span>}
* @generated
*/
namespace Composer;
{<span class="php-variable">$globalsCode</span>}
{<span class="php-variable">$streamProxyCode</span>}
return include {<span class="php-variable">$binPathExported</span>};
PROXY;
}
return <<<PROXY
#!/usr/bin/env sh
# Support bash to support `source` with fallback on \$0 if this does not run with bash
# https://stackoverflow.com/a/35006505/6512
selfArg="\$BASH_SOURCE"
if [ -z "\$selfArg" ]; then
selfArg="\$0"
fi
self=\$(realpath \$selfArg 2> /dev/null)
if [ -z "\$self" ]; then
self="\$selfArg"
fi
dir=\$(cd "\${self%[/\\\\]*}" > /dev/null; cd {<span class="php-variable">$binDir</span>} && pwd)
if [ -d /proc/cygdrive ]; then
case \$(which php) in
\$(readlink -n /proc/cygdrive)/*)
# We are in Cygwin using Windows php, so the path must be translated
dir=\$(cygpath -m "\$dir");
;;
esac
fi
export COMPOSER_RUNTIME_BIN_DIR="\$(cd "\${self%[/\\\\]*}" > /dev/null; pwd)"
# If bash is sourcing this file, we have to source the target as well
bashSource="\$BASH_SOURCE"
if [ -n "\$bashSource" ]; then
if [ "\$bashSource" != "\$0" ]; then
source "\${dir}/{<span class="php-variable">$binFile</span>}" "\$@"
return
fi
fi
exec "\${dir}/{<span class="php-variable">$binFile</span>}" "\$@"
PROXY;
}
}
Members
Title Sort descending | Modifiers | Object type | Summary |
---|---|---|---|
BinaryInstaller::$binCompat | protected | property | @var string |
BinaryInstaller::$binDir | protected | property | @var string |
BinaryInstaller::$filesystem | protected | property | @var Filesystem |
BinaryInstaller::$io | protected | property | @var IOInterface |
BinaryInstaller::$vendorDir | private | property | @var string|null |
BinaryInstaller::determineBinaryCaller | public static | function | |
BinaryInstaller::generateUnixyProxyCode | protected | function | |
BinaryInstaller::generateWindowsProxyCode | protected | function | |
BinaryInstaller::getBinaries | protected | function | |
BinaryInstaller::initializeBinDir | protected | function | |
BinaryInstaller::installBinaries | public | function | |
BinaryInstaller::installFullBinaries | protected | function | |
BinaryInstaller::installUnixyProxyBinaries | protected | function | |
BinaryInstaller::removeBinaries | public | function | |
BinaryInstaller::__construct | public | function |