$project) { $type = (isset($project['type']) && ($project['type'] == 'library')) ? 'libraries' : 'projects'; if ($previous_type != $project['_type']) { $previous_type = $project['_type']; $output[] = '; ' . ucfirst($previous_type) . 's'; } unset($project['_type']); if (!$project && is_string($name)) { $output[] = $type . '[] = "' . $name . '"'; continue; } $base = $type . '[' . $name . ']'; if (isset($project['custom_download'])) { $custom = TRUE; $output[] = '; Please fill the following out. Type may be one of get, git, bzr or svn,'; $output[] = '; and url is the url of the download.'; $output[$base . '[download][type]'] = '""'; $output[$base . '[download][url]'] = '""'; unset($project['custom_download']); } $output = array_merge($output, _drush_make_generate_lines($base, $project)); $output[] = ''; } } $string = ''; foreach ($output as $k => $v) { if (!is_numeric($k)) { $string .= $k . ' = ' . $v; } else { $string .= $v; } $string .= "\n"; } if ($custom) { drush_log(dt('Some of the properties in your makefile will have to be manually edited. Please do that now.'), LogLevel::WARNING); } return $string; } /** * Write a makefile based on data parsed from a previous makefile. * * @param $file * The path to the file to write our generated makefile to, or TRUE to * print to the terminal. * @param $makefile * A makefile on which to base our generated one. */ function make_generate_from_makefile($file, $makefile) { if (!$info = make_parse_info_file($makefile)) { return drush_set_error('MAKE_GENERATE_FAILED_PARSE', dt('Failed to parse makefile :makefile.', array(':makefile' => $makefile))); } $projects = drush_get_option('DRUSH_MAKE_PROJECTS', FALSE); if ($projects === FALSE) { $projects = make_prepare_projects(FALSE, $info); if (isset($projects['contrib'])) { $projects = array_merge($projects['core'], $projects['contrib']); } } $defaults = isset($info['defaults']) ? $info['defaults'] : array(); $core = current($projects); $core = $core['core']; foreach ($projects as $name => $project) { // If a specific revision was requested, do not set the version. if (!isset($project['revision'])) { $projects[$name]['version'] = isset($project['download']['full_version']) ? $project['download']['full_version'] : ''; if ($project['type'] != 'core' && strpos($projects[$name]['version'], $project['core']) === 0) { $projects[$name]['version'] = substr($projects[$name]['version'], strlen($project['core'] . '-')); } } else { unset($projects[$name]['version']); } $projects[$name]['_type'] = $project['type']; if ($project['download']['type'] == 'git') { drush_make_resolve_git_refs($projects[$name]); } // Don't clutter the makefile with defaults if (is_array($defaults)) { foreach ($defaults as $type => $defs) { if ($type == 'projects') { foreach ($defs as $key => $value) { if (isset($project[$key]) && $project[$key] == $value) { unset($projects[$name][$key]); } } } } } if ($project['name'] == $name) { unset($projects[$name]['name']); } if ($project['type'] == 'module' && !isset($info[$name]['type'])) { unset($projects[$name]['type']); // Module is the default } if (!(isset($project['download']['type'])) || ($project['download']['type'] == 'pm')) { unset($projects[$name]['download']); // PM is the default } $ignore = array('build_path', 'contrib_destination', 'core', 'make_directory', 'l10n_url', 'download_type'); foreach ($ignore as $key) { unset($projects[$name][$key]); } // Remove the location if it's the default. if ($projects[$name]['location'] == 'https://updates.drupal.org/release-history') { unset($projects[$name]['location']); } // Remove empty entries (e.g. 'directory_name') $projects[$name] = _make_generate_array_filter($projects[$name]); } $libraries = drush_get_option('DRUSH_MAKE_LIBRARIES', FALSE); if ($libraries === FALSE) { $libraries = isset($info['libraries']) ? $info['libraries'] : array(); } if (is_array($libraries)) { foreach ($libraries as $name => $library) { $libraries[$name]['type'] = 'library'; $libraries[$name]['_type'] = 'librarie'; if ($library['download']['type'] == 'git') { drush_make_resolve_git_refs($libraries[$name]); } } } $contents = make_generate_makefile_contents($projects, $libraries, $core, $defaults); // Write or print our makefile. $file = $file !== TRUE ? $file : NULL; make_generate_print($contents, $file); } /** * Resolve branches and revisions for git-based projects. */ function drush_make_resolve_git_refs(&$project) { if (!isset($project['download']['branch'])) { $project['download']['branch'] = drush_make_resolve_git_branch($project); } if (!isset($project['download']['revision'])) { $project['download']['revision'] = drush_make_resolve_git_revision($project); } } /** * Resolve branch for a git-based project. */ function drush_make_resolve_git_branch($project) { drush_log(dt('Resolving default branch for repo at: :repo', array(':repo' => $project['download']['url']))); if (drush_shell_exec("git ls-remote %s HEAD", $project['download']['url'])) { $head_output = drush_shell_exec_output(); list($head_commit) = explode("\t", $head_output[0]); drush_log(dt('Scanning branches in repo at: :repo', array(':repo' => $project['download']['url']))); drush_shell_exec("git ls-remote --heads %s", $project['download']['url']); $heads_output = drush_shell_exec_output(); $branches = array(); foreach ($heads_output as $key => $head) { list($commit, $ref) = explode("\t", $head); $branches[$commit] = explode("/", $ref)[2]; } $branch = $branches[$head_commit]; drush_log(dt('Resolved git branch to: :branch', array(':branch' => $branch))); return $branch; } else { drush_log(dt('Could not resolve branch for `:project` using git repo at :repo', array(':project' => $project['name'], ':repo' => $project['download']['url'])), 'warning'); } } /** * Resolve revision for a git-based project. */ function drush_make_resolve_git_revision($project) { drush_log(dt('Resolving head commit on `:branch` branch for repo at: :repo', array(':branch' => $project['download']['branch'], ':repo' => $project['download']['url']))); if (drush_shell_exec("git ls-remote %s %s", $project['download']['url'], $project['download']['branch'])) { $head_output = drush_shell_exec_output(); list($revision) = explode("\t", $head_output[0]); drush_log(dt('Resolved git revision to: :revision', array(':revision' => $revision))); return $revision; } else { drush_log(dt('Could not resolve head commit for `:project` using git repo at :repo', array(':project' => $project['name'], ':repo' => $project['download']['url'])), 'warning'); } } /** * Generate makefile contents in the appropriate format. */ function make_generate_makefile_contents($projects, $libraries = array(), $core = NULL, $defaults = array()) { $format = drush_get_option('format', 'yaml'); $func = "make_generate_makefile_contents_$format"; if (function_exists($func)) { $contents = call_user_func($func, $projects, $libraries, $core, $defaults); } else { return drush_set_error('MAKE_UNKNOWN_OUTPUT_FORMAT', dt('Generating makefiles in the :format output format is not yet supported. Implement :func() to add such support.', array(':format' => $format, ':func' => $func))); } return $contents; } /** * Generate makefile contents in (legacy) INI format. */ function make_generate_makefile_contents_ini($projects, $libraries, $core, $defaults) { return _drush_make_generate_makefile_contents($projects, $libraries, $core, $defaults); } /** * Generate makefile contents in YAML format. */ function make_generate_makefile_contents_yaml($projects, $libraries, $core, $defaults) { $info = array( 'core' => $core, 'api' => MAKE_API, 'defaults' => $defaults, 'projects' => $projects, 'libraries' => $libraries, ); $info = _make_generate_array_filter($info); $info = _make_generate_array_filter_key('_type', $info); $dumper = drush_load_engine('outputformat', 'yaml'); $yaml = $dumper->format($info, array()); return $yaml; } /** * Helper function to recursively remove empty values from an array (but not * '0'!). */ function _make_generate_array_filter($haystack) { foreach ($haystack as $key => $value) { if (is_array($value)) { $haystack[$key] = _make_generate_array_filter($haystack[$key]); } if (empty($value) && $value !== '0') { unset($haystack[$key]); } } return $haystack; } /** * Helper function to recursively remove elements matching a specific key from an array. */ function _make_generate_array_filter_key($needle, $haystack) { foreach ($haystack as $key => $value) { if ($key === $needle) { unset($haystack[$key]); } elseif (is_array($value)) { $haystack[$key] = _make_generate_array_filter_key($needle, $haystack[$key]); } } return $haystack; } /** * Print the generated makefile to the terminal, or write it to a file. * * @param $contents * The formatted contents of a makefile. * @param $file * (optional) The path to write the makefile. */ function make_generate_print($contents, $file = NULL) { if (!$file) { drush_print($contents); } elseif (file_put_contents($file, $contents)) { drush_log(dt("Wrote .make file @file", array('@file' => $file)), LogLevel::OK); } else { make_error('FILE_ERROR', dt("Unable to write .make file !file", array('!file' => $file))); } } /** * Utility function to generate the line or lines for a key/value pair in the * make file. * * @param $base * The base for the configuration lines. Values will be appended to it as * [$key] = $value, or if value is an array itself it will expand into as many * lines as required. * @param $values * May be a single value or an array. * @return * An array of strings that represent lines for the make file. */ function _drush_make_generate_lines($base, $values) { $output = array(); if (is_array($values)) { foreach ($values as $key => $value) { $newbase = $base . '[' . $key . ']'; $output = array_merge($output, _drush_make_generate_lines($newbase, $value)); } } else { $output[$base] = '"' . $values . '"'; } return $output; } function _drush_make_generate_defaults($defaults, &$output = array()) { $output[] = '; Defaults'; foreach ($defaults as $name => $project) { $type = 'defaults'; if (!$project && is_string($name)) { $output[] = $type . '[] = "' . $name . '"'; continue; } $base = $type . '[' . $name . ']'; $output = array_merge($output, _drush_make_generate_lines($base, $project)); } }