From: Michael Wallner Date: Wed, 4 Mar 2015 14:32:06 +0000 (+0100) Subject: init X-Git-Tag: v1.0.0~5 X-Git-Url: https://git.m6w6.name/?a=commitdiff_plain;h=0ff7b26bce8f0dfbd1d4d45313705a94f2ac5e28;p=pharext%2Fpharext init --- 0ff7b26bce8f0dfbd1d4d45313705a94f2ac5e28 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c1b53ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.buildpath +.project +.settings/ +*~ +*.tmp diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3f7d0f6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Michael Wallner . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3f00b50 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +# +# build bin/pharext +# + +all: bin/pharext + +bin/pharext: src/* src/pharext/* + php -d phar.readonly=0 build/create-phar.php + chmod +x $@ + +clean: + rm bin/pharext* + +.PHONY: all clean + diff --git a/README.md b/README.md new file mode 100644 index 0000000..9b098a6 --- /dev/null +++ b/README.md @@ -0,0 +1,123 @@ +# pharext + +Distribute your PHP extension as self-installable phar executable + +## About + +### Disclaimer + +You don't need this package to install any `*.ext.phar` extension packages, +just run them with php: + + $ ./pecl_http-2.4.0dev.ext.phar + +Or, if the execute permission bit got lost somehow: + + $ php pecl_http-2.4.0dev.ext.phar + +Command help: + + $ ./pecl_http-2.4.0dev.ext.phar -h + +Yields: + + Usage: + + $ ./pecl_http-2.4.0dev.ext.phar [-h|-v|-q|-s] [-p|-n|-c ] + + -h|--help Display help + -v|--verbose More output + -q|--quiet Less output + -p|--prefix PHP installation directory [/usr] + -n|--common-name PHP common program name, e.g. php5 [php] + -c|--configure Additional extension configure flags + -s|--sudo [] Installation might need increased privileges [sudo -S %s] + +If your installation destination needs escalated permissions, have a look at the `--sudo` option: + + $ ./pecl_http-2.4.0dev.ext.phar --sudo + Running phpize ... OK + Running configure ... OK + Running make ... OK + Running install ... Password:············ + Installing shared extensions: /usr/lib/php/extensions/no-debug-non-zts-20121212/ + Installing header files: /usr/include/php/ + OK + +### Prerequisites + +The usual tools you need to build a PHP extension: +* php, phpize and php-config +* make, cc and autotools +A network connection is not needed. + +### Not implemented + +* Dependencies +* Package description files + +## Installation for extension maintainers + + $ composer require m6w6/pharext + +### Prerequisites: + +* make +* php + phar + +## Usage + + $ ./bin/pharext --pecl --source ../pecl_http.git + +Yields: + + Creating phar ./pecl_http-2.4.0dev.ext.phar.54f6e987ae00f.tmp ... OK + Finalizing ./pecl_http-2.4.0dev.ext.phar ... OK + +Note that the PECL source can infer package name and release version from the package.xml. + +Another example using `git ls-files`: + + $ ./bin/pharext -v -g -s ../raphf.git --name raphf --release 1.0.5 + +Yields: + + Creating phar ./raphf-1.0.5.ext.phar.54f6ebd71f13b.tmp ... + Packaging .gitignore + Packaging CREDITS + Packaging Doxyfile + Packaging LICENSE + Packaging TODO + Packaging config.m4 + Packaging config.w32 + Packaging package.xml + Packaging php_raphf.c + Packaging php_raphf.h + Packaging raphf.png + Packaging tests/http001.phpt + Packaging tests/http002.phpt + Packaging tests/http003.phpt + Packaging tests/http004.phpt + OK + Finalizing ./raphf-1.0.5.ext.phar ... OK + +Command help: + + $ ./bin/pharext --help + +Yields: + + Usage: + + $ ./bin/pharext [-h|-v|-q|-g|-p] -s -n -r [-d ] + + -h|--help Display this help + -v|--verbose More output + -q|--quiet Less output + -s|--source Extension source directory (REQUIRED) + -g|--git Use `git ls-files` instead of the standard ignore filter + -p|--pecl Use PECL package.xml instead of the standard ignore filter + -d|--dest Destination directory [.] + -n|--name Extension name (REQUIRED) + -r|--release Extension release version (REQUIRED) + diff --git a/bin/pharext b/bin/pharext new file mode 100755 index 0000000..61c9b84 Binary files /dev/null and b/bin/pharext differ diff --git a/build/create-phar.php b/build/create-phar.php new file mode 100644 index 0000000..c03ba8e --- /dev/null +++ b/build/create-phar.php @@ -0,0 +1,27 @@ +buildFromDirectory(dirname(__DIR__)."/src", "/^.*\.php$/"); +$package->setDefaultStub("pharext_packager.php"); +$package->setStub("#!/usr/bin/php -dphar.readonly=0\n".$package->getStub()); +unset($package); + +if (!rename($tmpname, $pkgname)) { + fprintf(STDERR, "%s\n", error_get_last()["message"]); + exit(4); +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..8f8ab64 --- /dev/null +++ b/composer.json @@ -0,0 +1,11 @@ +{ + "name": "m6w6/pharext", + "description": "Package PHP extensions as self-installing PHARs", + "keywords": ["ext", "extension", "phar", "package", "install"], + "type": "project", + "license": "BSD-2-Clause", + "bin": ["bin/pharext"], + "scripts": { + "pre-install-cmd": "make -s" + } +} diff --git a/src/pharext/CliArgs.php b/src/pharext/CliArgs.php new file mode 100644 index 0000000..f93ad7b --- /dev/null +++ b/src/pharext/CliArgs.php @@ -0,0 +1,326 @@ +compile($spec); + } + + /** + * Compile the original spec + * @param array $spec + * @return pharext\CliArgs self + */ + public function compile(array $spec = null) { + $this->orig = $spec; + $this->spec = []; + foreach ((array) $spec as $arg) { + $this->spec["-".$arg[0]] = $arg; + $this->spec["--".$arg[1]] = $arg; + } + return $this; + } + + /** + * Parse command line arguments according to the compiled spec. + * + * The Generator yields any parsing errors. + * Parsing will stop when all arguments are processed or the first option + * flagged CliArgs::HALT was encountered. + * + * @param int $argc + * @param array $argv + * @return Generator + */ + public function parse($argc, array $argv) { + for ($i = 0; $i < $argc; ++$i) { + $o = $argv[$i]; + + if (!isset($this->spec[$o])) { + yield sprintf("Unknown option %s", $argv[$i]); + } elseif (!$this->optAcceptsArg($o)) { + $this[$o] = true; + } elseif ($i+1 < $argc && !isset($this->spec[$argv[$i+1]])) { + $this[$o] = $argv[++$i]; + } elseif ($this->optNeedsArg($o)) { + yield sprintf("Option --%s needs an argument", $this->optLongName($o)); + } else { + // OPTARG + $this[$o] = $this->optDefaultArg($o); + } + + if ($this->optHalts($o)) { + return; + } + } + } + + /** + * Validate that all required options were given. + * + * The Generator yields any validation errors. + * + * @return Generator + */ + public function validate() { + $required = array_filter($this->orig, function($spec) { + return $spec[3] & self::REQUIRED; + }); + foreach ($required as $req) { + if (!isset($this[$req[0]])) { + yield sprintf("Option --%s is required", $req[1]); + } + } + } + + /** + * Output command line help message + * @param string $prog + */ + public function help($prog) { + printf("\nUsage:\n\n $ %s", $prog); + $flags = []; + $required = []; + $optional = []; + foreach ($this->orig as $spec) { + if ($spec[3] & self::REQARG) { + if ($spec[3] & self::REQUIRED) { + $required[] = $spec; + } else { + $optional[] = $spec; + } + } else { + $flags[] = $spec; + } + } + + if ($flags) { + printf(" [-%s]", implode("|-", array_column($flags, 0))); + } + foreach ($required as $req) { + printf(" -%s ", $req[0]); + } + if ($optional) { + printf(" [-%s ]", implode("|-", array_column($optional, 0))); + } + printf("\n\n"); + foreach ($this->orig as $spec) { + printf(" -%s|--%s %s", $spec[0], $spec[1], ($spec[3] & self::REQARG) ? " " : (($spec[3] & self::OPTARG) ? "[]" : " ")); + printf("%s%s %s", str_repeat(" ", 16-strlen($spec[1])), $spec[2], ($spec[3] & self::REQUIRED) ? "(REQUIRED)" : ""); + if (isset($spec[4])) { + printf(" [%s]", $spec[4]); + } + printf("\n"); + } + printf("\n"); + } + + /** + * Retreive the default argument of an option + * @param string $o + * @return mixed + */ + private function optDefaultArg($o) { + $o = $this->opt($o); + if (isset($this->spec[$o][4])) { + return $this->spec[$o][4]; + } + return null; + } + + /** + * Retrieve the help message of an option + * @param string $o + * @return string + */ + private function optHelp($o) { + $o = $this->opt($o); + if (isset($this->spec[$o][2])) { + return $this->spec[$o][2]; + } + return ""; + } + + /** + * Check whether an option is flagged for halting argument processing + * @param string $o + * @return boolean + */ + private function optHalts($o) { + $o = $this->opt($o); + return $this->spec[$o][3] & self::HALT; + } + + /** + * Check whether an option needs an argument + * @param string $o + * @return boolean + */ + private function optNeedsArg($o) { + $o = $this->opt($o); + return $this->spec[$o][3] & self::REQARG; + } + + /** + * Check wether an option accepts any argument + * @param string $o + * @return boolean + */ + private function optAcceptsArg($o) { + $o = $this->opt($o); + return $this->spec[$o][3] & 0xf00; + } + + /** + * Check whether an option can be used more than once + * @param string $o + * @return boolean + */ + private function optIsMulti($o) { + $o = $this->opt($o); + return $this->spec[$o][3] & self::MULTI; + } + + /** + * Retreive the long name of an option + * @param string $o + * @return string + */ + private function optLongName($o) { + $o = $this->opt($o); + return $this->spec[$o][1]; + } + + /** + * Retreive the short name of an option + * @param string $o + * @return string + */ + private function optShortName($o) { + $o = $this->opt($o); + return $this->spec[$o][0]; + } + + /** + * Retreive the canonical name (--long-name) of an option + * @param string $o + * @return string + */ + private function opt($o) { + if ($o{0} !== '-') { + if (strlen($o) > 1) { + $o = "-$o"; + } + $o = "-$o"; + } + return $o; + } + + /**@+ + * Implements ArrayAccess and virtual properties + */ + function offsetExists($o) { + $o = $this->opt($o); + return isset($this->args[$o]); + } + function __isset($o) { + return $this->offsetExists($o); + } + function offsetGet($o) { + $o = $this->opt($o); + if (isset($this->args[$o])) { + return $this->args[$o]; + } + return $this->optDefaultArg($o); + } + function __get($o) { + return $this->offsetGet($o); + } + function offsetSet($o, $v) { + if ($this->optIsMulti($o)) { + $this->args["-".$this->optShortName($o)][] = $v; + $this->args["--".$this->optLongName($o)][] = $v; + } else { + $this->args["-".$this->optShortName($o)] = $v; + $this->args["--".$this->optLongName($o)] = $v; + } + } + function __set($o, $v) { + $this->offsetSet($o, $v); + } + function offsetUnset($o) { + unset($this->args["-".$this->optShortName($o)]); + unset($this->args["--".$this->optLongName($o)]); + } + function __unset($o) { + $this->offsetUnset($o); + } + /**@-*/ +} diff --git a/src/pharext/Command.php b/src/pharext/Command.php new file mode 100644 index 0000000..f7b0c74 --- /dev/null +++ b/src/pharext/Command.php @@ -0,0 +1,36 @@ +cmd = $cmd; + $this->path = $path; + parent::__construct( + new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path, + \FilesystemIterator::KEY_AS_PATHNAME | + \FilesystemIterator::CURRENT_AS_FILEINFO | + \FilesystemIterator::SKIP_DOTS + ) + ) + ); + foreach ([".gitignore", ".hgignore"] as $ignore) { + if (file_exists("$path/$ignore")) { + $this->filter = array_merge($this->filter, + array_map(function($pat) { + $pat = trim($pat); + if (substr($pat, -1) == '/') { + $pat .= '*'; + } + return $pat; + }, file("$path/$ignore", + FILE_IGNORE_NEW_LINES | + FILE_SKIP_EMPTY_LINES + )) + ); + } + } + } + + /** + * @inheritdoc + * @see \pharext\SourceDir::getBaseDir() + */ + public function getBaseDir() { + return $this->path; + } + + /** + * Implements FilterIterator + * @see FilterIterator::accept() + */ + public function accept() { + $fn = $this->key(); + if (is_dir($fn)) { + if ($this->cmd->getArgs()->verbose) { + $this->info("Excluding %s\n", $fn); + } + return false; + } + $pl = strlen($this->path) + 1; + $pn = substr($this->key(), $pl); + foreach ($this->filter as $pat) { + if (fnmatch($pat, $pn)) { + if ($this->cmd->getArgs()->verbose) { + $this->info("Excluding %s\n", $pn); + } + return false; + } + } + if ($this->cmd->getArgs()->verbose) { + $this->info("Packaging %s\n", $pn); + } + return true; + } +} \ No newline at end of file diff --git a/src/pharext/GitSourceDir.php b/src/pharext/GitSourceDir.php new file mode 100644 index 0000000..f18f97f --- /dev/null +++ b/src/pharext/GitSourceDir.php @@ -0,0 +1,70 @@ +cmd = $cmd; + $this->path = $path; + } + + /** + * @inheritdoc + * @see \pharext\SourceDir::getBaseDir() + */ + public function getBaseDir() { + return $this->path; + } + + /** + * Generate a list of files by `git ls-files` + * @return Generator + */ + private function generateFiles() { + $pwd = getcwd(); + chdir($this->path); + if (($pipe = popen("git ls-files", "r"))) { + while (!feof($pipe)) { + if (strlen($file = trim(fgets($pipe)))) { + if ($this->cmd->getArgs()->verbose) { + $this->cmd->info("Packaging %s\n", $file); + } + if (!($realpath = realpath($file))) { + $this->cmd->error("File %s does not exist\n", $file); + } + yield $realpath; + } + } + pclose($pipe); + } + chdir($pwd); + } + + /** + * Implements IteratorAggregate + * @see IteratorAggregate::getIterator() + */ + public function getIterator() { + return $this->generateFiles(); + } +} diff --git a/src/pharext/Installer.php b/src/pharext/Installer.php new file mode 100644 index 0000000..d8c7feb --- /dev/null +++ b/src/pharext/Installer.php @@ -0,0 +1,154 @@ +args = new CliArgs([ + ["h", "help", "Display help", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], + ["v", "verbose", "More output", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], + ["q", "quiet", "Less output", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], + ["p", "prefix", "PHP installation directory", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG, + "/usr"], + ["n", "common-name", "PHP common program name, e.g. php5", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG, + "php"], + ["c", "configure", "Additional extension configure flags", + CliArgs::OPTIONAL|CliArgs::MULTI|CliArgs::REQARG], + ["s", "sudo", "Installation might need increased privileges", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::OPTARG, + "sudo -S %s"] + ]); + } + + /** + * @inheritdoc + * @see \pharext\Command::run() + */ + public function run($argc, array $argv) { + $prog = array_shift($argv); + foreach ($this->args->parse(--$argc, $argv) as $error) { + $this->error("%s\n", $error); + } + + if ($this->args["help"]) { + $this->args->help($prog); + exit; + } + + foreach ($this->args->validate() as $error) { + $this->error("%s\n", $error); + } + + if (isset($error)) { + if (!$this->args["quiet"]) { + $this->args->help($prog); + } + exit(1); + } + + $this->installPackage(); + } + + /** + * @inheritdoc + * @see \pharext\Command::getArgs() + */ + public function getArgs() { + return $this->args; + } + + /** + * @inheritdoc + * @see \pharext\Command::info() + */ + public function info($fmt) { + if (!$this->args->quiet) { + vprintf($fmt, array_slice(func_get_args(), 1)); + } + } + + /** + * @inheritdoc + * @see \pharext\Command::error() + */ + public function error($fmt) { + if (!$this->args->quiet) { + vfprintf(STDERR, "ERROR: $fmt", array_slice(func_get_args(), 1)); + } + } + + /** + * Extract the phar to a temporary directory + */ + private function extract() { + if (!$file = Phar::running(false)) { + $this->error("Did your run the ext.phar?\n"); + exit(3); + } + $temp = sys_get_temp_dir()."/".basename($file, ".ext.phar"); + is_dir($temp) or mkdir($temp, 0750, true); + $phar = new Phar($file); + $phar->extractTo($temp, null, true); + chdir($temp); + } + + /** + * Execute a system command + * @param string $name pretty name + * @param string $command full command + * @param bool $sudo whether the command may need escalated privileges + */ + private function exec($name, $command, $sudo = false) { + $this->info("Running %s ...%s", $this->args->verbose ? $command : $name, $this->args->verbose ? "\n" : " "); + if ($sudo && isset($this->args->sudo)) { + if ($proc = proc_open(sprintf($this->args->sudo, $command)." 2>&1", [STDIN,STDOUT,STDERR], $pipes)) { + $retval = proc_close($proc); + } else { + $retval = -1; + } + } elseif ($this->args->verbose) { + passthru($command ." 2>&1", $retval); + } else { + exec($command ." 2>&1", $output, $retval); + } + if ($retval) { + $this->error("Command %s failed with (%s)\n", $command, $retval); + if (isset($output) && !$this->args->quiet) { + printf("%s\n", implode("\n", $output)); + } + exit(2); + } + $this->info("OK\n"); + } + + /** + * Prepares, configures, builds and installs the extension + */ + private function installPackage() { + $this->extract(); + $this->exec("phpize", "{$this->args->prefix}/bin/{$this->args->{'common-name'}}ize"); + $this->exec("configure", "./configure --with-php-config={$this->args->prefix}/bin/{$this->args->{'common-name'}}-config ". implode(" ", (array) $this->args->configure)); + $this->exec("make", "make -sj3"); + $this->exec("install", "make -s install", true); + } +} diff --git a/src/pharext/Packager.php b/src/pharext/Packager.php new file mode 100644 index 0000000..3408c75 --- /dev/null +++ b/src/pharext/Packager.php @@ -0,0 +1,187 @@ +args = new CliArgs([ + ["h", "help", "Display this help", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], + ["v", "verbose", "More output", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], + ["q", "quiet", "Less output", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], + ["s", "source", "Extension source directory", + CliArgs::REQUIRED|CliArgs::SINGLE|CliArgs::REQARG], + ["g", "git", "Use `git ls-files` instead of the standard ignore filter", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], + ["p", "pecl", "Use PECL package.xml instead of the standard ignore filter", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], + ["d", "dest", "Destination directory", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG, + "."], + ["n", "name", "Extension name", + CliArgs::REQUIRED|CliArgs::SINGLE|CliArgs::REQARG], + ["r", "release", "Extension release version", + CliArgs::REQUIRED|CliArgs::SINGLE|CliArgs::REQARG], + ["z", "gzip", "Create additional PHAR compressed with gzip", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], + ["Z", "bzip", "Create additional PHAR compressed with bzip", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], +]); + } + + /** + * @inheritdoc + * @see \pharext\Command::run() + */ + public function run($argc, array $argv) { + $prog = array_shift($argv); + foreach ($this->args->parse(--$argc, $argv) as $error) { + $this->error("%s\n", $error); + } + + if ($this->args["help"]) { + $this->args->help($prog); + exit; + } + + if ($this->args["source"]) { + if ($this->args["pecl"]) { + $this->source = new PeclSourceDir($this, $this->args["source"]); + } elseif ($this->args["git"]) { + $this->source = new GitSourceDir($this, $this->args["source"]); + } else { + $this->source = new FilteredSourceDir($this, $this->args["source"]); + } + } + + foreach ($this->args->validate() as $error) { + $this->error("%s\n", $error); + } + + if (isset($error)) { + if (!$this->args["quiet"]) { + $this->args->help($prog); + } + exit(1); + } + + $this->createPackage(); + } + + /** + * @inheritdoc + * @see \pharext\Command::getArgs() + */ + public function getArgs() { + return $this->args; + } + + /** + * @inheritdoc + * @see \pharext\Command::info() + */ + public function info($fmt) { + if (!$this->args->quiet) { + vprintf($fmt, array_slice(func_get_args(), 1)); + } + } + + /** + * @inheritdoc + * @see \pharext\Command::error() + */ + public function error($fmt) { + if (!$this->args->quiet) { + vfprintf(STDERR, "ERROR: $fmt", array_slice(func_get_args(), 1)); + } + } + + /** + * Traverses all pharext source files to bundle + * @return Generator + */ + private function bundle() { + foreach (scandir(__DIR__) as $entry) { + if (fnmatch("*.php", $entry)) { + yield "pharext/$entry" => __DIR__."/$entry"; + } + } + } + + /** + * Creates the extension phar + */ + private function createPackage() { + $pkguniq = uniqid(); + $pkgtemp = sys_get_temp_dir() ."/{$pkguniq}.phar"; + $pkgdesc = "{$this->args->name}-{$this->args->release}"; + + $this->info("Creating phar %s ...%s", $pkgtemp, $this->args->verbose ? "\n" : " "); + try { + $package = new Phar($pkgtemp, 0, "ext.phar"); + $package->startBuffering(); + $package->buildFromIterator($this->source, $this->source->getBaseDir()); + $package->buildFromIterator($this->bundle()); + $package->addFile(__DIR__."/../pharext_installer.php", "pharext_installer.php"); + $package->setDefaultStub("pharext_installer.php"); + $package->setStub("#!/usr/bin/php -dphar.readonly=1\n".$package->getStub()); + $package->stopBuffering(); + + chmod($pkgtemp, 0770); + if ($this->args->verbose) { + $this->info("Created executable phar %s\n", $pkgtemp); + } else { + $this->info("OK\n"); + } + if ($this->args->gzip) { + $this->info("Compressing with gzip ... "); + $package->compress(Phar::GZ); + $this->info("OK\n"); + } + if ($this->args->bzip) { + $this->info("Compressing with bzip ... "); + $package->compress(Phar::BZ2); + $this->info("OK\n"); + } + + unset($package); + } catch (\Exception $e) { + $this->error("%s\n", $e->getMessage()); + exit(4); + } + + foreach (glob($pkgtemp."*") as $pkgtemp) { + $pkgfile = str_replace($pkguniq, "{$pkgdesc}-ext", $pkgtemp); + $pkgname = $this->args->dest ."/". basename($pkgfile); + $this->info("Finalizing %s ... ", $pkgname); + if (!rename($pkgtemp, $pkgname)) { + $this->error("%s\n", error_get_last()["message"]); + exit(5); + } + $this->info("OK\n"); + } + } +} diff --git a/src/pharext/PeclSourceDir.php b/src/pharext/PeclSourceDir.php new file mode 100644 index 0000000..3844519 --- /dev/null +++ b/src/pharext/PeclSourceDir.php @@ -0,0 +1,101 @@ +registerXPathNamespace("pecl", "http://pear.php.net/dtd/package-2.0"); + + $args = $cmd->getArgs(); + if (!isset($args->name)) { + $name = (string) $sxe->xpath("/pecl:package/pecl:name")[0]; + foreach ($args->parse(2, ["--name", $name]) as $error) { + $cmd->error("%s\n", $error); + } + } + + if (!isset($args->release)) { + $release = (string) $sxe->xpath("/pecl:package/pecl:version/pecl:release")[0]; + foreach ($args->parse(2, ["--release", $release]) as $error) { + $cmd->error("%s\n", $error); + } + } + + $this->cmd = $cmd; + $this->sxe = $sxe; + $this->path = $path; + } + + /** + * @inheritdoc + * @see \pharext\SourceDir::getBaseDir() + */ + public function getBaseDir() { + return $this->path; + } + + /** + * Compute the path of a file by parent dir nodes + * @param \SimpleXMLElement $ele + * @return string + */ + private function dirOf($ele) { + $path = ""; + while (($ele = current($ele->xpath(".."))) && $ele->getName() == "dir") { + $path = trim($ele["name"], "/") ."/". $path ; + } + return trim($path, "/"); + } + + /** + * Generate a list of files from the package.xml + * @return Generator + */ + private function generateFiles() { + foreach ($this->sxe->xpath("//pecl:file") as $file) { + $path = $this->path ."/". $this->dirOf($file) ."/". $file["name"]; + if ($this->cmd->getArgs()->verbose) { + $this->cmd->info("Packaging %s\n", $path); + } + if (!($realpath = realpath($path))) { + $this->cmd->error("File %s does not exist", $path); + } + yield $realpath; + } + } + + /** + * Implements IteratorAggregate + * @see IteratorAggregate::getIterator() + */ + public function getIterator() { + return $this->generateFiles(); + } +} diff --git a/src/pharext/SourceDir.php b/src/pharext/SourceDir.php new file mode 100644 index 0000000..fbdd2f6 --- /dev/null +++ b/src/pharext/SourceDir.php @@ -0,0 +1,26 @@ +run($argc, $argv); diff --git a/src/pharext_packager.php b/src/pharext_packager.php new file mode 100644 index 0000000..d0636ee --- /dev/null +++ b/src/pharext_packager.php @@ -0,0 +1,16 @@ +run($argc, $argv); + +__HALT_COMPILER();