From 0ff7b26bce8f0dfbd1d4d45313705a94f2ac5e28 Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Wed, 4 Mar 2015 15:32:06 +0100 Subject: [PATCH] init --- .gitignore | 5 + LICENSE | 23 +++ Makefile | 15 ++ README.md | 123 +++++++++++ bin/pharext | Bin 0 -> 29758 bytes build/create-phar.php | 27 +++ composer.json | 11 + src/pharext/CliArgs.php | 326 ++++++++++++++++++++++++++++++ src/pharext/Command.php | 36 ++++ src/pharext/FilteredSourceDir.php | 97 +++++++++ src/pharext/GitSourceDir.php | 70 +++++++ src/pharext/Installer.php | 154 ++++++++++++++ src/pharext/Packager.php | 187 +++++++++++++++++ src/pharext/PeclSourceDir.php | 101 +++++++++ src/pharext/SourceDir.php | 26 +++ src/pharext_installer.php | 11 + src/pharext_packager.php | 16 ++ 17 files changed, 1228 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100755 bin/pharext create mode 100644 build/create-phar.php create mode 100644 composer.json create mode 100644 src/pharext/CliArgs.php create mode 100644 src/pharext/Command.php create mode 100644 src/pharext/FilteredSourceDir.php create mode 100644 src/pharext/GitSourceDir.php create mode 100644 src/pharext/Installer.php create mode 100644 src/pharext/Packager.php create mode 100644 src/pharext/PeclSourceDir.php create mode 100644 src/pharext/SourceDir.php create mode 100644 src/pharext_installer.php create mode 100644 src/pharext_packager.php 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 0000000000000000000000000000000000000000..61c9b843492df9d594c6605fbb2e194e7027972b GIT binary patch literal 29758 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."/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(); -- 2.30.2