false, ]; public function __construct($path=null, array $opts=[]) { if (!$path) { $path = getcwd(); } if (!file_exists($path."/composer.json")) { throw new \RuntimeException("Could not find a composer.json in {$path}"); } $this->opts = array_merge( self::$default_options, $opts ); $this->path = $path; } private function readComposer() { print_info("Parsing composer.json..."); $json = file_get_contents($this->path."/composer.json"); $composer = json_decode($json); if (isset($composer->require)) { foreach ($composer->require as $package=>$constraint) { if ($package == "php") { $this->dependencies[$package] = $constraint; print_info(" ! require php: %s", $constraint); } elseif (strpos($package, "ext-") === 0) { $this->dependencies[$package] = $constraint; print_info(" ! require ext: %s %s", $package, $constraint); } } } if (!isset($composer->bin) || !is_array($composer->bin)) { print_warn("No executable defined, building a library phar..."); } elseif (count($composer->bin)>1) { print_notice("More than one executable defined, using the first one as default"); print_info(" → Using default executable %s", $composer->bin[0]); for ($n = 1; $n < count($composer->bin); $n++) { print_info(" → Adding alternate executable %s", $composer->bin[$n]); } $this->bins = $composer->bin; } else { print_info(" → Using executable %s", $composer->bin[0]); $this->bins = $composer->bin; } if ($this->index) { $this->pharType = self::PHAR_TYPE_WEB; $this->output = basename($this->index,".php").".phar"; } else { if (isset($composer->bin) && is_array($composer->bin)) { $this->pharType = self::PHAR_TYPE_APP; } $this->output = "output.phar"; } if (isset($composer->extra)) { if (isset($composer->extra->phar)) { print_info(" → Found extra phar configuration"); $this->pharOpts = (array)$composer->extra->phar; } if (array_key_exists('output', $this->pharOpts)) { $this->output = $this->pharOpts['output']; } if (array_key_exists('stub', $this->pharOpts)) { // set a custom stub } } print_info("Generating output %s", $this->output); $this->composer = $composer; } private function installComposer() { print_info("Installing dependencies..."); passthru("composer install --no-interaction --optimize-autoloader"); } private function buildManifest() { print_info("Creating manifest..."); $excluded = (array)( array_key_exists('exclude',$this->pharOpts) ?$this->pharOpts['exclude'] :[] ); $manifest = new Manifest($this->path); $manifest->setExcluded($excluded); // Always include the vendor directory print_info(" ← vendor/"); $manifest->addDirectory("vendor"); // Include everything from the autoload if (isset($this->composer->autoload)) { foreach ((array)$this->composer->autoload as $type=>$autoloaders) { if ($type == "files") { foreach ($autoloaders as $file) { print_info(" ← %s", $file); $manifest->addFile($file); } continue; } foreach ($autoloaders as $prefix=>$path) { print_info(" ← %s", $path); $manifest->addDirectory($path); } } } if (array_key_exists('include', $this->pharOpts)) { foreach ((array)$this->pharOpts['include'] as $path) { print_info(" ← %s", $path); if (substr($path,-1,1)==DIRECTORY_SEPARATOR) { $manifest->addDirectory($path); } else { $manifest->addFile($path); } } } $this->manifest = $manifest; } private function generateBootstrap(\Phar $phar, $type, $index=null, ?array $bins=null) { print_info("Generating bootstrap stub..."); $bins = (array)$bins; $indexFile = "dependencies as $dep=>$constraint) { if ($dep == "php") { } elseif (strpos($dep, "ext-") === 0) { $ext = substr($dep, 4); $depcheck.= 'if (!extension_loaded("' . $ext . '")) { @fwrite(STDERR, "Fatal: Missing required extension ' . $ext . '.\n"); exit(2); }' . PHP_EOL; } } if (!$index && count($bins)==0) { $indexFile.= 'require_once __DIR__."/vendor/autoload.php";'; } elseif (count($bins)>1) { $indexFile.= '$bin=basename($argv[0],".phar"); switch($bin) {'; for ($n = 1; $nstripShebang($str); $phar->addFromString($bin, $str); } } else { if (count($bins)) { $index = array_shift($bins); } $indexFile = file_get_contents($index); $indexFile = $this->stripShebang($indexFile); $index = "bootstrap.php"; } $indexFile = str_replace("createDefaultStub($index); $phar->setStub($stub); if ($index) { $phar->addFromString($index, $indexFile); } } private function stripShebang($str) { if (strpos($str, '#!') === 0) { if (strpos($str, '#!/usr/bin/env php') === 0) { $str = substr($str,strpos($str, " "PharLite/".PHARLITE_VERSION." (PHP/".PHP_VERSION.")", 'phar.type' => $this->pharType, 'package.name' => isset($this->composer->name)?$this->composer->name:null, 'package.version' => isset($this->composer->version)?$this->composer->version:null, ], $metadata); print_info("Writing metadata:\n%s", json_encode($metadata, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT)); $phar->setMetadata($metadata); } public function build() { $this->readComposer(); if ($this->opts['install']) { $this->installComposer(); } $this->buildManifest(); print_info("Adding files..."); $tempName = uniqid("phar").".phar"; $phar = new \Phar($tempName); $total = count($this->manifest); $pcf = 100 / $total; $bw = 40; $tw = intval(exec("tput cols")); $pbf = $tw / $total; $index = 0; $spinner = [ "/", "-", "\\", "|" ]; $s = 0; $t = microtime(true); $totalBytes = 0; foreach ($this->manifest as $object) { // printf(" %s: %s\n", $object->getFilename(), $object->getLocalName()); $object->addToPhar($phar); $index++; $totalBytes += $object->getFilesize(); if (posix_isatty(STDOUT) && (microtime(true)>$t+.2)) { $t = microtime(true); $pc = $pcf * $index; $pb = round($pbf * $index); $bar = str_pad(sprintf(" %4.1f%% %d files, %.1fKiB %s ", $pc, $index, $totalBytes/1024, dirname($object->getFilename())), $tw); echo "\r\e[30;42m".substr($bar, 0, $pb)."\e[0m".substr($bar, $pb); //printf("\r%4.1f%% [%s%s%s] %d files, %.1fKiB", $pc, str_repeat("=",$pb), $spinner[$s=(($s+1)%4)], str_repeat(" ",$bw-$pb), $index, $totalBytes/1024); } } if (posix_isatty(STDOUT)) { printf("\r\e[2K"); } print_info("Added %d files!", $index); $this->generateBootstrap($phar, $this->pharType, $this->index, $this->bins); $this->writeMetadata($phar, (array)(array_key_exists('metadata',$this->pharOpts)?$this->pharOpts['metadata']:[])); if (file_exists($this->output)) { unlink($this->output); } rename($tempName, $this->output); chmod($this->output, 0777); } public function initConfig() { print_info("Configuring pharlite"); $askString = function($prompt, $default=null) { return readline($prompt." [".$default."]? ")?:null; }; $composer = json_decode(file_get_contents($this->path."/composer.json")); if (!isset($composer->extra)) { $composer->extra = (object)[]; } if (!isset($composer->extra->phar)) { $composer->extra->phar = (object)[]; } if ($output = $askString('Output filename', @$composer->extra->phar->output)) { $composer->extra->phar->output = $output; } if ($index = $askString('Index file (for web phar)', @$composer->extra->phar->index)) { $composer->extra->phar->index = $index; } if ($stub = $askString('Loader stub', @$composer->extra->phar->stub)) { $composer->extra->phar->stub = $stub; } if (empty($composer->bin)) { print_warn("You have no bins defined in your composer.json. Specify at least one bin to create an application phar."); } if (file_exists($this->path."/composer.bak")) { unlink($this->path."/composer.bak"); } rename($this->path."/composer.json", $this->path."/composer.bak"); file_put_contents($this->path."/composer.json", json_encode($composer, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)."\n"); exec("composer validate --no-check-lock -q", $output, $retval); if ($retval == 0) { print_info("Updated composer.json"); unlink($this->path."/composer.bak"); return; } print_error("The composer.json file isn't valid after being modified. Reverting..."); unlink($this->path."/composer.json"); rename($this->path."/composer.bak", $this->path."/composer.json"); } }