startSection('setting_up'); $this->stageDir = $stageDir; $this->projectRoot = $projectRoot; if (!$this->stageDir || $this->stageDir == '/') { throw new \InvalidArgumentException('Invalid base directory'); } if (is_dir($this->stageDir)) { $this->debug("Removing existing directory: $this->stageDir"); echo $this->exec("rm -rf $this->stageDir"); } $this->debug("Creating staging directory: $this->stageDir"); if (!mkdir($this->stageDir, 0777, true)) { throw new \RuntimeException("Could not create {$this->stageDir}"); } $this->stageDir = realpath($this->stageDir); $this->debug("Creating staging directory at: {$this->stageDir}"); if (!is_dir($this->projectRoot)) { throw new \InvalidArgumentException( "Project root not found: $this->projectRoot" ); } $this->endSection(); $this->startSection('staging'); chdir($this->projectRoot); } /** * Cleanup if the last section was not already closed. */ public function __destruct() { if ($this->sections) { $this->endSection(); } } /** * Call this method when starting a specific section of the packager. * * This makes the debug messages used in your script more meaningful and * adds context when things go wrong. Be sure to call endSection() when * you have finished a section of your packaging script. * * @param string $section Part of the packager that is running */ public function startSection($section) { $this->sections[] = $section; $this->debug('Starting'); } /** * Call this method when leaving the last pushed section of the packager. */ public function endSection() { if ($this->sections) { $this->debug('Completed'); array_pop($this->sections); } } /** * Prints a debug message to STDERR bound to the current section. * * @param string $message Message to echo to STDERR */ public function debug($message) { $prefix = date('c') . ': '; if ($this->sections) { $prefix .= '[' . end($this->sections) . '] '; } fwrite(STDERR, $prefix . $message . "\n"); } /** * Copies a file and creates the destination directory if needed. * * @param string $from File to copy * @param string $to Destination to copy the file to, relative to the * base staging directory. * @throws \InvalidArgumentException if the file cannot be found * @throws \RuntimeException if the directory cannot be created. * @throws \RuntimeException if the file cannot be copied. */ public function deepCopy($from, $to) { if (!is_file($from)) { throw new \InvalidArgumentException("File not found: {$from}"); } $to = str_replace('//', '/', $this->stageDir . '/' . $to); $dir = dirname($to); if (!is_dir($dir)) { if (!mkdir($dir, 0777, true)) { throw new \RuntimeException("Unable to create directory: $dir"); } } if (!copy($from, $to)) { throw new \RuntimeException("Unable to copy $from to $to"); } } /** * Recursively copy one folder to another. * * Any LICENSE file is automatically copied. * * @param string $sourceDir Source directory to copy from * @param string $destDir Directory to copy the files to that is relative * to the the stage base directory. * @param array $extensions File extensions to copy from the $sourceDir. * Defaults to "php" files only (e.g., ['php']). * @throws \InvalidArgumentException if the source directory is invalid. */ public function recursiveCopy( $sourceDir, $destDir, $extensions = array('php') ) { if (!realpath($sourceDir)) { throw new \InvalidArgumentException("$sourceDir not found"); } if (!$extensions) { throw new \InvalidArgumentException('$extensions is empty!'); } $sourceDir = realpath($sourceDir); $exts = array_fill_keys($extensions, true); $iter = new \RecursiveDirectoryIterator($sourceDir); $iter = new \RecursiveIteratorIterator($iter); $total = 0; $this->startSection('copy'); $this->debug("Starting to copy files from $sourceDir"); foreach ($iter as $file) { if (isset($exts[$file->getExtension()]) || $file->getBaseName() == 'LICENSE' ) { // Remove the source directory from the destination path $toPath = str_replace($sourceDir, '', (string) $file); $toPath = $destDir . '/' . $toPath; $toPath = str_replace('//', '/', $toPath); $this->deepCopy((string) $file, $toPath); $total++; } } $this->debug("Copied $total files from $sourceDir"); $this->endSection(); } /** * Execute a command and throw an exception if the return code is not 0. * * @param string $command Command to execute * * @return string Returns the output of the command as a string * @throws \RuntimeException on error. */ public function exec($command) { $this->debug("Executing: $command"); $output = $returnValue = null; exec($command, $output, $returnValue); if ($returnValue != 0) { throw new \RuntimeException('Error executing command: ' . $command . ' : ' . implode("\n", $output)); } return implode("\n", $output); } /** * Creates a class-map autoloader to the staging directory in a file * named autoloader.php * * @param array $files Files to explicitly require in the autoloader. This * is similar to Composer's "files" "autoload" section. * @param string $filename Name of the autoloader file. * @throws \RuntimeException if the file cannot be written */ public function createAutoloader($files = array(), $filename = 'autoloader.php') { $sourceDir = realpath($this->stageDir); $iter = new \RecursiveDirectoryIterator($sourceDir); $iter = new \RecursiveIteratorIterator($iter); $this->startSection('autoloader'); $this->debug('Creating classmap autoloader'); $this->debug("Collecting valid PHP files from {$this->stageDir}"); $classMap = array(); foreach ($iter as $file) { if ($file->getExtension() == 'php') { $location = str_replace($this->stageDir . '/', '', (string) $file); $className = str_replace('/', '\\', $location); $className = substr($className, 0, -4); // Remove "src\" or "lib\" if (strpos($className, 'src\\') === 0 || strpos($className, 'lib\\') === 0 ) { $className = substr($className, 4); } $classMap[$className] = "__DIR__ . '/$location'"; $this->debug("Found $className"); } } $destFile = $this->stageDir . '/' . $filename; $this->debug("Writing autoloader to {$destFile}"); if (!($h = fopen($destFile, 'w'))) { throw new \RuntimeException('Unable to open file for writing'); } $this->debug('Writing classmap files'); fwrite($h, " $f) { fwrite($h, " '$c' => $f,\n"); } fwrite($h, ");\n\n"); fwrite($h, <<debug('Writing automatically included files'); foreach ($files as $file) { fwrite($h, "require __DIR__ . '/$file';\n"); } fclose($h); $this->endSection(); } /** * Creates a default stub for the phar that includeds the generated * autoloader. * * This phar also registers a constant that can be used to check if you * are running the phar. The constant is the basename of the $dest variable * without the extension, with "_PHAR" appended, then converted to all * caps (e.g., "/foo/guzzle.phar" gets a contant defined as GUZZLE_PHAR. * * @param $dest * @param string $autoloaderFilename Name of the autoloader file. * * @return string */ private function createStub($dest, $autoloaderFilename = 'autoloader.php') { $this->startSection('stub'); $this->debug("Creating phar stub at $dest"); $alias = basename($dest); $constName = str_replace('.phar', '', strtoupper($alias)) . '_PHAR'; $stub = "endSection(); return $stub; } /** * Creates a phar that automatically registers an autoloader. * * Call this only after your staging directory is built. * * @param string $dest Where to save the file. The basename of the file * is also used as the alias name in the phar * (e.g., /path/to/guzzle.phar => guzzle.phar). * @param string|bool|null $stub The path to the phar stub file. Pass or * leave null to automatically have one created for you. Pass false * to no use a stub in the generated phar. * @param string $autoloaderFilename Name of the autolaoder filename. */ public function createPhar( $dest, $stub = null, $autoloaderFilename = 'autoloader.php' ) { $this->startSection('phar'); $this->debug("Creating phar file at $dest"); $this->createDirIfNeeded(dirname($dest)); $phar = new \Phar($dest, 0, basename($dest)); $phar->buildFromDirectory($this->stageDir); if ($stub !== false) { if (!$stub) { $stub = $this->createStub($dest, $autoloaderFilename); } $phar->setStub($stub); } $this->debug("Created phar at $dest"); $this->endSection(); } /** * Creates a zip file containing the staged files of your project. * * Call this only after your staging directory is built. * * @param string $dest Where to save the zip file */ public function createZip($dest) { $this->startSection('zip'); $this->debug("Creating a zip file at $dest"); $this->createDirIfNeeded(dirname($dest)); chdir($this->stageDir); $this->exec("zip -r $dest ./"); $this->debug(" > Created at $dest"); chdir(__DIR__); $this->endSection(); } private function createDirIfNeeded($dir) { if (!is_dir($dir) && !mkdir($dir, 0755, true)) { throw new \RuntimeException("Could not create dir: $dir"); } } }