groups/scripts/release-manifest.php
Marton Kiss 099741a4a8 Fix release manifest element order issue
Production releases must be insterted into first position of releases
element in the manifest xml. This bug prevented the proper execution
of drush dl tool, and failed to update the site to the latest version.

Change-Id: I1d913c3ca90e00d6f9801ca2e0039df29730603e
2014-11-02 15:30:54 +01:00

324 lines
9.3 KiB
PHP
Executable File

#!/usr/bin/env php
<?php
/**
* @file
* Drupal manifest file creation tool. The manifest file represents
* the release history of a Drupal project or module. This tool
* helps to build a custom manifest file as the part of the OpenStack
* CI process.
*/
// initial config settings
$config = array(
'manifest-url' => '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 = '<?xml version="1.0" encoding="utf-8"?>
<project xmlns:dc="http://purl.org/dc/elements/1.1/">
<title>Groups Portal</title>
<short_name>groups</short_name>
<type>project_distribution</type>
<api_version>7.x</api_version>
<recommended_major>0</recommended_major>
<supported_majors>0</supported_majors>
<default_major>0</default_major>
<project_status>published</project_status>
<link>https://drupal.org/project/groups</link>
<terms>
<term>
<name>Projects</name>
<value>Distributions</value>
</term>
</terms>
<releases>
</releases>
</project>';
// 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<major>\d+).((?P<patch>\d+)|x-(?P<extra>dev))$/';
$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);
}