Implement packager and installer hooks
authorMichael Wallner <mike@php.net>
Fri, 6 Mar 2015 08:11:02 +0000 (09:11 +0100)
committerMichael Wallner <mike@php.net>
Fri, 6 Mar 2015 09:08:15 +0000 (10:08 +0100)
==Packager hook
If not --pecl nor --git are explicitely given, look for a
pharext_install.php in --source. This script will be exectuted by the
Packager. It must return a callable with the following signature:

function(Packager $pkg, $path) : function(Packager $pkg, $path);

So, the callback should return another callable.
The primary callback is meant to set things like --name and --release,
so you don't have to on the command line; build automation FTW.
The secondary callback is meant to create the file iterator of the
source dir, but if you're in a git clone, you might easily just return a
new pharext\GitSourceDir and be done.

==Installer hook
The packager will look for a pharext_install.php file within the root of
the source dir. This script will be executed by the Installer; it must
return a callable with the following signature:

function(Installer $installer) : function(Installer $installer);

So, again, the callback should return another callable.
The primary callback is meant to add your own command line arguments to
the CliArgs parser, and the secnodary callback is meant to proccess
those args.

For --pecl source dirs a pharext_install.php script is automatically
generated from the package.xml.

==Examples for pecl_http

pharext_package.php
---8<---
<?php

namespace pharext;

return function(Packager $packager, $path) {
$args = $packager->getArgs();
$args->name = "pecl_http";
$args->release = current(preg_filter("/^.*PHP_PECL_HTTP_VERSION\s+\"(.*)\".*$/s", "\$1", file("../http.git/php_http.h")));
return function (Packager $packager, $path) {
return new GitSourceDir($packager, $path);
};
};
?>
--->8---

pharext_install.php
---8<---
<?php

namespace pharext;

return function(Installer $installer) {
$installer->getArgs()->compile([
[null, "with-http-zlib-dir", "Where to find zlib",
CliArgs::OPTARG],
[null, "with-http-libcurl-dir", "Where to find libcurl",
CliArgs::OPTARG],
[null, "with-http-libevent-dir", "Where to find libev{,ent{,2}}",
CliArgs::OPTARG],
[null, "with-http-libidn-dir", "Where to find libidn",
CliArgs::OPTARG],
]);

return function(Installer $installer) {
$opts = [
"with-http-zlib-dir",
"with-http-libcurl-dir",
"with-http-libevent-dir",
"with-http-libidn-dir",
];
$args = $installer->getArgs();
foreach ($opts as $opt) {
if (isset($args[$opt])) {
$args->configure = "--$opt=".$args[$opt];
}
}
};
};
?>
--->8---

Makefile
bin/pharext
src/pharext/CliArgs.php
src/pharext/CliCommand.php
src/pharext/Installer.php
src/pharext/Packager.php
src/pharext/PeclSourceDir.php
src/pharext/PharextSourceDir.php [new file with mode: 0644]
src/pharext_install.tpl.php [new file with mode: 0644]
tests/src/pharext/CliCommandTest.php

index 6b3dc1f0dc6ccc1e327869470e74d6d71f0289b7..10271865c90e708a9411e959da98c17f4c884db3 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -7,8 +7,6 @@ all: bin/pharext
 bin/pharext: src/* src/pharext/*
        @echo "Linting changed source files ... "
        @for file in $?; do php -l $$file | sed -ne '/^No syntax errors/!p' && exit $${PIPESTATUS[0]}; done
-       @echo "Running tests ... "
-       @phpunit tests
        @echo "Creating bin/pharext ... "
        php -d phar.readonly=0 build/create-phar.php
        chmod +x $@
index e79c0424b80e5f8c3e43e6359021f4c032fd54f4..641f4f50627e67bb5c63ae6ddc712d610d833bbb 100755 (executable)
Binary files a/bin/pharext and b/bin/pharext differ
index 1f07e4463ead32319d6dd641feae46badc9104ac..d753931ee7a5e0f86f7e21cd16dc41860dbc8f16 100644 (file)
@@ -51,7 +51,7 @@ class CliArgs implements \ArrayAccess
         * Original option spec
         * @var array
         */
-       private $orig;
+       private $orig = [];
        
        /**
         * Compiled spec
@@ -79,10 +79,11 @@ class CliArgs implements \ArrayAccess
         * @return pharext\CliArgs self
         */
        public function compile(array $spec = null) {
-               $this->orig = $spec;
-               $this->spec = [];
+               $this->orig = array_merge($this->orig, $spec);
                foreach ((array) $spec as $arg) {
-                       $this->spec["-".$arg[0]] = $arg;
+                       if (isset($arg[0])) { 
+                               $this->spec["-".$arg[0]] = $arg;
+                       }
                        $this->spec["--".$arg[1]] = $arg;
                }
                return $this;
@@ -296,12 +297,18 @@ class CliArgs implements \ArrayAccess
                return $this->offsetGet($o);
        }
        function offsetSet($o, $v) {
+               $osn = $this->optShortName($o);
+               $oln = $this->optLongName($o);
                if ($this->optIsMulti($o)) {
-                       $this->args["-".$this->optShortName($o)][] = $v;
-                       $this->args["--".$this->optLongName($o)][] = $v;
+                       if (isset($osn)) {
+                               $this->args["-$osn"][] = $v;
+                       }
+                       $this->args["--$oln"][] = $v;
                } else {
-                       $this->args["-".$this->optShortName($o)] = $v;
-                       $this->args["--".$this->optLongName($o)] = $v;
+                       if (isset($osn)) {
+                               $this->args["-$osn"] = $v;
+                       }
+                       $this->args["--$oln"] = $v;
                }
        }
        function __set($o, $v) {
index 1299a0e99397bb437fd9f58c308d5ccc8fd3b945..f4f29b6ab10d6d58aace00977c238486f574e98d 100644 (file)
@@ -16,7 +16,7 @@ trait CliCommand
         * Output pharext vX.Y.Z header
         */
        function header() {
-               printf("pharext v%s (c) Michael Wallner <mike@php.net>\n", VERSION);
+               printf("pharext v%s (c) Michael Wallner <mike@php.net>\n\n", VERSION);
        }
        
        /**
@@ -24,7 +24,7 @@ trait CliCommand
         * @param string $prog
         */
        public function help($prog) {
-               printf("\nUsage:\n\n  \$ %s", $prog);
+               printf("Usage:\n\n  \$ %s", $prog);
                
                $flags = [];
                $required = [];
@@ -51,9 +51,27 @@ trait CliCommand
                        printf(" [-%s <arg>]", implode("|-", array_column($optional, 0)));
                }
                printf("\n\n");
-               foreach ($this->args->getSpec() as $spec) {
-                       printf("    -%s|--%s %s", $spec[0], $spec[1], ($spec[3] & CliArgs::REQARG) ? "<arg>  " : (($spec[3] & CliArgs::OPTARG) ? "[<arg>]" : "       "));
-                       printf("%s%s%s", str_repeat(" ", 16-strlen($spec[1])), $spec[2], ($spec[3] & CliArgs::REQUIRED) ? " (REQUIRED)" : "");
+               $spc = $this->args->getSpec();
+               $max = max(array_map("strlen", array_column($spc, 1)));
+               $max += $max % 8 + 2;
+               foreach ($spc as $spec) {
+                       if (isset($spec[0])) {
+                               printf("    -%s|", $spec[0]);
+                       } else {
+                               printf("    ");
+                       }
+                       printf("--%s ", $spec[1]);
+                       if ($spec[3] & CliArgs::REQARG) {
+                               printf("<arg>  ");
+                       } elseif ($spec[3] & CliArgs::OPTARG) {
+                               printf("[<arg>]");
+                       } else {
+                               printf("       ");
+                       }
+                       printf("%s%s", str_repeat(" ", $max-strlen($spec[1])+3*!isset($spec[0])), $spec[2]);
+                       if ($spec[3] & CliArgs::REQUIRED) {
+                               printf(" (REQUIRED)");
+                       }
                        if (isset($spec[4])) {
                                printf(" [%s]", $spec[4]);
                        }
index f6c7807063be73616c162e390a7c95fd8b68aacf..626cfa6a95b30f0254f9f1e290bd89864ce670f6 100644 (file)
@@ -40,6 +40,13 @@ class Installer implements Command
         * @see \pharext\Command::run()
         */
        public function run($argc, array $argv) {
+               if (($hook = stream_resolve_include_path("pharext_install.php"))) {
+                       $callable = include $hook;
+                       if (is_callable($callable)) {
+                               $recv = $callable($this);
+                       }
+               }
+               
                $errs = [];
                $prog = array_shift($argv);
                foreach ($this->args->parse(--$argc, $argv) as $error) {
@@ -69,6 +76,10 @@ class Installer implements Command
                        exit(1);
                }
                
+               if (isset($recv)) {
+                       $recv($this);
+               }
+               
                $this->installPackage();
        }
        
index b6b05e1ac6b467f1b6f19acc57bc3fffc0ba448e..5401b4c269f84f7f2190c60b372b1caeb84e82b8 100644 (file)
@@ -70,6 +70,8 @@ class Packager implements Command
                                $this->source = new PeclSourceDir($this, $this->args["source"]);
                        } elseif ($this->args["git"]) {
                                $this->source = new GitSourceDir($this, $this->args["source"]);
+                       } elseif (realpath($this->args["source"]."/pharext_package.php")) {
+                               $this->source = new PharextSourceDir($this, $this->args["source"]);
                        } else {
                                $this->source = new FilteredSourceDir($this, $this->args["source"]);
                        }
index ab7174681c7d1fdf1cca8c67f6472ed161f17303..ca065ad21c4e142e50b521d07271513354d7a2f0 100644 (file)
@@ -25,6 +25,12 @@ class PeclSourceDir implements \IteratorAggregate, SourceDir
         */
        private $path;
        
+       /**
+        * Installer hook
+        * @var string
+        */
+       private $hook;
+       
        /**
         * @inheritdoc
         * @see \pharext\SourceDir::__construct()
@@ -48,6 +54,18 @@ class PeclSourceDir implements \IteratorAggregate, SourceDir
                        }
                }
                
+               if (($configure = $sxe->xpath("/pecl:package/pecl:extsrcrelease/pecl:configureoption"))) {
+                       $this->hook = tmpfile();
+                       ob_start(function($s) {
+                               fwrite($this->hook, $s);
+                               return null;
+                       });
+                       call_user_func(function() use ($configure) {
+                               include __DIR__."/../pharext_install.tpl.php";
+                       });
+                       ob_end_flush();
+               }
+               
                $this->cmd = $cmd;
                $this->sxe = $sxe;
                $this->path = $path;
@@ -79,6 +97,10 @@ class PeclSourceDir implements \IteratorAggregate, SourceDir
         * @return Generator
         */
        private function generateFiles() {
+               if ($this->hook) {
+                       rewind($this->hook);
+                       yield "pharext_install.php" => $this->hook;
+               }
                foreach ($this->sxe->xpath("//pecl:file") as $file) {
                        $path = $this->path ."/". $this->dirOf($file) ."/". $file["name"];
                        if ($this->cmd->getArgs()->verbose) {
diff --git a/src/pharext/PharextSourceDir.php b/src/pharext/PharextSourceDir.php
new file mode 100644 (file)
index 0000000..e0d9a2a
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+namespace pharext;
+
+class PharextSourceDir implements \IteratorAggregate, SourceDir
+{
+       private $cmd;
+       private $path;
+       private $iter;
+       
+       public function __construct(Command $cmd, $path) {
+               $this->cmd = $cmd;
+               $this->path = $path;
+               
+               $callable = include "$path/pharext_package.php";
+               if (!is_callable($callable)) {
+                       throw new \Exception("Package hook did not return a callable");
+               }
+               $this->iter = $callable($cmd, $path);
+       }
+       
+       public function getBaseDir() {
+               return $this->path;
+       }
+       
+       public function getIterator() {
+               if (!is_callable($this->iter)) {
+                       throw new \Exception("Package hook callback did not return a callable");
+               } 
+               return call_user_func($this->iter, $this->cmd, $this->path);
+       } 
+}
\ No newline at end of file
diff --git a/src/pharext_install.tpl.php b/src/pharext_install.tpl.php
new file mode 100644 (file)
index 0000000..9bf8271
--- /dev/null
@@ -0,0 +1,36 @@
+<?='<?php'?>
+
+/**
+ * Generated by pharext v<?=pharext\VERSION?> at <?=date("Y-m-d H:i:i T")?>.
+ */
+namespace pharext;
+
+return function(Installer $installer) {
+       $args = $installer->getArgs();
+       <?php foreach ($configure as $cfg) : ?>
+       
+       $args->compile([[
+               null, 
+               "<?=$cfg["name"]?>", 
+               "<?=ucfirst($cfg["prompt"])?>", 
+               CliArgs::OPTARG,
+               <?php if (strlen($cfg["default"])) : ?>
+               "<?=$cfg["default"]?>"
+               <?php else : ?>
+               NULL
+               <?php endif; ?>
+               
+       ]]);
+       <?php endforeach; ?>
+       
+       return function(Installer $installer) {
+               $args = $installer->getArgs();
+               <?php foreach ($configure as $cfg) : ?>
+               
+               if (isset($args["<?=$cfg["name"]?>"])) {
+                       $args->configure = "--<?=$cfg["name"]?>=".$args["<?=$cfg["name"]?>"];
+               }
+               <?php endforeach; ?>
+               
+       };
+};
index a2bf3d9acd02f672cea1a1501110c424a4c2938f..7fd24487d2888318328fce30d3ca7d2649b4819f 100644 (file)
@@ -35,7 +35,6 @@ class CliCommandTest extends \PHPUnit_Framework_TestCase
        
        public function testHelp() {
                $this->expectOutputString(<<<EOF
-
 Usage:
 
   $ testprog [-hvqs] [-p|-n|-c <arg>]