diff --git a/setup/cli/modules/deploy.php b/setup/cli/modules/deploy.php index 701ab0c677984737af8afff249810e28dcc89ca2..c5100a6a2e74dade52ea39069d8f55022d067579 100644 --- a/setup/cli/modules/deploy.php +++ b/setup/cli/modules/deploy.php @@ -180,7 +180,7 @@ class Deployment extends Unpacker { $X = implode(' --exclude-per-directory=', $patterns); chdir($source.$local); if (!($files = proc_open( - "git ls-files -s --full-name --exclude-standard --exclude-per-directory=$X -- .", + "git ls-files -zs --exclude-standard --exclude-per-directory=$X -- .", array(1 => array('pipe', 'w')), $pipes ))) { @@ -189,9 +189,9 @@ class Deployment extends Unpacker { $dryrun = $this->getOption('dry-run', false); $verbose = $this->getOption('verbose') || $dryrun; - while ($line = stream_get_line($pipes[1], 255, PHP_EOL)) { + while ($line = stream_get_line($pipes[1], 255, "\x00")) { list($mode, $hash, , $path) = preg_split('/\s+/', $line); - $src = $source.$path; + $src = $source.$local.$path; if ($this->exclude($exclude, $src)) continue; if (!$this->isChanged($src, $hash)) diff --git a/setup/cli/modules/package.php b/setup/cli/modules/package.php new file mode 100644 index 0000000000000000000000000000000000000000..77a86a9446382cd010b9972bcf6e854ee9557367 --- /dev/null +++ b/setup/cli/modules/package.php @@ -0,0 +1,121 @@ +<?php +require_once "deploy.php"; + +class Packager extends Deployment { + var $prologue = "Creates an osTicket distribution ZIP file"; + + var $epilog = + "Packaging is based on the `deploy` and `test` cli apps. After + running the tests, the system is deployed into a temporary staging + area using the files tracked in git if supported. Afterwards, the + staging area is packaged as a ZIP file."; + + var $options = array( + 'format' => array('-F','--format', + 'default'=>'zip', + 'help'=>'Output the package in this format. Supported formats are + "zip" (the default), and "targz"' + ), + 'skip-test' => array('-S','--skip-test', + 'action'=>'store_true', 'default'=>false, + 'help'=>'Skip regression testing (NOT RECOMMENDED)', + ), + ); + var $arguments = array(); + + function __construct() { + // Skip options added to the deploy — options and arguments are + // forced in this module + call_user_func_array(array('Module', '__construct'), func_get_args()); + } + + function run($args, $options) { + // Set some forced args and options + $temp = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + $stage_path = $temp . 'osticket' + . substr(md5('osticket-stage'.getmypid().getcwd()), -8); + $args['install-path'] = $stage_path . '/upload'; + + // Deployment will auto-create the staging area + + // Ensure that the staging path is cleaned up on exit + register_shutdown_function(function() use ($stage_path) { + $delTree = function($dir) use (&$delTree) { + $files = array_diff(scandir($dir), array('.','..')); + foreach ($files as $file) { + (is_dir("$dir/$file")) ? $delTree("$dir/$file") : unlink("$dir/$file"); + } + return rmdir($dir); + }; + return $delTree($stage_path); + }); + + $options['setup'] = true; + $options['git'] = true; + $options['verbose'] = true; + + $options['clean'] = false; + $options['dry-run'] = false; + $options['include'] = false; + + $this->_args = $args; + $this->_options = $options; + + // TODO: Run the testing applet first + $root = $this->find_root_folder(); + if (!$this->getOption('skip-test') && $this->run_tests($root) > 0) + $this->fail("Regression tests failed. Cowardly refusing to package"); + + // Run the deployment + // NOTE: The deployment will change the working directory + parent::run($args, $options); + + // Deploy the `setup/scripts` folder to `/scripts` + $root = $this->source; + Unpacker::unpackage("$root/setup/scripts/{,.}*", "$stage_path/scripts", -1); + + // Package up the staging area + $version = exec('git describe'); + switch (strtolower($this->getOption('format'))) { + case 'zip': + default: + $this->packageZip("$root/osTicket-$version.zip", $stage_path); + } + } + + function run_tests($root) { + return (require "$root/setup/test/run-tests.php"); + } + + function packageZip($name, $path) { + $zip = new ZipArchive(); + if (!$zip->open($name, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) + return false; + + $php56 = version_compare(phpversion(), '5.6.0', '>'); + $addFiles = function($dir) use (&$addFiles, $zip, $path, $php56) { + $files = array_diff(scandir($dir), array('.','..')); + $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + foreach ($files as $file) { + $full = "$dir/$file"; + $local = str_replace($path, '', $full); + if (is_dir($full)) + $addFiles($full); + else + // XXX: AddFile() will keep the file open and run + // out of OS open file handles + $zip->addFromString($local, file_get_contents($full)); + // This only works on PHP >= v5.6 + if ($php56) { + // Set the Unix mode of the file + $stat = stat($full); + $zip->setExternalAttributesName($local, ZipArchive::OPSYS_UNIX, $stat['mode']); + } + } + }; + $addFiles($path); + return $zip->close(); + + } +} +Module::register('package', 'Packager'); diff --git a/setup/cli/package.php b/setup/cli/package.php deleted file mode 100755 index b758f63c6fa3e40f3fab39b5ff8e56c0582cef28..0000000000000000000000000000000000000000 --- a/setup/cli/package.php +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/env php -<?php - -if (php_sapi_name() != 'cli') - die("Only command-line packaging is supported"); - -$stage_folder = "stage"; -$stage_path = dirname(__file__) . '/' . $stage_folder; - -function get_osticket_root_path() { - # Hop up to the root folder - $start = dirname(__file__); - for (;;) { - if (file_exists($start . '/main.inc.php')) break; - $start .= '/..'; - } - return realpath($start); -} - -function run_tests($root) { - return (require "$root/setup/test/run-tests.php"); -} - -# Check PHP syntax across all php files -function glob_recursive($pattern, $flags = 0) { - $files = glob($pattern, $flags); - foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) { - $files = array_merge($files, - glob_recursive($dir.'/'.basename($pattern), $flags)); - } - return $files; -} - -$root = get_osticket_root_path(); - -function exclude($pattern, $match) { - if (is_array($pattern)) { - foreach ($pattern as $p) - if (fnmatch($p, $match)) - return true; - } - else - return fnmatch($pattern, $match); - return false; -} - -function package($pattern, $destination, $recurse=false, $exclude=false) { - global $root, $stage_path; - $search = $root . '/' . $pattern; - echo "Packaging " . $search . "\n"; - foreach (glob($search, GLOB_BRACE|GLOB_NOSORT) as $file) { - if (is_file($file)) { - if ($exclude && exclude($exclude, $file)) - continue; - if (!is_dir("$stage_path/$destination")) - mkdir("$stage_path/$destination", 0777, true); - copy($file, $stage_path . '/' . $destination . '/' . basename($file)); - } - } - if ($recurse) { - foreach (glob(dirname($search).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) { - if ($exclude && exclude($exclude, $dir)) - continue; - package(dirname($pattern).'/'.basename($dir).'/'.basename($pattern), - $destination.'/'.basename($dir), - $recurse-1, $exclude); - } - } -} - -# Run tests before continuing -if (run_tests($root) > 0) - die("Regression tests failed. Cowardly refusing to package\n"); - -# Create the stage folder for the install files -if (!is_dir($stage_path)) - mkdir($stage_path); -else { - $dirs = array(); - foreach (glob_recursive($stage_path . '/*') as $file) - if (is_dir($file)) - $dirs[] = $file; - else - unlink($file); - sort($dirs); - foreach (array_reverse($dirs) as $dir) - rmdir($dir); -} - -# Source code goes into 'upload' -mkdir($stage_path . '/upload'); - -# Load the root directory files -package("*.php", 'upload/'); -package("web.config", 'upload/'); - -# Load the client interface -foreach (array('assets','css','images','js') as $dir) - package("$dir/*", "upload/$dir", -1, "*less"); - -# Load API and pages -package('api/{,.}*', 'upload/api'); -package('pages/{,.}*', 'upload/pages'); - -# Load the knowledgebase -package("kb/*.php", "upload/kb"); - -# Load the staff interface -package("scp/*.php", "upload/scp/", -1); -foreach (array('css','images','js') as $dir) - package("scp/$dir/*", "upload/scp/$dir", -1); - -# Load in the scripts -mkdir("$stage_path/scripts/"); -package("setup/scripts/*", "scripts/", -1, "*stage"); - -# Load the heart of the system -package("include/{,.}*", "upload/include", -1, array('*ost-config.php', '*.sw[a-z]','plugins/*')); - -# Include the installer -package("setup/*.{php,txt,html}", "upload/setup", -1, array("*scripts","*test","*stage")); -foreach (array('css','images','js') as $dir) - package("setup/$dir/*", "upload/setup/$dir", -1); -package("setup/inc/streams/*.sql", "upload/setup/inc/streams", -1); - -# Load the license and documentation -package("*.{txt,md}", ""); - -#Rename markdown as text TODO: Do html version before rename. -if(($mds = glob("$stage_path/*.md"))) { - foreach($mds as $md) - rename($md, preg_replace('/\.md$/', '.txt', $md)); -} - -# Make an archive of the stage folder -$version = exec('git describe'); -$hash = exec('git rev-parse HEAD'); -$short = substr($hash, 0, 7); - -$pwd = getcwd(); -chdir($stage_path); - -// Replace THIS_VERSION in the stage/ folder - -shell_exec("sed -ri -e \" - s/( *)define\('THIS_VERSION'.*/\\1define('THIS_VERSION', '$version');/ - s/( *)define\('GIT_VERSION'.*/\\1define('GIT_VERSION', '$short');/ - \" upload/bootstrap.php"); -shell_exec("find upload -name '*.php' -print0 | xargs -0 sed -i -e ' - s:<script\(.*\) src=\"\(.*\).js\"></script>:<script\\1 src=\"\\2.js?$short\"></script>: - s:<link\(.*\) href=\"\(.*\)\.css\"\(.*\)*/*>:<link\\1 href=\"\\2.css?$short\"\\3>: - '"); -shell_exec("find upload -name '*.php' -print0 | xargs -0 sed -i -e \" - s/\( *\)ini_set( *'display_errors'[^])]*);/\\1ini_set('display_errors', 0);/ - s/\( *\)ini_set( *'display_startup_errors'[^])]*);/\\1ini_set('display_startup_errors', 0);/ - \""); - -shell_exec("tar cjf '$pwd/osTicket-$version.tar.bz2' *"); -shell_exec("zip -r '$pwd/osTicket-$version.zip' *"); - -chdir($pwd); -?> diff --git a/setup/doc/package.md b/setup/doc/package.md new file mode 100644 index 0000000000000000000000000000000000000000..5570bb60931b910e3281e29ee5d326c938f6eae7 --- /dev/null +++ b/setup/doc/package.md @@ -0,0 +1,21 @@ +Creating an osTicket distribution +================================= +osTicket is packaged using an included script. Access to the packaging +system is provided via the `manage.php` CLI app. + +The packaging system extends the deployment system in order to remove +similar code between the two processes. Where possible, the files reported +to be part of the git repository are used in the packaging process, which +removes the possibility of experimental files and those ignored by git from +being added to the distribution. + +More information is available via the automated help output. + + php setup/cli/manage.php package --help + +Creating the ZIP file +--------------------- +To package the system using the defaults (as a ZIP file), just invoke the +packager with no other options. + + php setup/cli/manage.php package