From 861260c111bff72f60665393660b6f5375559510 Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Tue, 24 Mar 2015 17:58:24 +0100 Subject: [PATCH] major refactoring under the hood --- LICENSE | 2 +- Makefile | 1 - bin/pharext | Bin 47490 -> 62765 bytes build/create-phar.php | 57 ++---- src/pharext/Cli/Command.php | 67 ++++++- src/pharext/Command.php | 9 +- src/pharext/Exception.php | 17 ++ src/pharext/ExecCmd.php | 16 +- src/pharext/Installer.php | 257 ++++++++----------------- src/pharext/Openssl/PrivateKey.php | 12 +- src/pharext/Packager.php | 273 ++++++++++----------------- src/pharext/SourceDir/Git.php | 2 +- src/pharext/SourceDir/Pecl.php | 16 +- src/pharext/SourceDir/Pharext.php | 3 +- src/pharext/Task.php | 11 ++ src/pharext/Task/Activate.php | 94 +++++++++ src/pharext/Task/Askpass.php | 40 ++++ src/pharext/Task/BundleGenerator.php | 28 +++ src/pharext/Task/Configure.php | 54 ++++++ src/pharext/Task/Extract.php | 41 ++++ src/pharext/Task/GitClone.php | 36 ++++ src/pharext/Task/Make.php | 65 +++++++ src/pharext/Task/PeclFixup.php | 48 +++++ src/pharext/Task/PharBuild.php | 82 ++++++++ src/pharext/Task/PharCompress.php | 66 +++++++ src/pharext/Task/PharRename.php | 54 ++++++ src/pharext/Task/PharSign.php | 48 +++++ src/pharext/Task/Phpize.php | 55 ++++++ src/pharext/Task/StreamFetch.php | 75 ++++++++ src/pharext/Tempdir.php | 17 +- src/pharext/Tempfile.php | 42 +++-- src/pharext/Tempname.php | 29 +++ src/pharext_install.tpl.php | 2 +- 33 files changed, 1178 insertions(+), 441 deletions(-) create mode 100644 src/pharext/Exception.php create mode 100644 src/pharext/Task.php create mode 100644 src/pharext/Task/Activate.php create mode 100644 src/pharext/Task/Askpass.php create mode 100644 src/pharext/Task/BundleGenerator.php create mode 100644 src/pharext/Task/Configure.php create mode 100644 src/pharext/Task/Extract.php create mode 100644 src/pharext/Task/GitClone.php create mode 100644 src/pharext/Task/Make.php create mode 100644 src/pharext/Task/PeclFixup.php create mode 100644 src/pharext/Task/PharBuild.php create mode 100644 src/pharext/Task/PharCompress.php create mode 100644 src/pharext/Task/PharRename.php create mode 100644 src/pharext/Task/PharSign.php create mode 100644 src/pharext/Task/Phpize.php create mode 100644 src/pharext/Task/StreamFetch.php create mode 100644 src/pharext/Tempname.php diff --git a/LICENSE b/LICENSE index 3f7d0f6..ab8ebb2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015, Michael Wallner . +Copyright (c) 2015, Michael Wallner . All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Makefile b/Makefile index 9697343..433b800 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,6 @@ bin/pharext: src/* src/pharext/* src/pharext/*/* @for file in $?; do php -l $$file | sed -ne '/^No syntax errors/!p' && exit $${PIPESTATUS[0]}; done @echo "Creating bin/pharext ... " php -d phar.readonly=0 build/create-phar.php - chmod +x $@ test: @echo "Running tests ... " diff --git a/bin/pharext b/bin/pharext index 8e385989c2d14bcf8cf95ab55ebd041a9b6a2db7..dbde37d8ae89529d271f2a087833d8c9252d2def 100755 GIT binary patch delta 19496 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 diff --git a/build/create-phar.php b/build/create-phar.php index db9013f..4836ceb 100644 --- a/build/create-phar.php +++ b/build/create-phar.php @@ -4,47 +4,28 @@ * Creates bin/pharext, invoked through the Makefile */ -$pkgname = __DIR__."/../bin/pharext"; -$tmpname = __DIR__."/pharext.phar"; +set_include_path(dirname(__DIR__)."/src"); +spl_autoload_register(function($c) { + return include strtr($c, "\\_", "//") . ".php"; +}); -if (file_exists($tmpname)) { - if (!unlink($tmpname)) { - fprintf(STDERR, "%s\n", error_get_last()["message"]); - exit(3); - } -} +require_once __DIR__."/../src/pharext/Version.php"; -$package = new \Phar($tmpname, 0, "pharext.phar"); +$file = (new pharext\Task\PharBuild(null, [ + "header" => sprintf("pharext v%s (c) Michael Wallner ", pharext\VERSION), + "version" => pharext\VERSION, + "name" => "pharext", + "date" => date("Y-m-d"), + "stub" => "pharext_packager.php", + "license" => file_get_contents(__DIR__."/../LICENSE") +], false))->run(); if (getenv("SIGN")) { - shell_exec("stty -echo"); - printf("Password: "); - $password = fgets(STDIN, 1024); - printf("\n"); - shell_exec("stty echo"); - if (substr($password, -1) == "\n") { - $password = substr($password, 0, -1); - } - - $pkey = openssl_pkey_get_private("file://".__DIR__."/pharext.key", $password); - if (!is_resource($pkey)) { - $this->error("Could not load private key %s/pharext.key", __DIR__); - exit(3); - } - if (!openssl_pkey_export($pkey, $key)) { - $this->error(null); - exit(3); - } - - $package->setSignatureAlgorithm(Phar::OPENSSL, $key); + $pass = (new pharext\Task\Askpass)->run(); + $sign = new pharext\Task\PharSign($file, __DIR__."/pharext.key", $pass); + $pkey = $sign->run(); + $pkey->exportPublicKey(__DIR__."/../bin/pharext.pubkey"); } -$package->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); -} +/* we do not need the extra logic of Task\PharRename */ +rename($file, __DIR__."/../bin/pharext"); diff --git a/src/pharext/Cli/Command.php b/src/pharext/Cli/Command.php index e19ce93..0cc0bb4 100644 --- a/src/pharext/Cli/Command.php +++ b/src/pharext/Cli/Command.php @@ -4,7 +4,21 @@ namespace pharext\Cli; use pharext\Cli\Args as CliArgs; -require_once "pharext/Version.php"; +use Phar; + +if (!function_exists("array_column")) { + function array_column(array $array, $col, $idx = null) { + $result = []; + foreach ($array as $el) { + if (isset($idx)) { + $result[$el[$idx]] = $el[$col]; + } else { + $result[] = $el[$col]; + } + } + return $result; + } +} trait Command { @@ -22,12 +36,37 @@ trait Command return $this->args; } + /** + * Retrieve metadata of the currently running phar + * @param string $key + * @return mixed + */ + public function metadata($key = null) { + $running = new Phar(Phar::running(false)); + + if ($key === "signature") { + $sig = $running->getSignature(); + return sprintf("%s signature of %s\n%s", + $sig["hash_type"], + $this->metadata("name"), + chunk_split($sig["hash"], 64, "\n")); + } + + $metadata = $running->getMetadata(); + if (isset($key)) { + return $metadata[$key]; + } + return $metadata; + } + /** * Output pharext vX.Y.Z header */ - function header() { - printf("pharext v%s (c) Michael Wallner \n\n", - \pharext\VERSION); + public function header() { + if (!headers_sent()) { + /* only display header, if we didn't generate any output yet */ + printf("%s\n\n", $this->metadata("header")); + } } /** @@ -50,6 +89,22 @@ trait Command } } + /** + * @inheritdoc + * @see \pharext\Command::warn() + */ + public function warn($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, "Warning: $fmt", $arg); + } + } + /** * @inheritdoc * @see \pharext\Command::error() @@ -138,11 +193,11 @@ trait Command } elseif (is_dir("$dir/$entry")) { $this->rm("$dir/$entry"); } elseif (!unlink("$dir/$entry")) { - $this->error(null); + $this->warn(null); } } if (!rmdir($dir)) { - $this->error(null); + $this->warn(null); } } } diff --git a/src/pharext/Command.php b/src/pharext/Command.php index b174f7d..61b8810 100644 --- a/src/pharext/Command.php +++ b/src/pharext/Command.php @@ -27,13 +27,20 @@ interface Command */ public function info($fmt); + /** + * Print warning + * @param string $fmt + * @param string ...$args + */ + public function warn($fmt); + /** * Print error * @param string $fmt * @param string ...$args */ public function error($fmt); - + /** * Execute the command * @param int $argc command line argument count diff --git a/src/pharext/Exception.php b/src/pharext/Exception.php new file mode 100644 index 0000000..bf3d87b --- /dev/null +++ b/src/pharext/Exception.php @@ -0,0 +1,17 @@ +command = $command; $this->verbose = $verbose; - - /* interrupt output stream */ - if ($verbose) { - printf("\n"); - } } /** @@ -65,7 +63,7 @@ class ExecCmd 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}"); + throw new Exception("Failed to run {$command}"); } $stdout = $pipes[1]; $passwd = 0; @@ -89,7 +87,7 @@ class ExecCmd /** * Run the command * @param array $args - * @throws \Exception + * @throws \pharext\Exception */ public function run(array $args = null) { $exec = escapeshellcmd($this->command); @@ -112,7 +110,7 @@ class ExecCmd } if ($this->status) { - throw new \Exception("Command {$this->command} failed ({$this->status})"); + throw new Exception("Command {$this->command} failed ({$this->status})"); } } @@ -121,7 +119,7 @@ class ExecCmd * @return int */ public function getStatus() { - return $status; + return $this->status; } /** diff --git a/src/pharext/Installer.php b/src/pharext/Installer.php index 7866b65..4b90c49 100644 --- a/src/pharext/Installer.php +++ b/src/pharext/Installer.php @@ -2,10 +2,12 @@ namespace pharext; -use Phar; use pharext\Cli\Args as CliArgs; use pharext\Cli\Command as CliCommand; +use Phar; +use SplObjectStorage; + /** * The extension install command executed by the extension phar */ @@ -13,18 +15,6 @@ class Installer implements Command { use CliCommand; - /** - * The temporary directory we should operate in - * @var string - */ - private $tmp; - - /** - * The directory we came from - * @var string - */ - private $cwd; - /** * Create the command */ @@ -51,11 +41,22 @@ class Installer implements Command ]); } - /** - * Cleanup temp directory - */ - public function __destruct() { - $this->cleanup(); + private function extract(Phar $phar) { + $this->debug("Extracting %s ...\n", basename($phar->getPath())); + return (new Task\Extract($phar))->run($this->args->verbose); + } + + private function hooks(SplObjectStorage $phars) { + $hooks = []; + foreach ($phars as $phar) { + if (isset($phar["pharext_install.php"])) { + $callable = include $phar["pharext_install.php"]; + if (is_callable($callable)) { + $hooks[] = $callable($this); + } + } + } + return $hooks; } /** @@ -63,169 +64,111 @@ class Installer implements Command * @see \pharext\Command::run() */ public function run($argc, array $argv) { - $this->cwd = getcwd(); - $this->tmp = new Tempdir(basename(Phar::running(false))); - + $list = new SplObjectStorage(); $phar = new Phar(Phar::running(false)); + $temp = $this->extract($phar); + foreach ($phar as $entry) { - if (fnmatch("*.ext.phar*", $entry->getBaseName())) { - $temp = new Tempdir($entry->getBaseName()); - $phar->extractTo($temp, $entry->getFilename(), true); - $phars[(string) $temp] = new Phar($temp."/".$entry->getFilename()); + $dep_file = $entry->getBaseName(); + if (fnmatch("*.ext.phar*", $dep_file)) { + $dep_phar = new Phar("$temp/$dep_file"); + $list[$dep_phar] = $this->extract($dep_phar); } } - $phars[(string) $this->tmp] = $phar; + /* the actual ext.phar at last */ + $list[$phar] = $temp; - foreach ($phars as $phar) { - if (isset($phar["pharext_install.php"])) { - $callable = include $phar["pharext_install.php"]; - if (is_callable($callable)) { - $recv[] = $callable($this); - } - } - } - + /* installer hooks */ + $hook = $this->hooks($list); + + /* standard arg stuff */ $errs = []; $prog = array_shift($argv); foreach ($this->args->parse(--$argc, $argv) as $error) { $errs[] = $error; } - + if ($this->args["help"]) { $this->header(); $this->help($prog); exit; } - + foreach ($this->args->validate() as $error) { $errs[] = $error; } - + if ($errs) { if (!$this->args["quiet"]) { $this->header(); } foreach ($errs as $err) { $this->error("%s\n", $err); - } + } if (!$this->args["quiet"]) { $this->help($prog); } exit(1); } - - if (isset($recv)) { - foreach ($recv as $r) { - $r($this); - } - } - foreach ($phars as $temp => $phar) { - $this->installPackage($phar, $temp); - } - } - /** - * Prepares, configures, builds and installs the extension - */ - private function installPackage(Phar $phar, $temp) { - $this->info("Installing %s ... \n", basename($phar->getAlias())); - try { - $phar->extractTo($temp, null, true); - } catch (\Exception $e) { - $this->error("%s\n", $e->getMessage()); - exit(3); + /* post process hooks */ + foreach ($hook as $callback) { + if (is_callable($callback)) { + $callback($this); + } } - if (!chdir($temp)) { - $this->error(null); - exit(4); + /* install packages */ + foreach ($list as $phar) { + $this->info("Installing %s ...\n", basename($phar->getPath())); + $this->install($list[$phar]); + $this->activate($list[$phar]); + $this->cleanup($list[$phar]); + $this->info("Successfully installed %s!\n", basename($phar->getPath())); } - - $this->build(); - $this->activate(); - $this->cleanup($temp); } /** * Phpize + trinity */ - private function build() { + private function install($temp) { try { // phpize - $this->info("Runnin phpize ... "); - $cmd = new ExecCmd($this->php("ize"), $this->args->verbose); - $cmd->run(); - $this->info("OK\n"); - + $this->info("Running phpize ...\n"); + $phpize = new Task\Phpize($temp, $this->args->prefix, $this->args->{"common-name"}); + $phpize->run($this->args->verbose); + // 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"); + $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); // 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"); - + $this->info("Running make ...\n"); + $make = new Task\Make($temp); + $make->run($this->args->verbose); + // install - $this->info("Running make install ... "); - if (isset($this->args->sudo)) { - $cmd->setSu($this->args->sudo); - } - if ($this->args->verbose) { - $cmd->run(["install"]); - } else { - $cmd->run(["install", "-s"]); - } - $this->info("OK\n"); + $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); } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); - $this->error("%s\n", $cmd->getOutput()); + exit(2); } } - /** - * Perform any cleanups - */ - private function cleanup($temp = null) { - if (!isset($temp)) { - $temp = $this->tmp; - } + private function cleanup($temp) { if (is_dir($temp)) { - chdir($this->cwd); - $this->info("Cleaning up %s ...\n", $temp); $this->rm($temp); + } elseif (file_exists($temp)) { + unlink($temp); } } - /** - * Construct a command from prefix common-name and suffix - * @param type $suffix - * @return string - */ - private function php($suffix) { - $cmd = $this->args["common-name"] . $suffix; - if (isset($this->args->prefix)) { - $cmd = $this->args->prefix . "/bin/" . $cmd; - } - return $cmd; - } - - /** - * Activate extension in php.ini - */ - private function activate() { + private function activate($temp) { if ($this->args->ini) { $files = [realpath($this->args->ini)]; } else { @@ -233,60 +176,18 @@ class Installer implements Command $files[] = php_ini_loaded_file(); } - $extension = basename(current(glob("modules/*.so"))); - $pattern = preg_quote($extension); - - foreach ($files as $index => $file) { - $temp = new Tempfile("phpini"); - foreach (file($file) as $line) { - if (preg_match("/^\s*extension\s*=\s*[\"']?{$pattern}[\"']?\s*(;.*)?\$/", $line)) { - // already there - $this->info("Extension already activated\n"); - return; - } - fwrite($temp->getStream(), $line); - } - } - - // not found, add extension line to the last process file - if (isset($temp, $file)) { - fprintf($temp->getStream(), "extension=%s\n", $extension); - $temp->closeStream(); + $sudo = isset($this->args->sudo) ? $this->args->sudo : null; - $path = $temp->getPathname(); - $stat = stat($file); - - try { - $this->info("Running INI owner transfer ... "); - $ugid = sprintf("%d:%d", $stat["uid"], $stat["gid"]); - $cmd = new ExecCmd("chown", $this->args->verbose); - if (isset($this->args->sudo)) { - $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); - if (isset($this->args->sudo)) { - $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); - if (isset($this->args->sudo)) { - $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); + try { + $this->info("Running INI activation ...\n"); + $activate = new Task\Activate($temp, $files, $sudo); + if (!$activate->run($this->args->verbose)) { + $this->info("Extension already activated ...\n"); } + } catch (\Exception $e) { + $this->error("%s\n", $e->getMessage()); + $this->error("%s\n", $output); + exit(3); } } } diff --git a/src/pharext/Openssl/PrivateKey.php b/src/pharext/Openssl/PrivateKey.php index 35c596a..481fd86 100644 --- a/src/pharext/Openssl/PrivateKey.php +++ b/src/pharext/Openssl/PrivateKey.php @@ -2,6 +2,8 @@ namespace pharext\Openssl; +use pharext\Exception; + class PrivateKey { /** @@ -20,16 +22,16 @@ class PrivateKey * Read a private key * @param string $file * @param string $password - * @throws \Exception + * @throws \pharext\Exception */ function __construct($file, $password) { /* there appears to be a bug with refcount handling of this * resource; when the resource is stored as property, it cannot be - * "coerced to a private key" on openssl_sign() alter in another method + * "coerced to a private key" on openssl_sign() later in another method */ $key = openssl_pkey_get_private("file://$file", $password); if (!is_resource($key)) { - throw new \Exception("Could not load private key"); + throw new Exception("Could not load private key"); } openssl_pkey_export($key, $this->key); $this->pub = openssl_pkey_get_details($key)["key"]; @@ -46,11 +48,11 @@ class PrivateKey /** * Export the public key to a file * @param string $file - * @throws \Exception + * @throws \pharext\Exception */ function exportPublicKey($file) { if (!file_put_contents("$file.tmp", $this->pub) || !rename("$file.tmp", $file)) { - throw new \Exception(error_get_last()["message"]); + throw new Exception; } } } diff --git a/src/pharext/Packager.php b/src/pharext/Packager.php index d3e4000..ba7de89 100644 --- a/src/pharext/Packager.php +++ b/src/pharext/Packager.php @@ -3,7 +3,6 @@ namespace pharext; use Phar; -use PharData; use pharext\Cli\Args as CliArgs; use pharext\Cli\Command as CliCommand; @@ -58,6 +57,10 @@ class Packager implements Command CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG], [null, "signature", "Dump signature", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], + [null, "license", "Show license", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], + [null, "version", "Show version", + CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], ]); } @@ -68,7 +71,7 @@ class Packager implements Command foreach ($this->cleanup as $cleanup) { if (is_dir($cleanup)) { $this->rm($cleanup); - } else { + } elseif (file_exists($cleanup)) { unlink($cleanup); } } @@ -90,8 +93,16 @@ class Packager implements Command $this->help($prog); exit; } - if ($this->args["signature"]) { - exit($this->signature($prog)); + try { + foreach (["signature", "license", "version"] as $opt) { + if ($this->args[$opt]) { + printf("%s\n", $this->metadata($opt)); + exit; + } + } + } catch (\Exception $e) { + $this->error("%s\n", $e->getMessage()); + exit(2); } try { @@ -119,10 +130,7 @@ class Packager implements Command if ($errs) { if (!$this->args["quiet"]) { - if (!headers_sent()) { - /* only display header, if we didn't generate any output yet */ - $this->header(); - } + $this->header(); } foreach ($errs as $err) { $this->error("%s\n", $err); @@ -136,72 +144,29 @@ class Packager implements Command $this->createPackage(); } - - /** - * Dump program signature - * @param string $prog - * @return int exit code - */ - function signature($prog) { - try { - $sig = (new Phar(Phar::running(false)))->getSignature(); - printf("%s signature of %s\n%s", $sig["hash_type"], $prog, - chunk_split($sig["hash"], 64, "\n")); - return 0; - } catch (\Exception $e) { - $this->error("%s\n", $e->getMessage()); - return 2; - } - } - + /** * Download remote source * @param string $source * @return string local source */ private function download($source) { - $this->info("Fetching remote source %s ... ", $source); - if ($this->args["git"]) { - $local = new Tempdir("gitclone"); - $cmd = new ExecCmd("git", $this->args->verbose); - $cmd->run(["clone", $source, $local]); - if (!$this->args->verbose) { - $this->info("OK\n"); - } + $this->info("Fetching remote source %s ...\n", $source); + + if ($this->args->git) { + $task = new Task\GitClone($source); } else { - $context = stream_context_create([],["notification" => function($notification, $severity, $message, $code, $bytes_cur, $bytes_max) { - switch ($notification) { - case STREAM_NOTIFY_CONNECT: - $this->debug("\n"); - break; - case STREAM_NOTIFY_PROGRESS: - if ($bytes_max) { - $bytes_pct = $bytes_cur/$bytes_max; - $this->debug("\r %3d%% [%s>%s] ", - $bytes_pct*100, - str_repeat("=", round(70*$bytes_pct)), - str_repeat(" ", round(70*(1-$bytes_pct))) - ); - } - break; - case STREAM_NOTIFY_COMPLETED: - /* this is not generated, why? */ - break; - } - }]); - if (!$remote = fopen($source, "r", false, $context)) { - $this->error(null); - exit(2); - } - $local = new Tempfile("remote"); - if (!stream_copy_to_stream($remote, $local->getStream())) { - $this->error(null); - exit(2); - } - $local->closeStream(); - $this->info("OK\n"); + $task = new Task\StreamFetch($source, function($bytes_pct) { + $this->debug(" %3d%% [%s>%s] \r", + floor($bytes_pct*100), + str_repeat("=", round(50*$bytes_pct)), + str_repeat(" ", round(50*(1-$bytes_pct))) + ); + }); } - + $local = $task->run($this->args->verbose); + $this->debug("\n"); + $this->cleanup[] = $local; return $local; } @@ -212,11 +177,11 @@ class Packager implements Command * @return string extracted directory */ private function extract($source) { - $dest = new Tempdir("local"); - $this->debug("Extracting %s to %s ... ", $source, $dest); - $archive = new PharData($source); - $archive->extractTo($dest); - $this->debug("OK\n"); + $this->debug("Extracting %s ...\n", $source); + + $task = new Task\Extract($source); + $dest = $task->run($this->args->verbose); + $this->cleanup[] = $dest; return $dest; } @@ -229,134 +194,96 @@ class Packager implements Command private function localize($source) { if (!stream_is_local($source)) { $source = $this->download($source); + $this->cleanup[] = $source; } + $source = realpath($source); if (!is_dir($source)) { $source = $this->extract($source); - if ($this->args["pecl"]) { - $this->debug("Sanitizing PECL dir ... "); - $dirs = glob("$source/*", GLOB_ONLYDIR); - $files = array_diff(glob("$source/*"), $dirs); - $source = current($dirs); - foreach ($files as $file) { - rename($file, "$source/" . basename($file)); - } - $this->debug("OK\n"); + $this->cleanup[] = $source; + + if ($this->args->pecl) { + $this->debug("Sanitizing PECL dir ...\n"); + $source = (new Task\PeclFixup($source))->run($this->args->verbose); } } return $source; } - /** - * Traverses all pharext source files to bundle - * @return Generator - */ - private function bundle() { - $rdi = new \RecursiveDirectoryIterator(__DIR__); - $rii = new \RecursiveIteratorIterator($rdi); - for ($rii->rewind(); $rii->valid(); $rii->next()) { - yield "pharext/". $rii->getSubPathname() => $rii->key(); - - } - } - - /** - * Ask for password on the console - * @param string $prompt - * @return string password - */ - private function askpass($prompt = "Password:") { - system("stty -echo", $retval); - if ($retval) { - $this->error("Could not disable echo on the terminal\n"); - } - printf("%s ", $prompt); - $pass = fgets(STDIN, 1024); - system("stty echo"); - if (substr($pass, -1) == "\n") { - $pass = substr($pass, 0, -1); - } - return $pass; - } - /** * Creates the extension phar */ private function createPackage() { - $pkguniq = uniqid(); - $pkgtemp = sprintf("%s/%s.phar", sys_get_temp_dir(), $pkguniq); - $pkgdesc = "{$this->args->name}-{$this->args->release}"; - - $this->info("Creating phar %s ...%s", $pkgtemp, $this->args->verbose ? "\n" : " "); try { - $package = new Phar($pkgtemp); + $meta = array_merge($this->metadata(), [ + "date" => date("Y-m-d"), + "name" => $this->args->name, + "release" => $this->args->release, + "license" => @file_get_contents(current(glob($this->source->getBaseDir()."/LICENSE*"))), + "stub" => "pharext_installer.php", + ]); + $file = (new Task\PharBuild($this->source, $meta))->run(); if ($this->args->sign) { - $this->info("\nUsing private key to sign phar ... \n"); - $privkey = new Openssl\PrivateKey(realpath($this->args->sign), $this->askpass()); - $privkey->sign($package); + $this->info("Using private key to sign phar ...\n"); + $pass = (new Task\Askpass)->run($this->args->verbose); + $sign = new Task\PharSign($file, $this->args->sign, $pass); + $pkey = $sign->run($this->args->verbose); } - $package->startBuffering(); - $package->buildFromIterator($this->source, $this->source->getBaseDir()); - $package->buildFromIterator($this->bundle(__DIR__)); - $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(); + } catch (\Exception $e) { + $this->error("%s\n", $e->getMessage()); + exit(4); + } + + if ($this->args->gzip) { + 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); - if (!chmod($pkgtemp, 0777)) { - $this->error(null); - } elseif ($this->args->verbose) { - $this->debug("Created executable phar %s\n", $pkgtemp); - } else { - $this->info("OK\n"); - } - if ($this->args->gzip) { - $this->info("Compressing with gzip ... "); - try { - $package->compress(Phar::GZ) - ->setDefaultStub("pharext_installer.php"); - $this->info("OK\n"); - } catch (\Exception $e) { - $this->error("%s\n", $e->getMessage()); + $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"); } + + } catch (\Exception $e) { + $this->warn("%s\n", $e->getMessage()); } - if ($this->args->bzip) { - $this->info("Compressing with bzip ... "); - try { - $package->compress(Phar::BZ2) - ->setDefaultStub("pharext_installer.php"); - $this->info("OK\n"); - } catch (\Exception $e) { - $this->error("%s\n", $e->getMessage()); + } + + if ($this->args->bzip) { + 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); + + $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"); } + + } catch (\Exception $e) { + $this->warn("%s\n", $e->getMessage()); + } + } + + try { + $move = new Task\PharRename($file, $this->args->dest, $this->args->name ."-". $this->args->release); + $name = $move->run($this->args->verbose); + + $this->info("Created executable phar %s\n", $name); + + if (isset($pkey)) { + $pkey->exportPublicKey($name.".pubkey"); } - 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(null); - exit(5); - } - $this->info("OK\n"); - if ($this->args->sign && isset($privkey)) { - $keyname = $this->args->dest ."/". basename($pkgfile) . ".pubkey"; - $this->info("Public Key %s ... ", $keyname); - try { - $privkey->exportPublicKey($keyname); - $this->info("OK\n"); - } catch (\Exception $e) { - $this->error("%s", $e->getMessage()); - } - } - } } } diff --git a/src/pharext/SourceDir/Git.php b/src/pharext/SourceDir/Git.php index 531aa65..8e35bf9 100644 --- a/src/pharext/SourceDir/Git.php +++ b/src/pharext/SourceDir/Git.php @@ -55,7 +55,7 @@ class Git implements \IteratorAggregate, SourceDir } /* there may be symlinks, so no realpath here */ if (!file_exists("$path/$file")) { - $this->cmd->error("File %s does not exist in %s\n", $file, $path); + $this->cmd->warn("File %s does not exist in %s\n", $file, $path); } yield "$path/$file"; } diff --git a/src/pharext/SourceDir/Pecl.php b/src/pharext/SourceDir/Pecl.php index d21a0e0..0b7055a 100644 --- a/src/pharext/SourceDir/Pecl.php +++ b/src/pharext/SourceDir/Pecl.php @@ -3,6 +3,7 @@ namespace pharext\SourceDir; use pharext\Command; +use pharext\Exception; use pharext\SourceDir; /** @@ -38,7 +39,7 @@ class Pecl implements \IteratorAggregate, SourceDir } elseif (realpath("$path/package.xml")) { $sxe = simplexml_load_file("$path/package.xml"); } else { - throw new \Exception("Missing package.xml in $path"); + throw new Exception("Missing package.xml in $path"); } $sxe->registerXPathNamespace("pecl", $sxe->getDocNamespaces()[""]); @@ -46,14 +47,14 @@ class Pecl implements \IteratorAggregate, SourceDir 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); + $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->error("%s\n", $error); + $cmd->warn("%s\n", $error); } } @@ -89,6 +90,7 @@ class Pecl implements \IteratorAggregate, SourceDir * @return string */ private static function loadHook($configure, $dependencies) { + require_once "pharext/Version.php"; return include __DIR__."/../../pharext_install.tpl.php"; } @@ -106,7 +108,7 @@ class Pecl implements \IteratorAggregate, SourceDir substr($b, strpos(".ext.phar", $b)) ); }); - yield realpath($this->path."/".end($glob)); + yield end($glob); } else { unset($dependencies[$key]); } @@ -132,17 +134,17 @@ class Pecl implements \IteratorAggregate, SourceDir private function generateFiles() { foreach ($this->generateHooks() as $file => $hook) { if ($this->cmd->getArgs()->verbose) { - $this->cmd->info("Packaging %s\n", is_string($hook) ? $hook : $file); + $this->cmd->info("Packaging %s\n", is_scalar($hook) ? $hook : $file); } yield $file => $hook; } 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); + $this->cmd->info("Packaging %s\n", substr($path, strlen($this->path))); } if (!($realpath = realpath($path))) { - $this->cmd->error("File %s does not exist", $path); + $this->cmd->warn("File %s does not exist", $path); } yield $realpath; } diff --git a/src/pharext/SourceDir/Pharext.php b/src/pharext/SourceDir/Pharext.php index 2675a78..8ca0901 100644 --- a/src/pharext/SourceDir/Pharext.php +++ b/src/pharext/SourceDir/Pharext.php @@ -3,6 +3,7 @@ namespace pharext\SourceDir; use pharext\Command; +use pharext\Exception; use pharext\SourceDir; /** @@ -35,7 +36,7 @@ class Pharext implements \IteratorAggregate, SourceDir $callable = include "$path/pharext_package.php"; if (!is_callable($callable)) { - throw new \Exception("Package hook did not return a callable"); + throw new Exception("Package hook did not return a callable"); } $this->iter = $callable($cmd, $path); } diff --git a/src/pharext/Task.php b/src/pharext/Task.php new file mode 100644 index 0000000..473e1a6 --- /dev/null +++ b/src/pharext/Task.php @@ -0,0 +1,11 @@ +cwd = $cwd; + $this->sudo = $sudo; + if (!$this->inis = $inis) { + throw new Exception("No PHP INIs given"); + } + } + + /** + * @param bool $verbose + * @return boolean false, if extension was already activated + */ + public function run($verbose = false) { + $extension = basename(current(glob("{$this->cwd}/modules/*.so"))); + $pattern = preg_quote($extension); + + foreach ($this->inis as $file) { + $temp = new Tempfile("phpini"); + foreach (file($file) as $line) { + if (preg_match("/^\s*extension\s*=\s*[\"']?{$pattern}[\"']?\s*(;.*)?\$/", $line)) { + return false; + } + fwrite($temp->getStream(), $line); + } + } + + /* not found; append to last processed file, which is the main by default */ + fprintf($temp->getStream(), "extension=%s\n", $extension); + $temp->closeStream(); + + $path = $temp->getPathname(); + $stat = stat($file); + + // owner transfer + $ugid = sprintf("%d:%d", $stat["uid"], $stat["gid"]); + $cmd = new ExecCmd("chown", $verbose); + if (isset($this->sudo)) { + $cmd->setSu($this->sudo); + } + $cmd->run([$ugid, $path]); + + // permission transfer + $perm = decoct($stat["mode"] & 0777); + $cmd = new ExecCmd("chmod", $verbose); + if (isset($this->sudo)) { + $cmd->setSu($this->sudo); + } + $cmd->run([$perm, $path]); + + // rename + $cmd = new ExecCmd("mv", $verbose); + if (isset($this->sudo)) { + $cmd->setSu($this->sudo); + } + $cmd->run([$path, $file]); + + return true; + } +} diff --git a/src/pharext/Task/Askpass.php b/src/pharext/Task/Askpass.php new file mode 100644 index 0000000..24b22da --- /dev/null +++ b/src/pharext/Task/Askpass.php @@ -0,0 +1,40 @@ +prompt = $prompt; + } + + /** + * @param bool $verbose + * @return string + */ + public function run($verbose = false) { + system("stty -echo"); + printf("%s ", $this->prompt); + $pass = fgets(STDIN, 1024); + printf("\n"); + system("stty echo"); + if (substr($pass, -1) == "\n") { + $pass = substr($pass, 0, -1); + } + return $pass; + } +} \ No newline at end of file diff --git a/src/pharext/Task/BundleGenerator.php b/src/pharext/Task/BundleGenerator.php new file mode 100644 index 0000000..aabf9c8 --- /dev/null +++ b/src/pharext/Task/BundleGenerator.php @@ -0,0 +1,28 @@ +rewind(); $rii->valid(); $rii->next()) { + if (!$rii->isDot()) { + yield $rii->getSubPathname() => $rii->key(); + } + } + } +} diff --git a/src/pharext/Task/Configure.php b/src/pharext/Task/Configure.php new file mode 100644 index 0000000..3effbfb --- /dev/null +++ b/src/pharext/Task/Configure.php @@ -0,0 +1,54 @@ +cwd = $cwd; + $cmd = $common_name . "-config"; + if (isset($prefix)) { + $cmd = $prefix . "/bin/" . $cmd; + } + $this->args = ["--with-php-config=$cmd"]; + if ($args) { + $this->args = array_merge($this->args, $args); + } + } + + public function run($verbose = false) { + $pwd = getcwd(); + if (!chdir($this->cwd)) { + throw new Exception; + } + try { + $cmd = new ExecCmd("./configure", $verbose); + $cmd->run($this->args); + } finally { + chdir($pwd); + } + } +} diff --git a/src/pharext/Task/Extract.php b/src/pharext/Task/Extract.php new file mode 100644 index 0000000..b2f954f --- /dev/null +++ b/src/pharext/Task/Extract.php @@ -0,0 +1,41 @@ +source = $source; + } else { + $this->source = new PharData($source); + } + } + + /** + * @param bool $verbose + * @return \pharext\Tempdir + */ + public function run($verbose = false) { + $dest = new Tempdir("extract"); + $this->source->extractTo($dest); + return $dest; + } +} diff --git a/src/pharext/Task/GitClone.php b/src/pharext/Task/GitClone.php new file mode 100644 index 0000000..709a34a --- /dev/null +++ b/src/pharext/Task/GitClone.php @@ -0,0 +1,36 @@ +source = $source; + } + + /** + * @param bool $verbose + * @return \pharext\Tempdir + */ + public function run($verbose = false) { + $local = new Tempdir("gitclone"); + $cmd = new ExecCmd("git", $verbose); + $cmd->run(["clone", $this->source, $local]); + return $local; + } +} diff --git a/src/pharext/Task/Make.php b/src/pharext/Task/Make.php new file mode 100644 index 0000000..9e71565 --- /dev/null +++ b/src/pharext/Task/Make.php @@ -0,0 +1,65 @@ +cwd = $cwd; + $this->sudo = $sudo; + $this->args = $args; + } + + /** + * + * @param bool $verbose + * @throws \pharext\Exception + */ + public function run($verbose = false) { + $pwd = getcwd(); + if (!chdir($this->cwd)) { + throw new Exception; + } + try { + $cmd = new ExecCmd("make", $verbose); + if (isset($this->sudo)) { + $cmd->setSu($this->sudo); + } + $args = $this->args; + if (!$verbose) { + $args = array_merge((array) $args, ["-s"]); + } + $cmd->run($args); + } finally { + chdir($pwd); + } + } +} diff --git a/src/pharext/Task/PeclFixup.php b/src/pharext/Task/PeclFixup.php new file mode 100644 index 0000000..08a1d94 --- /dev/null +++ b/src/pharext/Task/PeclFixup.php @@ -0,0 +1,48 @@ +source = $source; + } + + /** + * @param bool $verbose + * @return string sanitized source location + * @throws \pahrext\Exception + */ + public function run($verbose = false) { + $dirs = glob("{$this->source}/*", GLOB_ONLYDIR); + $files = array_diff(glob("{$this->source}/*"), $dirs); + + if (count($dirs) !== 1 || !count($files)) { + throw new Exception("Does not look like an extracted PECL dir: {$this->source}"); + } + + $dest = current($dirs); + + foreach ($files as $file) { + if (!rename($file, "$dest/" . basename($file))) { + throw new Exception; + } + } + + return $dest; + } +} diff --git a/src/pharext/Task/PharBuild.php b/src/pharext/Task/PharBuild.php new file mode 100644 index 0000000..d6f2788 --- /dev/null +++ b/src/pharext/Task/PharBuild.php @@ -0,0 +1,82 @@ +source = $source; + $this->meta = $meta; + $this->readonly = $readonly; + } + + /** + * @param bool $verbose + * @return \pharext\Tempname + * @throws \pharext\Exception + */ + public function run($verbose = false) { + /* Phar::compress() and ::convert*() use strtok("."), ugh! + * so, be sure to not use any other dots in the filename + * except for .phar + */ + $temp = new Tempname("", "-pharext.phar"); + + $phar = new Phar($temp); + $phar->startBuffering(); + + if ($this->meta) { + $phar->setMetadata($this->meta); + if (isset($this->meta["stub"])) { + $phar->setDefaultStub($this->meta["stub"]); + $phar->setStub("#!/usr/bin/php -dphar.readonly=" . + intval($this->readonly) ."\n". + $phar->getStub()); + } + } + + $phar->buildFromIterator((new Task\BundleGenerator)->run()); + + if ($this->source) { + $phar->buildFromIterator($this->source, $this->source->getBaseDir()); + } + + $phar->stopBuffering(); + + if (!chmod($temp, fileperms($temp) | 0111)) { + throw new Exception; + } + + return $temp; + } +} \ No newline at end of file diff --git a/src/pharext/Task/PharCompress.php b/src/pharext/Task/PharCompress.php new file mode 100644 index 0000000..d090e28 --- /dev/null +++ b/src/pharext/Task/PharCompress.php @@ -0,0 +1,66 @@ +file = $file; + $this->package = new Phar($file); + $this->encoding = $encoding; + + switch ($encoding) { + case Phar::GZ: + $this->extension = ".gz"; + break; + case Phar::BZ2: + $this->extension = ".bz2"; + break; + } + } + + /** + * @param bool $verbose + * @return string + */ + public function run($verbose = false) { + $phar = $this->package->compress($this->encoding); + $meta = $phar->getMetadata(); + if (isset($meta["stub"])) { + /* drop shebang */ + $phar->setDefaultStub($meta["stub"]); + } + return $this->file . $this->extension; + } +} diff --git a/src/pharext/Task/PharRename.php b/src/pharext/Task/PharRename.php new file mode 100644 index 0000000..19094c1 --- /dev/null +++ b/src/pharext/Task/PharRename.php @@ -0,0 +1,54 @@ +phar = $phar; + $this->dest = $dest; + $this->name = $name; + } + + /** + * @param bool $verbose + * @return string path to renamed phar + * @throws \pharext\Exception + */ + public function run($verbose = false) { + $extension = substr(strstr($this->phar, "-pharext.phar"), 8); + $name = sprintf("%s/%s.ext%s", $this->dest, $this->name, $extension); + + if (!rename($this->phar, $name)) { + throw new Exception; + } + + return $name; + } +} diff --git a/src/pharext/Task/PharSign.php b/src/pharext/Task/PharSign.php new file mode 100644 index 0000000..739c587 --- /dev/null +++ b/src/pharext/Task/PharSign.php @@ -0,0 +1,48 @@ +phar = $phar; + } else { + $this->phar = new Phar($phar); + } + $this->pkey = new Openssl\PrivateKey($pkey, $pass); + } + + /** + * @param bool $verbose + * @return \pharext\Openssl\PrivateKey + */ + public function run($verbose = false) { + $this->pkey->sign($this->phar); + return $this->pkey; + } +} diff --git a/src/pharext/Task/Phpize.php b/src/pharext/Task/Phpize.php new file mode 100644 index 0000000..ab70534 --- /dev/null +++ b/src/pharext/Task/Phpize.php @@ -0,0 +1,55 @@ +cwd = $cwd; + $cmd = $common_name . "ize"; + if (isset($prefix)) { + $cmd = $prefix . "/bin/" . $cmd; + } + $this->phpize = $cmd; + } + + /** + * @param bool $verbose + * @throws \pharext\Exception + */ + public function run($verbose = false) { + $pwd = getcwd(); + if (!chdir($this->cwd)) { + throw new Exception; + } + try { + $cmd = new ExecCmd($this->phpize, $verbose); + $cmd->run(); + } finally { + chdir($pwd); + } + } +} diff --git a/src/pharext/Task/StreamFetch.php b/src/pharext/Task/StreamFetch.php new file mode 100644 index 0000000..4f090ef --- /dev/null +++ b/src/pharext/Task/StreamFetch.php @@ -0,0 +1,75 @@ +source = $source; + $this->progress = $progress; + } + + private function createStreamContext() { + $progress = $this->progress; + + return stream_context_create([],["notification" => function($notification, $severity, $message, $code, $bytes_cur, $bytes_max) use($progress) { + switch ($notification) { + case STREAM_NOTIFY_CONNECT: + $progress(0); + break; + case STREAM_NOTIFY_PROGRESS: + $progress($bytes_max ? $bytes_cur/$bytes_max : .5); + break; + case STREAM_NOTIFY_COMPLETED: + /* this is not generated, why? */ + $progress(1); + break; + } + }]); + } + + /** + * @param bool $verbose + * @return \pharext\Task\Tempfile + * @throws \pharext\Exception + */ + public function run($verbose = false) { + $context = $this->createStreamContext(); + + if (!$remote = fopen($this->source, "r", false, $context)) { + throw new Exception; + } + + $local = new Tempfile("remote"); + if (!stream_copy_to_stream($remote, $local->getStream())) { + throw new Exception; + } + $local->closeStream(); + + /* STREAM_NOTIFY_COMPLETED is not generated, see above */ + call_user_func($this->progress, 1); + + return $local; + } +} diff --git a/src/pharext/Tempdir.php b/src/pharext/Tempdir.php index 2de174d..eb59263 100644 --- a/src/pharext/Tempdir.php +++ b/src/pharext/Tempdir.php @@ -2,16 +2,19 @@ namespace pharext; +/** + * Create a temporary directory + */ class Tempdir extends \SplFileInfo { - private $dir; - + /** + * @param string $prefix prefix to uniqid() + * @throws \pharext\Exception + */ public function __construct($prefix) { - $temp = sprintf("%s/%s", sys_get_temp_dir(), uniqid($prefix)); - if (!is_dir($temp)) { - if (!mkdir($temp, 0700, true)) { - throw new Exception("Could not create tempdir: ".error_get_last()["message"]); - } + $temp = new Tempname($prefix); + if (!is_dir($temp) && !mkdir($temp, 0700, true)) { + throw new Exception("Could not create tempdir: ".error_get_last()["message"]); } parent::__construct($temp); } diff --git a/src/pharext/Tempfile.php b/src/pharext/Tempfile.php index c890cd9..e720551 100644 --- a/src/pharext/Tempfile.php +++ b/src/pharext/Tempfile.php @@ -2,38 +2,56 @@ namespace pharext; +/** + * Create a new temporary file + */ class Tempfile extends \SplFileInfo { + /** + * @var resource + */ private $handle; - - function __construct($prefix) { + + /** + * @param string $prefix uniqid() prefix + * @param string $suffix e.g. file extension + * @throws \pharext\Exception + */ + public function __construct($prefix, $suffix = ".tmp") { $tries = 0; - /* PharData needs a dot in the filename, sure */ - $temp = sys_get_temp_dir() . "/"; - $omask = umask(077); do { - $path = $temp.uniqid($prefix).".tmp"; + $path = new Tempname($prefix, $suffix); $this->handle = fopen($path, "x"); } while (!is_resource($this->handle) && $tries++ < 10); umask($omask); if (!is_resource($this->handle)) { - throw new \Exception("Could not create temporary file"); + throw new Exception("Could not create temporary file"); } parent::__construct($path); } - - function __destruct() { + + /** + * Unlink the file + */ + public function __destruct() { @unlink($this->getPathname()); } - - function closeStream() { + + /** + * Close the stream + */ + public function closeStream() { fclose($this->handle); } - function getStream() { + /** + * Retrieve the stream resource + * @return resource + */ + public function getStream() { return $this->handle; } } diff --git a/src/pharext/Tempname.php b/src/pharext/Tempname.php new file mode 100644 index 0000000..b5e7520 --- /dev/null +++ b/src/pharext/Tempname.php @@ -0,0 +1,29 @@ +name = sys_get_temp_dir() . "/" . uniqid($prefix) . $suffix; + } + + /** + * @return string + */ + public function __toString() { + return (string) $this->name; + } +} diff --git a/src/pharext_install.tpl.php b/src/pharext_install.tpl.php index 04d84b2..f7a356d 100644 --- a/src/pharext_install.tpl.php +++ b/src/pharext_install.tpl.php @@ -1,7 +1,7 @@ /** - * Generated by pharext v at . + * Generated by pharext v at . */ namespace pharext; -- 2.30.2