release v2.0.1
[pharext/pharext] / src / pharext / Packager.php
1 <?php
2
3 namespace pharext;
4
5 use Phar;
6 use pharext\Cli\Args as CliArgs;
7 use pharext\Cli\Command as CliCommand;
8
9 /**
10 * The extension packaging command executed by bin/pharext
11 */
12 class Packager implements Command
13 {
14 use CliCommand;
15
16 /**
17 * Extension source directory
18 * @var pharext\SourceDir
19 */
20 private $source;
21
22 /**
23 * Create the command
24 */
25 public function __construct() {
26 $this->args = new CliArgs([
27 ["h", "help", "Display this help",
28 CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT],
29 ["v", "verbose", "More output",
30 CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG],
31 ["q", "quiet", "Less output",
32 CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG],
33 ["n", "name", "Extension name",
34 CliArgs::REQUIRED|CliArgs::SINGLE|CliArgs::REQARG],
35 ["r", "release", "Extension release version",
36 CliArgs::REQUIRED|CliArgs::SINGLE|CliArgs::REQARG],
37 ["s", "source", "Extension source directory",
38 CliArgs::REQUIRED|CliArgs::SINGLE|CliArgs::REQARG],
39 ["g", "git", "Use `git ls-tree` to determine file list",
40 CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG],
41 ["p", "pecl", "Use PECL package.xml to determine file list, name and release",
42 CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG],
43 ["d", "dest", "Destination directory",
44 CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG,
45 "."],
46 ["z", "gzip", "Create additional PHAR compressed with gzip",
47 CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG],
48 ["Z", "bzip", "Create additional PHAR compressed with bzip",
49 CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG],
50 ["S", "sign", "Sign the PHAR with a private key",
51 CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG],
52 [null, "signature", "Dump signature",
53 CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT],
54 ]);
55 }
56
57 /**
58 * @inheritdoc
59 * @see \pharext\Command::run()
60 */
61 public function run($argc, array $argv) {
62 $errs = [];
63 $prog = array_shift($argv);
64 foreach ($this->args->parse(--$argc, $argv) as $error) {
65 $errs[] = $error;
66 }
67
68 if ($this->args["help"]) {
69 $this->header();
70 $this->help($prog);
71 exit;
72 }
73 if ($this->args["signature"]) {
74 exit($this->signature($prog));
75 }
76
77 try {
78 if ($this->args["source"]) {
79 if ($this->args["pecl"]) {
80 $this->source = new SourceDir\Pecl($this, $this->args["source"]);
81 } elseif ($this->args["git"]) {
82 $this->source = new SourceDir\Git($this, $this->args["source"]);
83 } else {
84 $this->source = new SourceDir\Pharext($this, $this->args["source"]);
85 }
86 }
87 } catch (\Exception $e) {
88 $errs[] = $e->getMessage();
89 }
90
91 foreach ($this->args->validate() as $error) {
92 $errs[] = $error;
93 }
94
95 if ($errs) {
96 if (!$this->args["quiet"]) {
97 $this->header();
98 }
99 foreach ($errs as $err) {
100 $this->error("%s\n", $err);
101 }
102 printf("\n");
103 if (!$this->args["quiet"]) {
104 $this->help($prog);
105 }
106 exit(1);
107 }
108
109 $this->createPackage();
110 }
111
112 function signature($prog) {
113 try {
114 $sig = (new Phar(Phar::running(false)))->getSignature();
115 printf("%s signature of %s\n%s", $sig["hash_type"], $prog,
116 chunk_split($sig["hash"], 64, "\n"));
117 return 0;
118 } catch (\Exception $e) {
119 $this->error("%s\n", $e->getMessage());
120 return 2;
121 }
122 }
123
124 /**
125 * Traverses all pharext source files to bundle
126 * @return Generator
127 */
128 private function bundle() {
129 $rdi = new \RecursiveDirectoryIterator(__DIR__);
130 $rii = new \RecursiveIteratorIterator($rdi);
131 for ($rii->rewind(); $rii->valid(); $rii->next()) {
132 yield "pharext/". $rii->getSubPathname() => $rii->key();
133
134 }
135 }
136
137 private function askpass($prompt = "Password:") {
138 system("stty -echo", $retval);
139 if ($retval) {
140 $this->error("Could not disable echo on the terminal\n");
141 }
142 printf("%s ", $prompt);
143 $pass = fgets(STDIN, 1024);
144 system("stty echo");
145 if (substr($pass, -1) == "\n") {
146 $pass = substr($pass, 0, -1);
147 }
148 return $pass;
149 }
150
151 /**
152 * Creates the extension phar
153 */
154 private function createPackage() {
155 $pkguniq = uniqid();
156 $pkgtemp = $this->tempname($pkguniq, "phar");
157 $pkgdesc = "{$this->args->name}-{$this->args->release}";
158
159 $this->info("Creating phar %s ...%s", $pkgtemp, $this->args->verbose ? "\n" : " ");
160 try {
161 $package = new Phar($pkgtemp);
162
163 if ($this->args->sign) {
164 $this->info("\nUsing private key to sign phar ... \n");
165 $privkey = new Openssl\PrivateKey(realpath($this->args->sign), $this->askpass());
166 $privkey->sign($package);
167 }
168
169 $package->startBuffering();
170 $package->buildFromIterator($this->source, $this->source->getBaseDir());
171 $package->buildFromIterator($this->bundle(__DIR__));
172 $package->addFile(__DIR__."/../pharext_installer.php", "pharext_installer.php");
173 $package->setDefaultStub("pharext_installer.php");
174 $package->setStub("#!/usr/bin/php -dphar.readonly=1\n".$package->getStub());
175 $package->stopBuffering();
176
177 if (!chmod($pkgtemp, 0777)) {
178 $this->error(null);
179 } elseif ($this->args->verbose) {
180 $this->info("Created executable phar %s\n", $pkgtemp);
181 } else {
182 $this->info("OK\n");
183 }
184 if ($this->args->gzip) {
185 $this->info("Compressing with gzip ... ");
186 try {
187 $package->compress(Phar::GZ)
188 ->setDefaultStub("pharext_installer.php");
189 $this->info("OK\n");
190 } catch (\Exception $e) {
191 $this->error("%s\n", $e->getMessage());
192 }
193 }
194 if ($this->args->bzip) {
195 $this->info("Compressing with bzip ... ");
196 try {
197 $package->compress(Phar::BZ2)
198 ->setDefaultStub("pharext_installer.php");
199 $this->info("OK\n");
200 } catch (\Exception $e) {
201 $this->error("%s\n", $e->getMessage());
202 }
203 }
204
205 unset($package);
206 } catch (\Exception $e) {
207 $this->error("%s\n", $e->getMessage());
208 exit(4);
209 }
210
211 foreach (glob($pkgtemp."*") as $pkgtemp) {
212 $pkgfile = str_replace($pkguniq, "{$pkgdesc}.ext", $pkgtemp);
213 $pkgname = $this->args->dest ."/". basename($pkgfile);
214 $this->info("Finalizing %s ... ", $pkgname);
215 if (!rename($pkgtemp, $pkgname)) {
216 $this->error(null);
217 exit(5);
218 }
219 $this->info("OK\n");
220 if ($this->args->sign && isset($privkey)) {
221 $keyname = $this->args->dest ."/". basename($pkgfile) . ".pubkey";
222 $this->info("Public Key %s ... ", $keyname);
223 try {
224 $privkey->exportPublicKey($keyname);
225 $this->info("OK\n");
226 } catch (\Exception $e) {
227 $this->error("%s", $e->getMessage());
228 }
229 }
230 }
231 }
232 }