From e990b6dabecbdaf98b8d8b2173b0d697f9b2b754 Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Fri, 27 Mar 2015 20:08:44 +0100 Subject: [PATCH] more refactoring; now the package hook starts to make sense --- Makefile | 2 +- bin/pharext | Bin 65445 -> 65466 bytes src/pharext/Cli/Args.php | 17 +-- src/pharext/Cli/Command.php | 37 +++--- src/pharext/ExecCmd.php | 58 +++++++-- src/pharext/Installer.php | 73 +++++------ src/pharext/Packager.php | 78 +++++++----- src/pharext/SourceDir.php | 31 +++-- src/pharext/SourceDir/Git.php | 42 ++++--- src/pharext/SourceDir/Pecl.php | 161 ++++++++++++------------- src/pharext/SourceDir/Pharext.php | 62 ---------- src/pharext/Task/Cleanup.php | 47 ++++++++ src/pharext_install.tpl.php | 38 ------ tests/src/pharext/GitSourceDirTest.php | 2 +- tests/src/pharext/TaskTest.php | 71 +++++++++++ 15 files changed, 402 insertions(+), 317 deletions(-) delete mode 100644 src/pharext/SourceDir/Pharext.php create mode 100644 src/pharext/Task/Cleanup.php delete mode 100644 src/pharext_install.tpl.php create mode 100644 tests/src/pharext/TaskTest.php diff --git a/Makefile b/Makefile index 433b800..4c78e66 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ bin/pharext: src/* src/pharext/* src/pharext/*/* test: @echo "Running tests ... " - @phpunit tests + @php -dphar.readonly=0 `which phpunit` tests clean: rm bin/pharext* diff --git a/bin/pharext b/bin/pharext index 911b258b1f47e349d06b92667419046624737b15..c32af327e50927ca82d60750af0b2d3a270e4a62 100755 GIT binary patch delta 5475 zcmd5=dvH|M84rO78<1?w1M<+E+}urelg(2ql0eKRArJ{+$ph+amgQ#mZgS1;-Oass z6C;VKZMCJHvDA;5;y8|dTB}aM+0j}%+M%sh3zo-FE82(DRrBn!0m zuTC>_bM~I|ec$-q2T8J$ zjqebr{m7MRX*kzU#|`JJr@>Jwvc;~_O8eI3e36d!J+J0K+>r}i<+g%auN;ij#E0V_ zXK@NTlG|5(2uH+RI9!@-zdeI5&~fnIzXhREC}VQHLNyz|Ah_80h_H%{CxvQze>!KP z<5w!L*#&2W0%$JF;*4e2vhmKcnk7`@Pv)nk?GJkn!kN-sI967`7T-0A%EdbNev|k) zHhxoF!I-9y>Bi+}wo}PPZ4p^g6LD(CbvRFj(y{AA$yT(3tM^Z_fiZ5zg${lR4{pH;3Kc=OneLeo`N1>*I1uEHJv3n6Tju&PrszKyi z6ocr7WF&MLeR?^R*JWpkW3o0F(`B-TsEJ4fI_rE_7{iKQ-3;HaTL&lWTua5^upAuG zk)pn?DHE2~A1)@Vi8<>cJ2GwfC&ndR9}iVkHNnYxcfR?GJQjBqsc(X%4W-kzU`#a> zH6b@K#wq%LipUXNR>H(-F*aL_)&{keM3u)$L{ViTg$-FF@o^%nhBiSeYni0DhDL^+ z!hkBcxNJ+5G z1UfpqJzdOFDo~S+gr-{h@+hPGQSo3P2vS2K_+MDKa#&JB5%irgEEy!E=<$d&p%Xn8 zl_@1Nk&F*35gF&C5k(y$v4jy%!2a62V%OL`8RYe>m7)z)I+TijhM;h*! zx6n)w$XM}67YiFt2`g_(F?eAK)0be1mX0PA*}#l6(;+q(Kr=PN>0_q2iC{TN@I!RP zvP@EgiZwKs*9*xnuh_I?F_)?s6HXV@w-mxhElvMb5A|w+rJ}hZIf&Q0oF#;(}{75hZ7?+}=W@=E5g-LHLp#|l3MYH5uj+e9{M1o)} z>W*VhpigkS>gFNY@Zf^eWgat|I2?oIN;RB* z%>z4HZFT5*x)-K3<}n)Y)LkYjFi})79yG2|Q!=YPe}R))V_9sPrkkdjp;r;t1Siaz{Z>sCxx&+Ngo+7uNS9CZF9M*o3(`M1f)yW zcP~xdtjn8N7*PhO%VBf44w`Pc5}v*<8!mY7fTNqI;8&Z=;A40Gf-r^%N}jEPLqjF- z(ksQ;2!F;fH;{&1(IK<92u}3~OHK1A2l@u=y=CdR8tE+rZ|^0mT5kY;+q)6=ZF$(4 zX{)WQWDw9JBOJ(MG8w{6WK9kcGutp3!!c;_Ujqk6v#!Bh@$%H|#A%ic9Q>T3Dn((x zud)qCS#Vi7J)y{v5E0lUS<_6M{KUHmrI&qm2n@3b>J&8@?`fUNf$skN){CU@RarL>n77dHXy)>)ccbLZTLtRg*F#howY> zl9C%tkuy`IkzLo+8Ibf{j|ykQIbYHLg+4h4YtToFZPr4J$(ljV7>AUA<#2@zk0fmys|fz{9?lxvu(O zZR>xcAQ|4;G7ob6v1!CA3YUZf6g&TDf)S0=)97T78a8hywupDe>KhRD2UHXOD26z6 zXCTnt*%Ju3ny}t+6Fg^uU<7M0L=-IFI5cbWxT1zkjG^ZSv$T$$uFG8z^o+0#+2x8_ zhUB=8N+!?V1oQCVBSzytoTqxy1uhT8%|VkU*syJN7C#J{EJY{+IpG`IRzdv6JUA3M zzNpQDvOjrC;b+@NVM5xo$U-XEzoiJa4&H`O5$xSpUUVsTm`?Ed@d6-}2F9MfDxJMv z1J68{Q*hZt%S7$L<}XWrG*~$g?hIWA*UIZ*w=x0E+gHQ1I};`&w&bCky^G+_@r8Fk zxG>qS7pB9y-PQPhMRHd{67a|3WgXP^2^~SZ6H8FZh-pok30;N@qeZY_)K%9b2NRmE zjLDG+6675O>F8y3sv_%|)N3qC#KeC%lPHC|^!x={6iaJqq-2>E<%n%MRg2QgON+w1 zA5~EEAkdltua4#w^1~9qD~v}RZ;&WQKX`A{xow7>1Iwj)H#WBQ_w=mo?hAN&171(x z`fkt0wPX^5YO`IAx_3jTH_*PRua{;htkkN|X_R)xV#^`ch#Rc$>}tm{wWqg_R|?@6 z=0tE9t9(sSh=e#WdqbHeF)ma$D>~L9SYDXDg-wexGZ`^WW6VV&-$Y9;h8s3pLdCvd zgfP(jg@zNCaxEzEduav3m7siMp|J=JaAZeO3fh7|Xy2#`tXTi&Hrs|p_&$QGG8rEB8 zxkY)Ix~MWuVH@oF@rosLHG#gp9l7Qv)EP4_hEMlSUTM{FF-;lrB_sQ?i?bA+0c<#G zF#kgAKpyNrSU&3!c>F+hI-ZuY7b=sRUzp6WZDAcEmZY%vsbcthLssT&kqU>OcNCaM zl3tLrzNPsIo&yCH<|-?ZXP3F_Ekr}#B}Z1spmkv#X>;q+WH4QZ{+I|ICxz}Ha<_xyEnvis!G z^a5JqIR)alHso*+pF?kU=zeUj96DV8)&$|eU=s%?3|SAvg9bLZq#lbH+L|{C0zCRw zUDj+4(Yq=Wwx7x?xU@Qs#DY=;cdy@_2dmz`ke$j!`vv5)U(5%G#*5+BcRsm{K%KQZ zE|@&E<3FsPKecno+^yi-&*Z^Fr}J{UtS*Dg!4_JB?RQte#MAaAbNvOL?i~32>)G(; z>kW{7HV10nu*0!2J8XI**JkQ~fN#7}Va_gHyH24^Cs&W2lQ|zn6g@!CfKxEnYcc#6 z_je}t9~sNBTiu{V4$V(JA>A%8@kV5P|8V>-Pn})g`S{dbJAb<2#a;NVE^S@w#@2rUH(el` delta 5791 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 diff --git a/src/pharext/Cli/Args.php b/src/pharext/Cli/Args.php index f0b0498..38cfaca 100644 --- a/src/pharext/Cli/Args.php +++ b/src/pharext/Cli/Args.php @@ -67,24 +67,27 @@ class Args implements \ArrayAccess /** * Compile the original spec - * @param array $spec + * @param array|Traversable $spec */ - public function __construct(array $spec = null) { - $this->compile($spec); + public function __construct($spec = null) { + if (is_array($spec) || $spec instanceof Traversable) { + $this->compile($spec); + } + } /** * Compile the original spec - * @param array $spec + * @param array|Traversable $spec * @return pharext\CliArgs self */ - public function compile(array $spec = null) { - $this->orig = array_merge($this->orig, (array) $spec); - foreach ((array) $spec as $arg) { + public function compile($spec) { + foreach ($spec as $arg) { if (isset($arg[0])) { $this->spec["-".$arg[0]] = $arg; } $this->spec["--".$arg[1]] = $arg; + $this->orig[] = $arg; } return $this; } diff --git a/src/pharext/Cli/Command.php b/src/pharext/Cli/Command.php index 0cc0bb4..6ad2c80 100644 --- a/src/pharext/Cli/Command.php +++ b/src/pharext/Cli/Command.php @@ -110,15 +110,13 @@ trait Command * @see \pharext\Command::error() */ public function error($fmt) { - if (!$this->args->quiet) { - if (!isset($fmt)) { - $fmt = "%s\n"; - $arg = error_get_last()["message"]; - } else { - $arg = array_slice(func_get_args(), 1); - } - vfprintf(STDERR, "ERROR: $fmt", $arg); + if (!isset($fmt)) { + $fmt = "%s\n"; + $arg = error_get_last()["message"]; + } else { + $arg = array_slice(func_get_args(), 1); } + vfprintf(STDERR, "ERROR: $fmt", $arg); } /** @@ -183,21 +181,16 @@ trait Command } /** - * rm -r - * @param string $dir + * Verbosity + * @return boolean */ - private function rm($dir) { - foreach (scandir($dir) as $entry) { - if ($entry === "." || $entry === "..") { - continue; - } elseif (is_dir("$dir/$entry")) { - $this->rm("$dir/$entry"); - } elseif (!unlink("$dir/$entry")) { - $this->warn(null); - } - } - if (!rmdir($dir)) { - $this->warn(null); + public function verbosity() { + if ($this->args->verbose) { + return true; + } elseif ($this->args->quiet) { + return false; + } else { + return null; } } } diff --git a/src/pharext/ExecCmd.php b/src/pharext/ExecCmd.php index 84bb708..7e657f3 100644 --- a/src/pharext/ExecCmd.php +++ b/src/pharext/ExecCmd.php @@ -57,16 +57,19 @@ class ExecCmd /** * Execute a program with escalated privileges handling interactive password prompt * @param string $command - * @param string $output - * @param int $status + * @param bool $verbose + * @return int exit status */ - private function suExec($command, &$output, &$status) { + private function suExec($command, $verbose = null) { if (!($proc = proc_open($command, [STDIN,["pipe","w"],["pipe","w"]], $pipes))) { - $status = -1; + $this->status = -1; throw new Exception("Failed to run {$command}"); } + $stdout = $pipes[1]; $passwd = 0; + $checks = 10; + while (!feof($stdout)) { $R = [$stdout]; $W = []; $E = []; if (!stream_select($R, $W, $E, null)) { @@ -74,14 +77,49 @@ class ExecCmd } $data = fread($stdout, 0x1000); /* only check a few times */ - if ($passwd++ < 10) { + if ($passwd < $checks) { + $passwd++; if (stristr($data, "password")) { + $passwd = $checks + 1; printf("\n%s", $data); + continue; + } + } elseif ($passwd > $checks) { + /* new line after pw entry */ + printf("\n"); + $passwd = $checks; + } + + if ($verbose === null) { + print $this->progress($data, 0); + } else { + if ($verbose) { + printf("%s\n", $data); } + $this->output .= $data; } - $output .= $data; } - $status = proc_close($proc); + if ($verbose === null) { + $this->progress("", PHP_OUTPUT_HANDLER_FINAL); + } + return $this->status = proc_close($proc); + } + + /** + * Output handler that displays some progress while soaking output + * @param string $string + * @param int $flags + * @return string + */ + private function progress($string, $flags) { + static $c = 0; + static $s = ["\\","|","/","-"]; + + $this->output .= $string; + + return $flags & PHP_OUTPUT_HANDLER_FINAL + ? " \r" + : sprintf(" %s\r", $s[$c++ % count($s)]); } /** @@ -97,7 +135,7 @@ class ExecCmd } if ($this->sudo) { - $this->suExec(sprintf($this->sudo." 2>&1", $exec), $this->output, $this->status); + $this->suExec(sprintf($this->sudo." 2>&1", $exec), $this->verbose); } elseif ($this->verbose) { ob_start(function($s) { $this->output .= $s; @@ -105,6 +143,10 @@ class ExecCmd }, 1); passthru($exec, $this->status); ob_end_flush(); + } elseif ($this->verbose !== false /* !quiet */) { + ob_start([$this, "progress"], 1); + passthru($exec . " 2>&1", $this->status); + ob_end_flush(); } else { exec($exec ." 2>&1", $output, $this->status); $this->output = implode("\n", $output); diff --git a/src/pharext/Installer.php b/src/pharext/Installer.php index 83a40ad..60ca226 100644 --- a/src/pharext/Installer.php +++ b/src/pharext/Installer.php @@ -55,20 +55,39 @@ class Installer implements Command private function extract(Phar $phar) { $this->debug("Extracting %s ...\n", basename($phar->getPath())); - return (new Task\Extract($phar))->run($this->args->verbose); + return (new Task\Extract($phar))->run($this->verbosity()); } - + private function hooks(SplObjectStorage $phars) { - $hooks = []; + $hook = []; foreach ($phars as $phar) { - if (isset($phar["pharext_install.php"])) { - $callable = include $phar["pharext_install.php"]; - if (is_callable($callable)) { - $hooks[] = $callable($this); + if (isset($phar["pharext_package.php"])) { + $sdir = include $phar["pharext_package.php"]; + if ($sdir instanceof SourceDir) { + $this->args->compile($sdir->getArgs()); + $hook[] = $sdir; } } } - return $hooks; + return $hook; + } + + private function load() { + $list = new SplObjectStorage(); + $phar = new Phar(Phar::running(false)); + $temp = $this->extract($phar); + + foreach ($phar as $entry) { + $dep_file = $entry->getBaseName(); + if (fnmatch("*.ext.phar*", $dep_file)) { + $dep_phar = new Phar("$temp/$dep_file"); + $list[$dep_phar] = $this->extract($dep_phar); + } + } + + /* the actual ext.phar at last */ + $list[$phar] = $temp; + return $list; } /** @@ -77,20 +96,8 @@ class Installer implements Command */ public function run($argc, array $argv) { try { - $list = new SplObjectStorage(); - $phar = new Phar(Phar::running(false)); - $temp = $this->extract($phar); - - foreach ($phar as $entry) { - $dep_file = $entry->getBaseName(); - if (fnmatch("*.ext.phar*", $dep_file)) { - $dep_phar = new Phar("$temp/$dep_file"); - $list[$dep_phar] = $this->extract($dep_phar); - } - } - /* the actual ext.phar at last */ - $list[$phar] = $temp; - + /* load the phar(s) */ + $list = $this->load(); /* installer hooks */ $hook = $this->hooks($list); } catch (\Exception $e) { @@ -141,10 +148,8 @@ class Installer implements Command try { /* post process hooks */ - foreach ($hook as $callback) { - if (is_callable($callback)) { - $callback($this); - } + foreach ($hook as $sdir) { + $sdir->setArgs($this->args); } } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); @@ -173,31 +178,27 @@ class Installer implements Command // phpize $this->info("Running phpize ...\n"); $phpize = new Task\Phpize($temp, $this->args->prefix, $this->args->{"common-name"}); - $phpize->run($this->args->verbose); + $phpize->run($this->verbosity()); // configure $this->info("Running configure ...\n"); $configure = new Task\Configure($temp, $this->args->configure, $this->args->prefix, $this->args{"common-name"}); - $configure->run($this->args->verbose); + $configure->run($this->verbosity()); // make $this->info("Running make ...\n"); $make = new Task\Make($temp); - $make->run($this->args->verbose); + $make->run($this->verbosity()); // install $this->info("Running make install ...\n"); $sudo = isset($this->args->sudo) ? $this->args->sudo : null; $install = new Task\Make($temp, ["install"], $sudo); - $install->run($this->args->verbose); + $install->run($this->verbosity()); } private function cleanup($temp) { - if (is_dir($temp)) { - $this->rm($temp); - } elseif (file_exists($temp)) { - unlink($temp); - } + (new Task\Cleanup($temp))->run(); } private function activate($temp) { @@ -213,7 +214,7 @@ class Installer implements Command $this->info("Running INI activation ...\n"); $activate = new Task\Activate($temp, $files, $type, $this->args->prefix, $this->args{"common-name"}, $sudo); - if (!$activate->run($this->args->verbose)) { + if (!$activate->run($this->verbosity())) { $this->info("Extension already activated ...\n"); } } diff --git a/src/pharext/Packager.php b/src/pharext/Packager.php index b3be6d0..3cf4f5b 100644 --- a/src/pharext/Packager.php +++ b/src/pharext/Packager.php @@ -5,6 +5,7 @@ namespace pharext; use Phar; use pharext\Cli\Args as CliArgs; use pharext\Cli\Command as CliCommand; +use pharext\Exception; /** * The extension packaging command executed by bin/pharext @@ -71,11 +72,7 @@ class Packager implements Command */ function __destruct() { foreach ($this->cleanup as $cleanup) { - if (is_dir($cleanup)) { - $this->rm($cleanup); - } elseif (file_exists($cleanup)) { - unlink($cleanup); - } + $cleanup->run(); } } @@ -112,16 +109,7 @@ class Packager implements Command * so e.g. name and version can be overriden and CliArgs * does not complain about missing arguments */ - if ($this->args["source"]) { - $source = $this->localize($this->args["source"]); - if ($this->args["pecl"]) { - $this->source = new SourceDir\Pecl($this, $source); - } elseif ($this->args["git"]) { - $this->source = new SourceDir\Git($this, $source); - } else { - $this->source = new SourceDir\Pharext($this, $source); - } - } + $this->loadSource(); } catch (\Exception $e) { $errs[] = $e->getMessage(); } @@ -159,17 +147,17 @@ class Packager implements Command $task = new Task\GitClone($source); } else { $task = new Task\StreamFetch($source, function($bytes_pct) { - $this->debug(" %3d%% [%s>%s] \r", + $this->info(" %3d%% [%s>%s] \r%s", floor($bytes_pct*100), str_repeat("=", round(50*$bytes_pct)), - str_repeat(" ", round(50*(1-$bytes_pct))) + str_repeat(" ", round(50*(1-$bytes_pct))), + $bytes_pct == 1 ? "\n":"" ); }); } - $local = $task->run($this->args->verbose); - $this->debug("\n"); + $local = $task->run($this->verbosity()); - $this->cleanup[] = $local; + $this->cleanup[] = new Task\Cleanup($local); return $local; } @@ -182,9 +170,9 @@ class Packager implements Command $this->debug("Extracting %s ...\n", $source); $task = new Task\Extract($source); - $dest = $task->run($this->args->verbose); + $dest = $task->run($this->verbosity()); - $this->cleanup[] = $dest; + $this->cleanup[] = new Task\Cleanup($dest); return $dest; } @@ -196,21 +184,47 @@ class Packager implements Command private function localize($source) { if (!stream_is_local($source)) { $source = $this->download($source); - $this->cleanup[] = $source; + $this->cleanup[] = new Task\Cleanup($source); } $source = realpath($source); if (!is_dir($source)) { $source = $this->extract($source); - $this->cleanup[] = $source; + $this->cleanup[] = new Task\Cleanup($source); if ($this->args->pecl) { $this->debug("Sanitizing PECL dir ...\n"); - $source = (new Task\PeclFixup($source))->run($this->args->verbose); + $source = (new Task\PeclFixup($source))->run($this->verbosity()); } } return $source; } + /** + * Load the source dir + * @throws \pharext\Exception + */ + private function loadSource(){ + if ($this->args["source"]) { + $source = $this->localize($this->args["source"]); + + if ($this->args["pecl"]) { + $this->source = new SourceDir\Pecl($source); + } elseif ($this->args["git"]) { + $this->source = new SourceDir\Git($source); + } elseif (is_file("$source/parext_package.php")) { + $this->source = include "$source/pharext_package.php"; + } + + if (!$this->source instanceof SourceDir) { + throw new Exception("Unknown source dir $source"); + } + + foreach ($this->source->getPackageInfo() as $key => $val) { + $this->args->$key = $val; + } + } + } + /** * Creates the extension phar */ @@ -233,9 +247,9 @@ class Packager implements Command try { if ($this->args->sign) { $this->info("Using private key to sign phar ...\n"); - $pass = (new Task\Askpass)->run($this->args->verbose); + $pass = (new Task\Askpass)->run($this->verbosity()); $sign = new Task\PharSign($file, $this->args->sign, $pass); - $pkey = $sign->run($this->args->verbose); + $pkey = $sign->run($this->verbosity()); } } catch (\Exception $e) { @@ -247,13 +261,13 @@ class Packager implements Command try { $gzip = (new Task\PharCompress($file, Phar::GZ))->run(); $move = new Task\PharRename($gzip, $this->args->dest, $this->args->name ."-". $this->args->release); - $name = $move->run($this->args->verbose); + $name = $move->run($this->verbosity()); $this->info("Created gzipped phar %s\n", $name); if ($this->args->sign) { $sign = new Task\PharSign($name, $this->args->sign, $pass); - $sign->run($this->args->verbose)->exportPublicKey($name.".pubkey"); + $sign->run($this->verbosity())->exportPublicKey($name.".pubkey"); } } catch (\Exception $e) { @@ -265,13 +279,13 @@ class Packager implements Command try { $bzip = (new Task\PharCompress($file, Phar::BZ2))->run(); $move = new Task\PharRename($bzip, $this->args->dest, $this->args->name ."-". $this->args->release); - $name = $move->run($this->args->verbose); + $name = $move->run($this->verbosity()); $this->info("Created bzipped phar %s\n", $name); if ($this->args->sign) { $sign = new Task\PharSign($name, $this->args->sign, $pass); - $sign->run($this->args->verbose)->exportPublicKey($name.".pubkey"); + $sign->run($this->verbosity())->exportPublicKey($name.".pubkey"); } } catch (\Exception $e) { @@ -281,7 +295,7 @@ class Packager implements Command try { $move = new Task\PharRename($file, $this->args->dest, $this->args->name ."-". $this->args->release); - $name = $move->run($this->args->verbose); + $name = $move->run($this->verbosity()); $this->info("Created executable phar %s\n", $name); diff --git a/src/pharext/SourceDir.php b/src/pharext/SourceDir.php index fbdd2f6..8620668 100644 --- a/src/pharext/SourceDir.php +++ b/src/pharext/SourceDir.php @@ -3,24 +3,31 @@ namespace pharext; /** - * Source directory interface + * Source directory interface, which should yield file names to package on traversal */ interface SourceDir extends \Traversable { - /** - * Read the source directory - * - * Note: Best practices are for others, but if you want to follow them, do - * not put constructors in interfaces. Keep your complaints, I warned you. - * - * @param Command $cmd - * @param string $path - */ - public function __construct(Command $cmd, $path); - /** * Retrieve the base directory * @return string */ public function getBaseDir(); + + /** + * Retrieve gathered package info + * @return array|Traversable + */ + public function getPackageInfo(); + + /** + * Provide installer command line args + * @return array|Traversable + */ + public function getArgs(); + + /** + * Process installer command line args + * @param \pharext\Cli\Args $args + */ + public function setArgs(Cli\Args $args); } diff --git a/src/pharext/SourceDir/Git.php b/src/pharext/SourceDir/Git.php index 8e35bf9..e17a305 100644 --- a/src/pharext/SourceDir/Git.php +++ b/src/pharext/SourceDir/Git.php @@ -3,6 +3,7 @@ namespace pharext\SourceDir; use pharext\Command; +use pharext\Cli\Args; use pharext\SourceDir; /** @@ -10,12 +11,6 @@ use pharext\SourceDir; */ class Git implements \IteratorAggregate, SourceDir { - /** - * The Packager command - * @var pharext\Command - */ - private $cmd; - /** * Base directory * @var string @@ -26,8 +21,7 @@ class Git implements \IteratorAggregate, SourceDir * @inheritdoc * @see \pharext\SourceDir::__construct() */ - public function __construct(Command $cmd, $path) { - $this->cmd = $cmd; + public function __construct($path) { $this->path = $path; } @@ -38,7 +32,29 @@ class Git implements \IteratorAggregate, SourceDir public function getBaseDir() { return $this->path; } - + + /** + * @inheritdoc + * @return array + */ + public function getPackageInfo() { + return []; + } + + /** + * @inheritdoc + * @return array + */ + public function getArgs() { + return []; + } + + /** + * @inheritdoc + */ + public function setArgs(Args $args) { + } + /** * Generate a list of files by `git ls-files` * @return Generator @@ -50,13 +66,7 @@ class Git implements \IteratorAggregate, SourceDir $path = realpath($this->path); while (!feof($pipe)) { if (strlen($file = trim(fgets($pipe)))) { - if ($this->cmd->getArgs()->verbose) { - $this->cmd->info("Packaging %s\n", $file); - } /* there may be symlinks, so no realpath here */ - if (!file_exists("$path/$file")) { - $this->cmd->warn("File %s does not exist in %s\n", $file, $path); - } yield "$path/$file"; } } @@ -64,7 +74,7 @@ class Git implements \IteratorAggregate, SourceDir } chdir($pwd); } - + /** * Implements IteratorAggregate * @see IteratorAggregate::getIterator() diff --git a/src/pharext/SourceDir/Pecl.php b/src/pharext/SourceDir/Pecl.php index 9ca9726..188ea67 100644 --- a/src/pharext/SourceDir/Pecl.php +++ b/src/pharext/SourceDir/Pecl.php @@ -2,21 +2,16 @@ namespace pharext\SourceDir; -use pharext\Command; +use pharext\Cli\Args; use pharext\Exception; use pharext\SourceDir; +use pharext\Tempfile; /** * A PECL extension source directory containing a v2 package.xml */ class Pecl implements \IteratorAggregate, SourceDir { - /** - * The Packager command - * @var pharext\Packager - */ - private $cmd; - /** * The package.xml * @var SimpleXmlElement @@ -28,44 +23,28 @@ class Pecl implements \IteratorAggregate, SourceDir * @var string */ private $path; + + /** + * The package.xml + * @var string + */ + private $file; /** * @inheritdoc * @see \pharext\SourceDir::__construct() */ - public function __construct(Command $cmd, $path) { - if (realpath("$path/package2.xml")) { - $sxe = simplexml_load_file("$path/package2.xml"); - } elseif (realpath("$path/package.xml")) { - $sxe = simplexml_load_file("$path/package.xml"); + public function __construct($path) { + if (is_file("$path/package2.xml")) { + $sxe = simplexml_load_file($this->file = "$path/package2.xml"); + } elseif (is_file("$path/package.xml")) { + $sxe = simplexml_load_file($this->file = "$path/package.xml"); } else { throw new Exception("Missing package.xml in $path"); } - $sxe->registerXPathNamespace("pecl", $sxe->getDocNamespaces()[""]); - - $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->warn("%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->warn("%s\n", $error); - } - } - if (!isset($args->zend)) { - if ($sxe->xpath("/pecl:package/pecl:zendextsrcrelease")) { - foreach ($args->parse(1, ["--zend"]) as $error) { - $cmd->warn("%s\n", $error); - } - } - } + $sxe->registerXPathNamespace("pecl", $sxe->getDocNamespaces()[""]); - $this->cmd = $cmd; $this->sxe = $sxe; $this->path = $path; } @@ -77,6 +56,58 @@ class Pecl implements \IteratorAggregate, SourceDir public function getBaseDir() { return $this->path; } + + /** + * Retrieve gathered package info + * @return Generator + */ + public function getPackageInfo() { + if (($name = $this->sxe->xpath("/pecl:package/pecl:name"))) { + yield "name" => (string) $name[0]; + } + if (($release = $this->sxe->xpath("/pecl:package/pecl:version/pecl:release"))) { + yield "release" => (string) $release[0]; + } + if ($this->sxe->xpath("/pecl:package/pecl:zendextsrcrelease")) { + yield "zend" => true; + } + } + + /** + * @inheritdoc + * @see \pharext\SourceDir::getArgs() + */ + public function getArgs() { + $configure = $this->sxe->xpath("/pecl:package/pecl:extsrcrelease/pecl:configureoption"); + foreach ($configure as $cfg) { + yield [null, $cfg["name"], ucfirst($cfg["prompt"]), Args::OPTARG, + strlen($cfg["default"]) ? $cfg["default"] : null]; + } + $configure = $this->sxe->xpath("/pecl:package/pecl:zendextsrcrelease/pecl:configureoption"); + foreach ($configure as $cfg) { + yield [null, $cfg["name"], ucfirst($cfg["prompt"]), Args::OPTARG, + strlen($cfg["default"]) ? $cfg["default"] : null]; + } + } + + /** + * @inheritdoc + * @see \pharext\SourceDir::setArgs() + */ + public function setArgs(Args $args) { + $configure = $this->sxe->xpath("/pecl:package/pecl:extsrcrelease/pecl:configureoption"); + foreach ($configure as $cfg) { + if (isset($args[$cfg["name"]])) { + $args->configure = "--{$cfg["name"]}={$args[$cfg["name"]]}"; + } + } + $configure = $this->sxe->xpath("/pecl:package/pecl:zendextsrcrelease/pecl:configureoption"); + foreach ($configure as $cfg) { + if (isset($args[$cfg["name"]])) { + $args->configure = "--{$cfg["name"]}={$args[$cfg["name"]]}"; + } + } + } /** * Compute the path of a file by parent dir nodes @@ -92,20 +123,17 @@ class Pecl implements \IteratorAggregate, SourceDir } /** - * Render installer hook - * @param array $configure - * @return string + * Generate a list of files from the package.xml + * @return Generator */ - private static function loadHook($configure, $dependencies) { - require_once "pharext/Version.php"; - return include __DIR__."/../../pharext_install.tpl.php"; - } + private function generateFiles() { + /* hook */ + $temp = tmpfile(); + fprintf($temp, " $temp; - /** - * Create installer hook - * @return \Generator - */ - private function generateHooks() { + /* deps */ $dependencies = $this->sxe->xpath("/pecl:package/pecl:dependencies/pecl:required/pecl:package"); foreach ($dependencies as $key => $dep) { if (($glob = glob("{$this->path}/{$dep->name}-*.ext.phar*"))) { @@ -116,44 +144,13 @@ class Pecl implements \IteratorAggregate, SourceDir ); }); yield end($glob); - } else { - unset($dependencies[$key]); - } - } - $configure = $this->sxe->xpath("/pecl:package/pecl:extsrcrelease/pecl:configureoption"); - if ($configure) { - $fd = tmpfile(); - ob_start(function($s) use($fd){ - fwrite($fd, $s); - return null; - }); - self::loadHook($configure, $dependencies); - ob_end_flush(); - rewind($fd); - yield "pharext_install.php" => $fd; - } - } - - /** - * Generate a list of files from the package.xml - * @return Generator - */ - private function generateFiles() { - foreach ($this->generateHooks() as $file => $hook) { - if ($this->cmd->getArgs()->verbose) { - $this->cmd->info("Packaging %s\n", is_scalar($hook) ? $hook : $file); } - yield $file => $hook; } + + /* files */ + yield $this->file; 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", substr($path, strlen($this->path))); - } - if (!($realpath = realpath($path))) { - $this->cmd->warn("File %s does not exist", $path); - } - yield $realpath; + yield realpath($this->path ."/". $this->dirOf($file) ."/". $file["name"]); } } diff --git a/src/pharext/SourceDir/Pharext.php b/src/pharext/SourceDir/Pharext.php deleted file mode 100644 index 8ca0901..0000000 --- a/src/pharext/SourceDir/Pharext.php +++ /dev/null @@ -1,62 +0,0 @@ -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); - } - - /** - * @inheritdoc - * @see \pharext\SourceDir::getBaseDir() - */ - public function getBaseDir() { - return $this->path; - } - - /** - * Implements IteratorAggregate - * @see IteratorAggregate::getIterator() - */ - public function getIterator() { - if (!is_callable($this->iter)) { - return new Git($this->cmd, $this->path); - } - return call_user_func($this->iter, $this->cmd, $this->path); - } -} diff --git a/src/pharext/Task/Cleanup.php b/src/pharext/Task/Cleanup.php new file mode 100644 index 0000000..1263bd9 --- /dev/null +++ b/src/pharext/Task/Cleanup.php @@ -0,0 +1,47 @@ +rm = $rm; + } + + /** + * @param bool $verbose + */ + public function run($verbose = false) { + if (is_dir($this->rm)) { + $rdi = new RecursiveDirectoryIterator($this->rm, + FilesystemIterator::CURRENT_AS_PATHNAME | + FilesystemIterator::SKIP_DOTS); + $rii = new RecursiveIteratorIterator($rdi, + RecursiveIteratorIterator::CHILD_FIRST); + foreach ($rii as $path) { + if ($rii->isDir()) { + rmdir($path); + } else { + unlink($path); + } + } + rmdir($this->rm); + } else { + @unlink($this->rm); + } + } +} diff --git a/src/pharext_install.tpl.php b/src/pharext_install.tpl.php deleted file mode 100644 index f7a356d..0000000 --- a/src/pharext_install.tpl.php +++ /dev/null @@ -1,38 +0,0 @@ - - -/** - * Generated by pharext v at . - */ -namespace pharext; - -use pharext\Cli\Args as CliArgs; - -return function(Installer $installer) { - $args = $installer->getArgs(); - - - $args->compile([[ - null, - "", - "", - CliArgs::OPTARG, - - "" - - NULL - - - ]]); - - - return function(Installer $installer) { - $args = $installer->getArgs(); - - - if (isset($args[""])) { - $args->configure = "--=".$args[""]; - } - - - }; -}; diff --git a/tests/src/pharext/GitSourceDirTest.php b/tests/src/pharext/GitSourceDirTest.php index 5e6a989..ac04d4d 100644 --- a/tests/src/pharext/GitSourceDirTest.php +++ b/tests/src/pharext/GitSourceDirTest.php @@ -25,7 +25,7 @@ class GitSourceDirTest extends \PHPUnit_Framework_TestCase protected $source; protected function setUp() { - $this->source = new SourceDir\Git(new Cmd, "."); + $this->source = new SourceDir\Git("."); } public function testGetBaseDir() { diff --git a/tests/src/pharext/TaskTest.php b/tests/src/pharext/TaskTest.php new file mode 100644 index 0000000..cb11377 --- /dev/null +++ b/tests/src/pharext/TaskTest.php @@ -0,0 +1,71 @@ +run(); + + $this->assertTrue(is_dir("$dir/.git"), "is_dir($dir/.git)"); + + (new Task\Cleanup($dir))->run(); + $this->assertFalse(is_dir($dir), "is_dir($dir)"); + } + + function testPecl() { + $cmd = new Task\StreamFetch("http://pecl.php.net/get/pecl_http", function($pct) use(&$log) { + $log[] = $pct; + }); + $tmp = $cmd->run(); + + $this->assertTrue(is_file($tmp), "is_file($tmp)"); + $this->assertGreaterThan(1, count($log), "1 < count(\$log)"); + $this->assertContains(0, $log, "in_array(0, \$log)"); + $this->assertContains(1, $log, "in_array(1, \$log)"); + + $cmd = new Task\Extract($tmp); + $dir = $cmd->run(); + + $this->assertTrue(is_dir($dir), "is_dir($dir)"); + $this->assertTrue(is_file("$dir/package.xml"), "is_file($dir/package.xml"); + + $cmd = new Task\PeclFixup($dir); + $new = $cmd->run(); + + $this->assertTrue(is_dir($new), "is_dir($new)"); + $this->assertFalse(is_file("$dir/package.xml"), "is_file($dir/package.xml"); + $this->assertTrue(is_file("$new/package.xml"), "is_file($new/package.xml"); + + (new Task\Cleanup($dir))->run(); + $this->assertFalse(is_dir($dir), "is_dir($dir)"); + $this->assertFalse(is_dir($new), "is_dir($new)"); + } + + function testPackage() { + $tmp = (new Task\StreamFetch("http://pecl.php.net/get/json_post/1.0.0", function(){}))->run(); + $dir = (new Task\Extract($tmp))->run(); + $new = (new Task\PeclFixup($dir))->run(); + $src = new SourceDir\Pecl($new); + $inf = [ + "date" => date("Y-m-d"), + "name" => "json_post", + "release" => "1.0.0", + "license" => file_get_contents($src->getBaseDir()."/LICENSE"), + "stub" => "pharext_installer.php", + "type" => "extension", + ]; + $pkg = (new Task\PharBuild($src, $inf))->run(); + $gzp = (new Task\PharCompress($pkg, \Phar::GZ))->run(); + $pkg = (new Task\PharRename($pkg, ".", "json_post-1.0.0"))->run(); + $gzp = (new Task\PharRename($gzp, ".", "json_post-1.0.0"))->run(); + + $this->assertTrue(is_file($pkg), "is_file($pkg)"); + $this->assertTrue(is_file($gzp), "is_file($gzp)"); + } +} -- 2.30.2