#!/usr/bin/env php 'http://tarballs.openstack.org/groups/7.x', 'debug' => FALSE, 'verbose' => FALSE, 'outfile' => 'manifest.xml', 'help' => FALSE, ); // command line argument schema definition $arg_schema = array( 'version' => array( '#required' => TRUE, '#description' => 'release version (7.x-N.N or 7.x-N.x-dev)', ), 'md5' => array( '#required' => TRUE, '#description' => 'basename of release tar.gz file (eg. groups-N.N.tar.gz)', ), 'releasetar' => array( '#required' => TRUE, '#description' => 'md5 hash of release tar.gz file', ), 'debug' => array( '#required' => FALSE, '#description' => 'enable debug messages, defaults to FALSE', ), 'verbose' => array( '#required' => FALSE, '#description' => 'enable verbouse output, defaults to FALSE', ), 'manifest-url' => array( '#required' => FALSE, '#description' => 'url of original manifest file', ), 'outfile' => array( '#required' => FALSE, '#description' => 'write manifest to this file, defaults to manifest.xml', ), 'help' => array( '#required' => FALSE, '#description' => 'Show the help', ), ); // manifest template $manifest_template = ' Groups Portal groups project_distribution 7.x 0 0 0 published https://drupal.org/project/groups Projects Distributions '; // log level constants define('_LOG_DEBUG_', 'DEBUG'); define('_LOG_INFO_', 'INFO'); define('_LOG_ERROR_', 'ERROR'); /** * Write a log message depending on configuration settings. * * @param string $type log level (debug, info, error) * @param string $message log message */ function write_log($type, $message) { global $config; if (($type == 'DEBUG') && ($config['debug'] == FALSE)) { // skip if debug mode disabled return; } if (($type == 'INFO') && ($config['verbose'] == FALSE)) { // skip if verbose disabled return; } echo sprintf("%s [%s] %s\n", date('c'), $type, $message); } /** * Convert an array into SimpleXML recursively. */ function __append_elements($item, &$parent) { foreach ($item as $k => $v) { if (is_array($v)) { $element = $parent->addChild($k); __append_elements($v, $element); } else { $parent->addChild($k, $v); } } } /** * Convert a SimpleXML into an array recursively. */ function simple_xml_to_array($xml){ $array = (array)$xml; foreach ($array as $key => $value){ if($value instanceof SimpleXMLElement) { $array[$key] = simple_xml_to_array($value); } else { $array[$key] = $value; } } return $array; } /** * Reorder release elements into a descending list by version_patch. * Fix drush download issues. */ function order_release_elements($xml) { $prod_releases = array(); $dev_releases = array(); $releases = $xml->xpath('releases/release'); foreach ($releases as $i => $release) { if (isset($release->version_patch)) { $prod_releases[(int)$release->version_patch] = simple_xml_to_array($release); } else { $dev_releases[(string)$release->version_extra] = simple_xml_to_array($release); } } unset($xml->releases); $releases = $xml->addChild('releases'); // reverse-order prod releases here krsort($prod_releases); foreach ($prod_releases as $item) { $release = $releases->addChild('release'); __append_elements($item, $release); } foreach ($dev_releases as $item) { $release = $releases->addChild('release'); __append_elements($item, $release); } return $xml; } /** * Validate and decode a Drupal format version string * into a key-value array. * * 7.x-1.0 converted to: * array( * 'major' => 1, * 'patch' => 0, * ) * * 7.x-1.x-dev converted to: * array( * 'major' => 1, * 'extra' => 'dev', * ) * * @param string $version version string * @return array decoded version as key value array */ function match_version($version) { $pattern = '/^7.x-(?P\d+).((?P\d+)|x-(?Pdev))$/'; $matches = array(); if (preg_match($pattern, $version, $matches)) { foreach ($matches as $key => $value) { if ((is_int($key)) || ($value == NULL)) { unset($matches[$key]); } } } else { throw new Exception(sprintf('Invalid version number format: %s', $version)); } return $matches; } /** * Insert or update a release element in project xml. * * @param class $xml project xml as a simplexml object. * @param string $version release version * @param string $releaseTar basename of release tar.gz file * @param string $md5 md5 hash of release tar.gz file * @param string $fileSize file size of release (optional) * @param string $releaseDate release date of the release (optional) */ function append_release($xml, $version, $releaseTar, $md5, $fileSize = NULL, $releaseDate = NULL) { $downloadUrl = 'http://tarballs.openstack.org/groups/'.$releaseTar; // remove previous release entry with same version list($element) = $xml->xpath('/project/releases/release/version[. = "'.$version.'"]/parent::*'); if (!empty($element)) { unset($element[0]); $verb = 'Override'; } else { $verb = 'Insert'; } write_log(_LOG_INFO_, sprintf('%s a release element [version=%s, releaseTar=%s, md5=%s]', $verb, $version, $releaseTar, $md5)); // add release elements $release = $xml->releases->addChild('release'); $release->addChild('name', 'groups '.$version); $release->addChild('version', $version); $release->addChild('tag', $version); $release->addChild('status', 'published'); $release->addChild('download_link', $downloadUrl); $release->addChild('mdhash', $md5); // append version $v = match_version($version); if (empty($v)) { throw new Exception('Invalid version format.'); } foreach ($v as $key => $value) { $release->addChild('version_'.$key, $value); } $files = $release->addChild('files'); $file = $files->addChild('file'); $file->addChild('url', $downloadUrl); $file->addChild('archive_type', 'tar.gz'); $file->addChild('variant', 'full'); $file->addChild('md5', $md5); } /** * Show the help and construct parameter list by * argument schema. * * @param array $arg_schema argument schema */ function print_help($arg_schema) { echo "Generate Drupal manifest file to represent a project release history.\n\n"; echo "Example:\n"; echo " release-manifest.php --version=7.x-1.0 --releasetar=groups-1.0.tar.gz --md5=c59611415cea4bc6397b1351b2b36b7c\n\n"; echo "Options:\n"; foreach ($arg_schema as $key => $value) { echo sprintf(" --%-16s %s\n", $key, $value['#description']); } echo "\n"; } /** * Parse and validate command line parameters based on * predefined argument schema. * * @param array $argv command line arguments * @param array $arg_schema argument schema * @return array parsed parameters */ function get_cli_parameters($argv, $arg_schema) { $params = array(); // parse cli arguments foreach ($argv as $arg) { if (strpos($arg, '--') !== false) { list($key, $value) = explode("=",$arg); $key = substr($key, 2); if (isset($arg_schema[$key]) == FALSE) { print_help($arg_schema); throw new Exception(sprintf('Invalid command line argument: %s', $key)); } if (isset($key)) { $params[$key] = isset($value) ? $value : TRUE; } } } if (empty($params['help'])) { // check required parameters foreach ($arg_schema as $key => $value) { if (($value['#required']) && (empty($params[$key]))) { print_help($arg_schema); throw new Exception(sprintf('Mandatory parameter %s missing.', $key)); } } } return $params; } try { // parse cli arguments and merge with config $params = get_cli_parameters($argv, $arg_schema); $config = array_replace($config, $params); if ($config['help']) { print_help($arg_schema); exit(0); } if ($config['debug']) { write_log(_LOG_DEBUG_, 'Command line args:'); foreach ($config as $key => $value) { write_log(_LOG_DEBUG_, sprintf(' %-16s = %s', $key, (string)$value)); } } // load original manifest $xml = @simplexml_load_file($config['manifest-url']); if (!$xml) { write_log(_LOG_INFO_, 'Create a new manifest file, failed to fetch from remote url.'); $xml = simplexml_load_string($manifest_template); } append_release($xml, $params['version'], $params['releasetar'], $params['md5']); // reorder xml here $xml = order_release_elements($xml); $xml_content = $xml->asXML(); write_log(_LOG_DEBUG_, sprintf("Generated manifest:\n %s", $xml_content)); file_put_contents($config['outfile'], $xml_content); } catch (Exception $e) { write_log(_LOG_ERROR_, $e->getMessage()); exit(1); }