add ExecCmd and Tempdir
authorMichael Wallner <mike@php.net>
Sun, 22 Mar 2015 06:08:35 +0000 (07:08 +0100)
committerMichael Wallner <mike@php.net>
Sun, 22 Mar 2015 06:08:35 +0000 (07:08 +0100)
bin/pharext
src/pharext/ExecCmd.php [new file with mode: 0644]
src/pharext/Installer.php
src/pharext/Packager.php
src/pharext/Tempdir.php [new file with mode: 0644]

index 79478198dd95ae29458a0a89bd69ed145bd26e2d..65f4d24c12310395bd647732db313df98912fea3 100755 (executable)
Binary files a/bin/pharext and b/bin/pharext differ
diff --git a/src/pharext/ExecCmd.php b/src/pharext/ExecCmd.php
new file mode 100644 (file)
index 0000000..056f0b2
--- /dev/null
@@ -0,0 +1,134 @@
+<?php
+
+namespace pharext;
+
+class ExecCmd
+{
+       /**
+        * Sudo command, if the cmd needs escalated privileges
+        * @var string
+        */
+       private $sudo;
+       
+       /**
+        * Executable of the cmd
+        * @var string
+        */
+       private $command;
+       
+       /**
+        * Passthrough cmd output
+        * @var bool
+        */
+       private $verbose;
+       
+       /**
+        * Output of cmd run
+        * @var string
+        */
+       private $output;
+       
+       /**
+        * Return code of cmd run
+        * @var int
+        */
+       private $status;
+
+       /**
+        * @param string $command
+        * @param bool verbose
+        */
+       public function __construct($command, $verbose = false) {
+               $this->command = $command;
+               $this->verbose = $verbose;
+               
+               /* interrupt output stream */
+               if ($verbose) {
+                       printf("\n");
+               }
+       }
+       
+       /**
+        * (Re-)set sudo command
+        * @param string $sudo
+        */
+       public function setSu($sudo = false) {
+               $this->sudo = $sudo;
+       }
+       
+       /**
+        * Execute a program with escalated privileges handling interactive password prompt
+        * @param string $command
+        * @param string $output
+        * @param int $status
+        */
+       private function suExec($command, &$output, &$status) {
+               if (!($proc = proc_open($command, [STDIN,["pipe","w"],["pipe","w"]], $pipes))) {
+                       $status = -1;
+                       throw new \Exception("Failed to run {$command}");
+               }
+               $stdout = $pipes[1];
+               $passwd = 0;
+               while (!feof($stdout)) {
+                       $R = [$stdout]; $W = []; $E = [];
+                       if (!stream_select($R, $W, $E, null)) {
+                               continue;
+                       }
+                       $data = fread($stdout, 0x1000);
+                       /* only check a few times */
+                       if ($passwd++ < 10) {
+                               if (stristr($data, "password")) {
+                                       printf("\n%s", $data);
+                               }
+                       }
+                       $output .= $data;
+               }
+               $status = proc_close($proc);
+       }
+
+       /**
+        * Run the command
+        * @param array $args
+        * @throws \Exception
+        */
+       public function run(array $args = null) {
+               $exec = escapeshellcmd($this->command);
+               if ($args) {
+                       $exec .= " ". implode(" ", array_map("escapeshellarg", (array) $args));
+               }
+               
+               if ($this->sudo) {
+                       $this->suExec(sprintf($this->sudo." 2>&1", $exec), $this->output, $this->status);
+               } elseif ($this->verbose) {
+                       ob_start(function($s) {
+                               $this->output .= $s;
+                               return $s;
+                       }, 1);
+                       passthru($exec, $this->status);
+                       ob_end_flush();
+               } else {
+                       exec($exec ." 2>&1", $output, $this->status);
+                       $this->output = implode("\n", $output);
+               }
+               
+               if ($this->status) {
+                       throw new \Exception("Command {$this->command} failed ({$this->status})");
+               }
+       }
+       
+       /**
+        * Retrieve exit code of cmd run
+        * @return int
+        */
+       public function getStatus() {
+               return $status;
+       }
+       
+       /**
+        * Retrieve output of cmd run
+        * @return string
+        */
+       public function getOutput() {
+               return $this->output;
+       }
+}
index 5e86f3189f4c74fa1d7825164ed6c50cf3b54714..b41d7fdea4ffc43f0b56f74c4512fe56fa15f549 100644 (file)
@@ -69,7 +69,7 @@ class Installer implements Command
                $phar = new Phar(Phar::running(false));
                foreach ($phar as $entry) {
                        if (fnmatch("*.ext.phar*", $entry->getBaseName())) {
-                               $temp = $this->newtemp($entry->getBaseName());
+                               $temp = new Tempdir($entry->getBaseName());
                                $phar->extractTo($temp, $entry->getFilename(), true);
                                $phars[$temp] = new Phar($temp."/".$entry->getFilename());
                        }
@@ -140,37 +140,58 @@ class Installer implements Command
                        $this->error(null);
                        exit(4);
                }
-
-               // phpize
-               $this->exec("phpize", $this->php("ize"));
-
-               // configure
-               $args = ["--with-php-config=". $this->php("-config")];
-               if ($this->args->configure) {
-                       $args = array_merge($args, $this->args->configure);
-               }
-               $this->exec("configure", "./configure", $args);
-
-               // make
-               if ($this->args->verbose) {
-                       $this->exec("make", "make", ["-j3"]);
-               } else {
-                       $this->exec("make", "make", ["-j3", "-s"]);
-               }
-
-               // install
-               if ($this->args->verbose) {
-                       $this->exec("install", "make", ["install"], true);
-               } else {
-                       $this->exec("install", "make", ["install", "-s"], true);
-               }
-
-               // activate
+               
+               $this->build();
                $this->activate();
-
-               // cleanup
                $this->cleanup($temp);
        }
+       
+       /**
+        * Phpize + trinity
+        */
+       private function build() {
+               try {
+                       // phpize
+                       $this->info("Runnin phpize ... ");
+                       $cmd = new ExecCmd($this->php("ize"), $this->args->verbose);
+                       $cmd->run();
+                       $this->info("OK\n");
+                               
+                       // configure
+                       $this->info("Running configure ... ");
+                       $args = ["--with-php-config=". $this->php("-config")];
+                       if ($this->args->configure) {
+                               $args = array_merge($args, $this->args->configure);
+                       }
+                       $cmd = new ExecCmd("./configure", $this->args->verbose);
+                       $cmd->run($args);
+                       $this->info("OK\n");
+                               
+                       // make
+                       $this->info("Running make ... ");
+                       $cmd = new ExecCmd("make", $this->args->verbose);
+                       if ($this->args->verbose) {
+                               $cmd->run(["-j3"]);
+                       } else {
+                               $cmd->run(["-j3", "-s"]);
+                       }
+                       $this->info("OK\n");
+               
+                       // install
+                       $this->info("Running make install ... ");
+                       $cmd->setSu($this->args->sudo);
+                       if ($this->args->verbose) {
+                               $cmd->run(["install"]);
+                       } else {
+                               $cmd->run(["install", "-s"]);
+                       }
+                       $this->info("OK\n");
+               
+               } catch (\Exception $e) {
+                       $this->error("%s\n", $e->getMessage());
+                       $this->error("%s\n", $cmd->getOutput());
+               }
+       }
 
        /**
         * Perform any cleanups
@@ -233,13 +254,31 @@ class Installer implements Command
                        $path = $temp->getPathname();
                        $stat = stat($file);
 
-                       $ugid = sprintf("%d:%d", $stat["uid"], $stat["gid"]);
-                       $this->exec("INI owner transfer", "chown", [$ugid, $path], true);
-                       
-                       $perm = decoct($stat["mode"] & 0777);
-                       $this->exec("INI permission transfer", "chmod", [$perm, $path], true);
-                       
-                       $this->exec("INI activation", "mv", [$path, $file], true);
+                       try {
+                               $this->info("Running INI owner transfer ... ");
+                               $ugid = sprintf("%d:%d", $stat["uid"], $stat["gid"]);
+                               $cmd = new ExecCmd("chown", $this->args->verbose);
+                               $cmd->setSu($this->args->sudo);
+                               $cmd->run([$ugid, $path]);
+                               $this->info("OK\n");
+                               
+                               $this->info("Running INI permission transfer ... ");
+                               $perm = decoct($stat["mode"] & 0777);
+                               $cmd = new ExecCmd("chmod", $this->args->verbose);
+                               $cmd->setSu($this->args->sudo);
+                               $cmd->run([$perm, $path]);
+                               $this->info("OK\n");
+       
+                               $this->info("Running INI activation ... ");
+                               $cmd = new ExecCmd("mv", $this->args->verbose);
+                               $cmd->setSu($this->args->sudo);
+                               $cmd->run([$path, $file]);
+                               $this->info("OK\n");
+                       } catch (\Exception $e) {
+                               $this->error("%s\n", $e->getMessage());
+                               $this->error("%s\n", $cmd->getOutput());
+                               exit(5);
+                       }
                }
        }
 }
index 33aa72e1f4e839665b68b1d3346a54dd7a8d4b84..d66e83b51783c933082853d9bd8528a3dcc952ad 100644 (file)
@@ -95,6 +95,10 @@ class Packager implements Command
                }
 
                try {
+                       /* source needs to be evaluated before CliArgs validation, 
+                        * 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"]) {
@@ -115,7 +119,10 @@ class Packager implements Command
                
                if ($errs) {
                        if (!$this->args["quiet"]) {
-                               $this->header();
+                               if (!headers_sent()) {
+                                       /* only display header, if we didn't generate any output yet */
+                                       $this->header();
+                               }
                        }
                        foreach ($errs as $err) {
                                $this->error("%s\n", $err);
@@ -154,9 +161,13 @@ class Packager implements Command
         */
        private function download($source) {
                if ($this->args["git"]) {
-                       $local = $this->newtemp("gitclone");
-                       $this->exec("git clone", "git", ["clone", $source, $local]);
-                       $source = $local;
+                       $this->info("Cloning %s ... ", $source);
+                       $local = new Tempdir("gitclone");
+                       $cmd = new ExecCmd("git", $this->args->verbose);
+                       $cmd->run(["clone", $source, $local]);
+                       if (!$this->args->verbose) {
+                               $this->info("OK\n");
+                       }
                } else {
                        $this->info("Fetching remote source %s ... ", $source);
                        if (!$remote = fopen($source, "r")) {
@@ -169,12 +180,11 @@ class Packager implements Command
                                exit(2);
                        }
                        $local->closeStream();
-                       $source = $local->getPathname();
                        $this->info("OK\n");
                }
                
                $this->cleanup[] = $local;
-               return $source;
+               return $local->getPathname();
        }
 
        /**
@@ -183,8 +193,10 @@ class Packager implements Command
         * @return string extracted directory
         */
        private function extract($source) {
-               $dest = $this->newtemp("local");
-               $this->info("Extracting to %s ... ", $dest);
+               $dest = new Tempdir("local");
+               if ($this->args->verbose) {
+                       $this->info("Extracting to %s ... ", $dest);
+               }
                $archive = new PharData($source);
                $archive->extractTo($dest);
                $this->info("OK\n");
diff --git a/src/pharext/Tempdir.php b/src/pharext/Tempdir.php
new file mode 100644 (file)
index 0000000..585033a
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+
+namespace pharext;
+
+class Tempdir extends \SplFileInfo
+{
+       private $dir;
+       
+       public function __construct($prefix) {
+               $temp = sprintf("%s/%s.%s", sys_get_temp_dir(), $prefix, uniqid());
+               if (!is_dir($temp)) {
+                       if (!mkdir($temp, 0700, true)) {
+                               throw new Exception("Could not create tempdir: ".error_get_last()["message"]);
+                       }
+               }
+               parent::__construct($temp);
+       }
+}