Initial commit
This commit is contained in:
commit
fd5b3ddb8f
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/composer.lock
|
||||
/vendor
|
87
README.md
Normal file
87
README.md
Normal file
@ -0,0 +1,87 @@
|
||||
PharLite: Easy PHAR generator
|
||||
=============================
|
||||
|
||||
Make sure PHP can write PHARs in your `php.ini` before trying anything else.
|
||||
|
||||
|
||||
`composer.json` options
|
||||
-----------------------
|
||||
|
||||
{
|
||||
...
|
||||
"extra": {
|
||||
"phar": {
|
||||
"include": [ ... ],
|
||||
"exclude": [ ... ],
|
||||
"index": "web/index.php",
|
||||
"output": "pharname.phar",
|
||||
"strip": "none",
|
||||
"metadata": { ... },
|
||||
"preload": [ ... ],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
### `include` and `exclude`
|
||||
|
||||
Specify additional files or paths to include or exclude.
|
||||
|
||||
### `index`: Create a web phar
|
||||
|
||||
Specifying this option will automatically opt to create a web phar. The value should
|
||||
point to a valid index file or router.
|
||||
|
||||
### `output`: Output filename
|
||||
|
||||
Defines the output filename. Defaults to `output.phar`
|
||||
|
||||
### `strip`: Trim file sizes
|
||||
|
||||
Valid values are `none`, `all` or an array of patterns to match.
|
||||
|
||||
"strip": "all" Strip all files
|
||||
"strip": "none" Include everything as is (default)
|
||||
"strip": [ "src/*.php" ] All *.php in src/ and subdirectories
|
||||
|
||||
Stripping will remove comments and whitepace in files, which makes them smaller
|
||||
but can confuse code that dynamically parses f.ex. docblock comments for routing
|
||||
info etc.
|
||||
|
||||
### `metadata`: Phar metadata
|
||||
|
||||
Define additional metadata to write to the phar. The following keys are automatically
|
||||
written:
|
||||
|
||||
| Key | Type | Description |
|
||||
|:----------------------|:---------:|:-------------------------------------------
|
||||
| `phar.generator` | string | The string `PharLite/x.y (PHP/x.y)`
|
||||
| `phar.type` | string | One of `app`, `library` or `web`
|
||||
| `package.name` | string | The composer package name
|
||||
| `package.version` | string | The composer package version
|
||||
|
||||
### `preload`: Files to preload
|
||||
|
||||
If defined, this should be an array of files to include after `composer.json` in the
|
||||
stub.
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
To build a phar from a composer project, just run `pharlite`. It will build one of
|
||||
the following:
|
||||
|
||||
* **Library phar** - A single file containing a composer library, complete with
|
||||
autoloaders and all that magic.
|
||||
* **Application phar** - An executable file containing an application and its
|
||||
dependencies.
|
||||
* **Web phar** - A complete website in a single file
|
||||
|
||||
If more than one `bin` is defined in the `composer.json`, the first one will be
|
||||
the default one, but all defined bins will be available by symlinking the phar
|
||||
to the respective names.
|
||||
|
||||
"bin": [ "bin/foo", "bin/bar" ]
|
||||
|
||||
output.phar, foo.phar, foo, hello.phar -> calls bin/foo
|
||||
bar.phar, bar -> calls bin/bar
|
83
bin/pharlite
Executable file
83
bin/pharlite
Executable file
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
require_once __DIR__."/../vendor/autoload.php";
|
||||
|
||||
define("PHARLITE_VERSION", "0.1.x");
|
||||
|
||||
if (posix_isatty(STDOUT)) {
|
||||
define("FMT_ERR", "\e[31;1m");
|
||||
define("FMT_WARN", "\e[33;1m");
|
||||
define("FMT_INFO", "\e[32m");
|
||||
define("FMT_AFTER", "\e[0m");
|
||||
} else {
|
||||
define("FMT_ERR", "");
|
||||
define("FMT_WARN", "");
|
||||
define("FMT_INFO", "");
|
||||
define("FMT_AFTER", "");
|
||||
}
|
||||
|
||||
function print_error($fmt, ...$arg) {
|
||||
fprintf(STDOUT, FMT_ERR.$fmt."ERROR: ".FMT_AFTER.PHP_EOL, ...$arg);
|
||||
}
|
||||
|
||||
function print_warn($fmt, ...$arg) {
|
||||
fprintf(STDOUT, FMT_WARN.$fmt.FMT_AFTER.PHP_EOL, ...$arg);
|
||||
}
|
||||
|
||||
function print_info($fmt, ...$arg) {
|
||||
fprintf(STDOUT, FMT_INFO.$fmt.FMT_AFTER.PHP_EOL, ...$arg);
|
||||
}
|
||||
|
||||
function show_app_usage() {
|
||||
printf("usage\n");
|
||||
}
|
||||
|
||||
function parse_opts() {
|
||||
|
||||
$opts = getopt(
|
||||
"ho:d:i",
|
||||
[
|
||||
"help",
|
||||
"directory:",
|
||||
"dir:",
|
||||
"output:",
|
||||
"install"
|
||||
]
|
||||
);
|
||||
|
||||
$parsed = [];
|
||||
|
||||
foreach ($opts as $option=>$value) {
|
||||
switch ($option) {
|
||||
case 'h':
|
||||
case 'help':
|
||||
show_app_usage();
|
||||
return false;
|
||||
case 'd':
|
||||
case 'dir':
|
||||
case 'directory':
|
||||
$parsed['dir'] = $value;
|
||||
break;
|
||||
case 'o':
|
||||
case 'output':
|
||||
$parsed['output'] = $value;
|
||||
break;
|
||||
case 'i':
|
||||
case 'install':
|
||||
$parsed['install'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
$opts = parse_opts();
|
||||
if (false === parse_opts()) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$path = getcwd();
|
||||
|
||||
$builder = new PharLite\Builder($path, $opts);
|
||||
$builder->build();
|
13
composer.json
Normal file
13
composer.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PharLite\\": "src/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"symfony/finder": "^4.0"
|
||||
},
|
||||
"bin": [
|
||||
"bin/pharlite"
|
||||
]
|
||||
}
|
230
src/Builder.php
Normal file
230
src/Builder.php
Normal file
@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
namespace PharLite;
|
||||
|
||||
class Builder
|
||||
{
|
||||
|
||||
const PHAR_TYPE_APP = "app";
|
||||
const PHAR_TYPE_WEB = "web";
|
||||
const PHAR_TYPE_LIB = "lib";
|
||||
|
||||
/** @var string */
|
||||
protected $path;
|
||||
|
||||
protected $manifest;
|
||||
|
||||
protected $composer;
|
||||
|
||||
protected $output;
|
||||
|
||||
protected $pharType = self::PHAR_TYPE_LIB;
|
||||
|
||||
protected $pharOpts = [];
|
||||
|
||||
protected $index;
|
||||
|
||||
protected $bins;
|
||||
|
||||
private static $default_options = [
|
||||
'install' => false,
|
||||
];
|
||||
|
||||
public function __construct($path=null, array $opts=[])
|
||||
{
|
||||
if (!$path) {
|
||||
$path = getcwd();
|
||||
}
|
||||
|
||||
if (!file_exists($path."/composer.json")) {
|
||||
throw new \RuntimeException("Could not find a composer.json in {$path}");
|
||||
}
|
||||
|
||||
$this->opts = array_merge(
|
||||
self::$default_options,
|
||||
$opts
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function readComposer()
|
||||
{
|
||||
print_info("Parsing composer.json...");
|
||||
|
||||
$json = file_get_contents("composer.json");
|
||||
|
||||
$composer = json_decode($json);
|
||||
|
||||
if (!isset($composer->bin) || !is_array($composer->bin)) {
|
||||
print_warn("No executable defined, building a library phar...");
|
||||
} elseif (count($composer->bin)>1) {
|
||||
print_info("More than one executable defined, using the first one as default");
|
||||
print_info(" → Using executable %s", $composer->bin[0]);
|
||||
$this->bins = $composer->bin;
|
||||
} else {
|
||||
print_info(" → Using executable %s", $composer->bin[0]);
|
||||
$this->bins = $composer->bin;
|
||||
}
|
||||
|
||||
if (isset($composer->extra) && isset($composer->extra->phar)) {
|
||||
print_info(" → Found extra phar configuration");
|
||||
$this->pharOpts = (array)$composer->extra->phar;
|
||||
}
|
||||
|
||||
if ($this->index) {
|
||||
$this->pharType = self::PHAR_TYPE_WEB;
|
||||
$this->output = basename($this->index,".php").".phar";
|
||||
} else {
|
||||
if (isset($composer->bin) && is_array($composer->bin)) {
|
||||
$this->pharType = self::PHAR_TYPE_APP;
|
||||
}
|
||||
$this->output = "output.phar";
|
||||
}
|
||||
print_info("Generating output %s", $this->output);
|
||||
|
||||
$this->composer = $composer;
|
||||
}
|
||||
|
||||
private function installComposer()
|
||||
{
|
||||
print_info("Installing dependencies...");
|
||||
passthru("composer install --no-interaction --optimize-autoloader");
|
||||
}
|
||||
|
||||
private function buildManifest()
|
||||
{
|
||||
print_info("Creating manifest...");
|
||||
|
||||
$manifest = new Manifest($this->path);
|
||||
// Always include the vendor directory
|
||||
print_info(" ← vendor/");
|
||||
$manifest->addDirectory("vendor");
|
||||
// Include everything from the autoload
|
||||
if (isset($this->composer->autoload)) {
|
||||
foreach ((array)$this->composer->autoload as $type=>$autoloaders) {
|
||||
if ($type == "files") {
|
||||
foreach ($autoloaders as $file) {
|
||||
print_info(" ← %s", $file);
|
||||
$manifest->addFile($file);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
foreach ($autoloaders as $prefix=>$path) {
|
||||
print_info(" ← %s", $path);
|
||||
$manifest->addDirectory($path);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (array_key_exists('include', $this->pharOpts)) {
|
||||
foreach ((array)$this->pharOpts['include'] as $path) {
|
||||
print_info(" ← %s", $path);
|
||||
if (substr($path,-1,1)==DIRECTORY_SEPARATOR) {
|
||||
$manifest->addDirectory($path);
|
||||
} else {
|
||||
$manifest->addFile($path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->manifest = $manifest;
|
||||
}
|
||||
|
||||
private function generateBootstrap(\Phar $phar, $type, $index=null, array $bins=[])
|
||||
{
|
||||
print_info("Generating bootstrap stub...");
|
||||
|
||||
if (!$index && count($bins)==0) {
|
||||
$indexFile = '<?php require_once __DIR__."/vendor/autoload.php";';
|
||||
} elseif (count($bins)>1) {
|
||||
$indexFile = '<?php $bin=basename($argv[0],".phar"); switch($bin) {';
|
||||
for ($n = 1; $n<count($bins); $n++) {
|
||||
$indexFile.= 'case '.var_export(basename($bins[$n]),true).': require_once __DIR__."/".'.var_export($bins[$n],true).'; break;';
|
||||
}
|
||||
$indexFile.= 'case '.var_export(basename($bins[0]),true).': default: require_once __DIR__."/".'.var_export($bins[0],true).'; break;';
|
||||
$indexFile.= '}';
|
||||
$index = "bootstrap.php";
|
||||
foreach ($bins as $bin) {
|
||||
$str = file_get_contents($bin);
|
||||
$str = $this->stripShebang($str);
|
||||
$phar->addFromString($bin, $str);
|
||||
}
|
||||
} else {
|
||||
$indexFile = file_get_contents($index);
|
||||
$indexFile = $this->stripShebang($indexFile);
|
||||
$index = "bootstrap.php";
|
||||
}
|
||||
|
||||
$stub = "#!/usr/bin/env php\n".$phar->createDefaultStub($index);
|
||||
|
||||
$phar->addFromString($index, $indexFile);
|
||||
$phar->setStub($stub);
|
||||
|
||||
}
|
||||
|
||||
private function stripShebang($str)
|
||||
{
|
||||
if (strpos($str, '#!') === 0) {
|
||||
if (strpos($str, '#!/usr/bin/env php') === 0) {
|
||||
$str = substr($str,strpos($str, "<?"));
|
||||
}
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
private function writeMetadata(\Phar $phar, array $metadata=[])
|
||||
{
|
||||
$metadata = array_merge([
|
||||
'phar.generator' => "PharLite/".PHARLITE_VERSION." (PHP/".PHP_VERSION.")",
|
||||
'phar.type' => $this->pharType,
|
||||
'package.name' => isset($this->composer->name)?$this->composer->name:null,
|
||||
'package.version' => isset($this->composer->version)?$this->composer->version:null,
|
||||
], $metadata);
|
||||
|
||||
print_info("Writing metadata:\n%s", json_encode($metadata, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT));
|
||||
|
||||
$phar->setMetadata($metadata);
|
||||
}
|
||||
|
||||
public function build()
|
||||
{
|
||||
$this->readComposer();
|
||||
|
||||
if ($this->opts['install']) {
|
||||
$this->installComposer();
|
||||
}
|
||||
|
||||
$this->buildManifest();
|
||||
|
||||
print_info("Adding files...");
|
||||
|
||||
$phar = new \Phar($this->output);
|
||||
|
||||
$total = count($this->manifest);
|
||||
$pcf = 100 / $total;
|
||||
$bw = 40;
|
||||
$pbf = $bw / $total;
|
||||
$index = 0;
|
||||
foreach ($this->manifest as $object) {
|
||||
// printf(" %s: %s\n", $object->getFilename(), $object->getLocalName());
|
||||
$object->addToPhar($phar);
|
||||
if (posix_isatty(STDOUT) && ($index++ % 25 == 0)) {
|
||||
$pc = $pcf * $index;
|
||||
$pb = round($pbf * $index);
|
||||
printf("\r[%s%s] %.1f%%", str_repeat("=",$pb), str_repeat(" ",$bw-$pb), $pc);
|
||||
}
|
||||
}
|
||||
if (posix_isatty(STDOUT)) {
|
||||
printf("\r\e[2K");
|
||||
}
|
||||
|
||||
print_info("Added %d files!", $index);
|
||||
|
||||
$this->generateBootstrap($phar, $this->pharType, $this->index, $this->bins);
|
||||
$this->writeMetadata($phar, (array)(array_key_exists('metadata',$this->pharOpts)?$this->pharOpts['metadata']:[]));
|
||||
|
||||
chmod($this->output, 0777);
|
||||
|
||||
}
|
||||
|
||||
}
|
53
src/Manifest.php
Normal file
53
src/Manifest.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace PharLite;
|
||||
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
|
||||
class Manifest implements \IteratorAggregate, \Countable
|
||||
{
|
||||
/** @var string Root directory of the project being built */
|
||||
protected $root;
|
||||
/** @var ManifestObject[] The included objects */
|
||||
protected $objects = [];
|
||||
|
||||
public function __construct($root)
|
||||
{
|
||||
$this->root = $root;
|
||||
}
|
||||
|
||||
public function addDirectory($path, callable $filter=null)
|
||||
{
|
||||
$path = rtrim($path, DIRECTORY_SEPARATOR);
|
||||
$obj = (new Finder)->files()->in($path);
|
||||
foreach ($obj as $o) {
|
||||
if (is_callable($filter) && (false === $filter($o))) {
|
||||
continue;
|
||||
}
|
||||
$path = $o->getPathname();
|
||||
$this->addFile($path);
|
||||
}
|
||||
}
|
||||
|
||||
public function addFile($file)
|
||||
{
|
||||
if (strpos($file, $this->root) === 0) {
|
||||
$local = substr($file, 0, strlen($this->root));
|
||||
} else {
|
||||
$local = $file;
|
||||
}
|
||||
$this->objects[] = new ManifestObject($file, $local);
|
||||
}
|
||||
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->objects);
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
return count($this->objects);
|
||||
}
|
||||
|
||||
}
|
46
src/ManifestObject.php
Normal file
46
src/ManifestObject.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace PharLite;
|
||||
|
||||
/**
|
||||
* Class representing a single object to be added to a phar
|
||||
*
|
||||
*
|
||||
*/
|
||||
class ManifestObject
|
||||
{
|
||||
protected $filename;
|
||||
|
||||
protected $localname;
|
||||
|
||||
public function __construct($filename, $localname=null)
|
||||
{
|
||||
$this->filename = $filename;
|
||||
$this->localname = $localname;
|
||||
}
|
||||
|
||||
public function getFilename()
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function getLocalName()
|
||||
{
|
||||
return $this->localname;
|
||||
}
|
||||
|
||||
public function addToPhar(\Phar $phar)
|
||||
{
|
||||
$phar->addFile(
|
||||
$this->getFilename(),
|
||||
$this->getLocalName()
|
||||
);
|
||||
}
|
||||
|
||||
public function addFiltered(\Phar $phar, callable $filter)
|
||||
{
|
||||
$body = file_get_contents($this->filename);
|
||||
$body = call_user_func($filter, $body);
|
||||
$phar->addFromString($this->getLocalName(), $body);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user