7 use Drush\Log\LogLevel;
8 use Drush\UpdateService\ReleaseInfo;
11 * Default localization server for downloading translations.
13 define('MAKE_DEFAULT_L10N_SERVER', 'http://ftp.drupal.org/files/translations/l10n_server.xml');
16 * Make refuses to build makefiles whose api version is mismatched
19 define('MAKE_API', 2);
21 include_once 'make.utilities.inc';
22 include_once 'make.download.inc';
23 include_once 'make.project.inc';
24 include_once 'generate.contents.make.inc';
27 * Implements hook_drush_help().
29 function make_drush_help($section) {
31 case 'meta:make:title':
32 return dt('Make commands');
33 case 'meta:make:summary':
34 return dt('Manage Drupal codebases using manifests of projects and libraries.');
36 return dt('Turns a makefile into a Drupal codebase. For a full description of options and makefile syntax, see docs/make.txt and examples/example.make.');
37 case 'drush:make-generate':
38 return dt('Generate a makefile from the current Drupal site, specifying project version numbers unless not known or otherwise specified. Unversioned projects will be interpreted later by drush make as "most recent stable release"');
43 * Implements hook_drush_command().
45 function make_drush_command() {
47 'description' => 'Restrict the make to this comma-separated list of projects. To specify all projects, pass *.',
48 'example-value' => 'views,ctools',
51 'description' => 'Restrict the make to this comma-separated list of libraries. To specify all libraries, pass *.',
52 'example-value' => 'tinymce',
55 $items['make'] = array(
56 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
57 'description' => 'Turns a makefile into a working Drupal codebase.',
59 'makefile' => 'Filename of the makefile to use for this build.',
60 'build path' => 'The path at which to build the makefile.',
63 'drush make example.make example' => 'Build the example.make makefile in the example directory.',
64 'drush make --no-core --contrib-destination=. installprofile.make' => 'Build an installation profile within an existing Drupal site',
65 'drush make http://example.com/example.make example' => 'Build the remote example.make makefile in the example directory.',
66 'drush make example.make --no-build --lock=example.lock' => 'Write a new makefile to example.lock. All project versions will be resolved.',
69 'version' => 'Print the make API version and exit.',
70 'concurrency' => array(
71 'description' => 'Set the number of concurrent projects that will be processed at the same time. The default is 1.',
72 'example-value' => '1',
74 'contrib-destination' => 'Specify a path under which modules and themes should be placed. Defaults to sites/all for Drupal 6,7 and the corresponding directory in the Drupal root for Drupal 8 and above.',
75 'force-complete' => 'Force a complete build even if errors occur.',
76 'ignore-checksums' => 'Ignore md5 checksums for downloads.',
78 'description' => 'Output an md5 hash of the current build after completion. Use --md5=print to print to stdout.',
79 'example-value' => 'print',
80 'value' => 'optional',
82 'make-update-default-url' => 'The default location to load the XML update information from.',
83 'no-build' => 'Do not build a codebase. Makes the `build path` argument optional.',
84 'no-cache' => 'Do not use the pm-download caching (defaults to cache enabled).',
85 'no-clean' => 'Leave temporary build directories in place instead of cleaning up after completion.',
86 'no-core' => 'Do not require a Drupal core project to be specified.',
87 'no-recursion' => 'Do not recurse into the makefiles of any downloaded projects; you can also set [do_recursion] = 0 on a per-project basis in the makefile.',
88 'no-patch-txt' => 'Do not write a PATCHES.txt file in the directory of each patched project.',
89 'no-gitinfofile' => 'Do not modify .info files when cloning from Git.',
90 'force-gitinfofile' => 'Force a modification of .info files when cloning from Git even if repository isn\'t hosted on Drupal.org.',
91 'no-gitprojectinfo' => 'Do not inject project info into .info files when cloning from Git.',
92 'overwrite' => 'Overwrite existing directories. Default is to merge.',
93 'prepare-install' => 'Prepare the built site for installation. Generate a properly permissioned settings.php and files directory.',
94 'tar' => 'Generate a tar archive of the build. The output filename will be [build path].tar.gz.',
95 'test' => 'Run a temporary test build and clean up.',
96 'translations' => 'Retrieve translations for the specified comma-separated list of language(s) if available for all projects.',
97 'working-copy' => 'Preserves VCS directories, like .git, for projects downloaded using such methods.',
98 'download-mechanism' => 'How to download files. Should be autodetected, but this is an override if it doesn\'t work. Options are "curl" and "make" (a native download method).',
99 'projects' => $projects,
100 'libraries' => $libraries,
101 'allow-override' => array(
102 'description' => 'Restrict the make options to a comma-separated list. Defaults to unrestricted.',
105 'description' => 'Generate a makefile, based on the one passed in, with all versions *resolved*. Defaults to printing to the terminal, but an output file may be provided.',
106 'example-value' => 'example.make.lock',
108 'shallow-clone' => array(
109 'description' => 'For makefile entries which use git for downloading, this option will utilize shallow clones where possible (ie. by using the git-clone\'s depth=1 option). If the "working-copy" option is not desired, this option will significantly speed up makes which involve modules stored in very large git repos. In fact, if "working-copy" option is enabled, this option cannot be used.',
111 'bundle-lockfile' => array(
112 'description' => 'Generate a lockfile for this build and copy it into the codebase (at sites/all/drush/platform.lock). An alternate path (relative to the Drupal root) can also be specified',
113 'example-value' => 'sites/all/drush/example.make.lock',
116 'description' => 'The format for generated lockfiles. Options are "yaml" or "ini". Defaults to "yaml".',
117 'example-value' => 'ini',
119 'core-quick-drupal' => array(
120 'description' => 'Return project info for use by core-quick-drupal.',
123 'includes' => 'A list of makefiles to include at build-time.',
124 'overrides' => 'A list of makefiles to that can override values in other makefiles.',
126 'engines' => array('release_info'),
127 'topics' => array('docs-make', 'docs-make-example'),
130 $items['make-generate'] = array(
131 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
132 'description' => 'Generate a makefile from the current Drupal site.',
134 'drush generate-makefile example.make' => 'Generate a makefile with ALL projects versioned (should a project have a known version number)',
135 'drush generate-makefile example.make --exclude-versions' => 'Generate a makefile with NO projects versioned',
136 'drush generate-makefile example.make --exclude-versions=drupal,views,cck' => 'Generate a makefile with ALL projects versioned EXCEPT core, Views and CCK',
137 'drush generate-makefile example.make --include-versions=admin_menu,og,ctools (--exclude-versions)' => 'Generate a makefile with NO projects versioned EXCEPT Admin Menu, OG and CTools.',
140 'exclude-versions' => 'Exclude all version numbers (default is include all version numbers) or optionally specify a list of projects to exclude from versioning',
141 'include-versions' => 'Include a specific list of projects, while all other projects remain unversioned in the makefile (so implies --exclude-versions)',
143 'description' => 'The format for generated makefile. Options are "yaml" or "ini". Defaults to "yaml".',
144 'example-value' => 'ini',
147 'engines' => array('release_info'),
148 'aliases' => array('generate-makefile'),
151 $items['make-convert'] = array(
152 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
153 'description' => 'Convert a legacy makefile into another format. Defaults to converting .make => .make.yml.',
154 'arguments' => array(
155 'makefile' => 'Filename of the makefile to convert.',
158 'projects' => $projects,
159 'libraries' => $libraries,
160 'includes' => 'A list of makefiles to include at build-time.',
161 'format' => 'The format to which the make file should be converted. Accepted values include make, composer, and yml.',
163 'required-arguments' => TRUE,
165 'drush make-convert example.make --format=composer > composer.json' => 'Convert example.make to composer.json',
166 'drush make-convert example.make --format=yml > example.make.yml' => 'Convert example.make to example.make.yml',
167 'drush make-convert composer.lock --format=make > example.make' => 'Convert composer.lock example.make',
171 // Hidden command to build a group of projects.
172 $items['make-process'] = array(
174 'arguments' => array(
175 'directory' => 'The temporary working directory to use',
178 'projects-location' => 'Name of a temporary file containing json-encoded output of make_projects().',
179 'manifest' => 'An array of projects already being processed.',
181 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
182 'engines' => array('release_info'),
185 $items['make-update'] = array(
186 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
187 'description' => 'Process a makefile and outputs an equivalent makefile with projects version resolved to latest available.',
188 'arguments' => array(
189 'makefile' => 'Filename of the makefile to use for this build.',
192 'result-file' => array(
193 'description' => 'Save to a file. If not provided, the updated makefile will be dumped to stdout.',
194 'example-value' => 'updated.make',
197 'description' => 'The format for generated lockfiles. Options are "yaml" or "ini". Defaults to "yaml".',
198 'example-value' => 'ini',
200 'includes' => 'A list of makefiles to include at build-time.',
202 'engines' => array('release_info', 'update_status'),
205 $items['make-lock'] = array(
206 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
207 'description' => 'Process a makefile and outputs an equivalent makefile with projects version *resolved*. Respects pinned versions.',
208 'arguments' => array(
209 'makefile' => 'Filename of the makefile to use for this build.',
212 'result-file' => array(
213 'description' => 'Save to a file. If not provided, the lockfile will be dumped to stdout.',
214 'example-value' => 'platform.lock',
217 'description' => 'The format for generated lockfiles. Options are "yaml" or "ini". Defaults to "yaml".',
218 'example-value' => 'ini',
220 'includes' => 'A list of makefiles to include at build-time.',
222 'allow-additional-options' => TRUE,
223 'engines' => array('release_info', 'update_status'),
227 $docs_dir = drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH);
228 $items['docs-make'] = array(
229 'description' => 'Drush Make overview with examples',
232 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
233 'callback' => 'drush_print_file',
234 'callback arguments' => array($docs_dir . '/docs/make.md'),
236 $items['docs-make-example'] = array(
237 'description' => 'Drush Make example makefile',
240 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
241 'callback' => 'drush_print_file',
242 'callback arguments' => array($docs_dir . '/examples/example.make.yml'),
248 * Command argument complete callback.
251 * Strong glob of files to complete on.
253 function make_make_complete() {
256 'directories' => array(
258 'flags' => GLOB_ONLYDIR,
261 'pattern' => '*.make',
268 * Validation callback for make command.
270 function drush_make_validate($makefile = NULL, $build_path = NULL) {
271 // Don't validate if --version option is supplied.
272 if (drush_get_option('version', FALSE)) {
276 if (drush_get_option('shallow-clone', FALSE) && drush_get_option('working-copy', FALSE)) {
277 return drush_set_error('MAKE_SHALLOW_CLONE_WORKING_COPY_CONFLICT', dt('You cannot use "--shallow-clone" and "--working-copy" options together.'));
280 // Error out if the build path is not valid and --no-build was not supplied.
281 if (!drush_get_option('no-build', FALSE) && !make_build_path($build_path)) {
287 * Implements drush_hook_pre_COMMAND().
289 * If --version option is supplied, print it and prevent execution of the command.
291 function drush_make_pre_make($makefile = NULL, $build_path = NULL) {
292 if (drush_get_option('version', FALSE)) {
293 drush_print(dt('Drush make API version !version', array('!version' => MAKE_API)));
294 drush_print_pipe(MAKE_API);
295 // Prevent command execution.
301 * Drush callback; make based on the makefile.
303 function drush_make($makefile = NULL, $build_path = NULL) {
304 // Set the cache option based on our '--no-cache' option.
305 _make_enable_cache();
308 if (!drush_get_option('no-build', FALSE)) {
309 $info = make_parse_info_file($makefile);
310 drush_log(dt('Beginning to build !makefile.', array('!makefile' => $makefile)), LogLevel::OK);
312 // Default contrib destination depends on Drupal core version.
313 $core_version = str_replace('.x', '', $info['core'][0]);
314 $sitewide = drush_drupal_sitewide_directory($core_version);
315 $contrib_destination = drush_get_option('contrib-destination', $sitewide);
317 $build_path = make_build_path($build_path);
318 $make_dir = realpath(dirname($makefile));
320 $success = make_projects(FALSE, $contrib_destination, $info, $build_path, $make_dir);
322 make_libraries(FALSE, $contrib_destination, $info, $build_path, $make_dir);
324 if (drush_get_option('prepare-install')) {
325 make_prepare_install($build_path);
327 if ($option = drush_get_option('md5')) {
329 if ($option === 'print') {
333 drush_log(dt('Build hash: %md5', array('%md5' => $md5)), LogLevel::OK);
336 // Only take final build steps if not in testing mode.
337 if (!drush_get_option('test')) {
338 if (drush_get_option('tar')) {
339 make_tar($build_path);
342 make_move_build($build_path);
348 return make_error('MAKE_PROJECTS_FAILED', dt('Drush Make failed to download all projects. See the log above for the specific errors.'));
352 // Process --lock and --bundle-lockfile
353 $lockfiles = array();
354 if ($result_file = drush_get_option('bundle-lockfile', FALSE)) {
355 if ($result_file === TRUE) {
356 $result_file = 'sites/all/drush/platform.make';
358 $lockfiles[] = $build_path . '/' . $result_file;
360 if ($result_file = drush_get_option('lock', FALSE)) {
361 $lockfiles[] = $result_file;
363 if (count($lockfiles)) {
364 foreach ($lockfiles as $lockfile) {
365 if ($lockfile !== TRUE) {
366 $result_file = drush_normalize_path($lockfile);
367 drush_mkdir(dirname($result_file), $required = TRUE);
368 drush_set_option('result-file', $result_file);
370 drush_invoke('make-lock', $makefile);
371 drush_unset_option('result-file');
375 // Used by core-quick-drupal command.
376 // @see drush_core_quick_drupal().
377 if (drush_get_option('core-quick-drupal', FALSE)) {
383 * Command callback; convert ini makefile to YAML.
385 function drush_make_convert($source) {
386 $dest_format = drush_get_option('format', 'yml');
389 $source_format = pathinfo($source, PATHINFO_EXTENSION);
391 if ($source_format == $dest_format || $source_format == 'lock' && $dest_format == 'composer') {
392 drush_print('The source format cannot be the same as the destination format.');
395 // Obtain drush make $info array, converting if necessary.
396 switch ($source_format) {
400 $info = make_parse_info_file($source);
403 $composer_json_file = str_replace('lock', 'json', $source);
404 if (!file_exists($composer_json_file)) {
405 drush_print('Please ensure that a composer.json file is in the same directory as the specified composer.lock file.');
408 $composer_json = json_decode(make_get_data($composer_json_file), TRUE);
409 $composer_lock = json_decode(make_get_data($source), TRUE);
410 $info = drush_make_convert_composer_to_make($composer_lock, $composer_json);
413 drush_print('Please use composer.lock instead of composer.json as source for conversion.');
418 // Output into destination formation.
419 switch ($dest_format) {
422 $output = drush_make_convert_make_to_yml($info);
426 foreach ($info['projects'] as $key => $project) {
427 $info['projects'][$key]['_type'] = $info['projects'][$key]['type'];
429 foreach ($info['libraries'] as $key => $library) {
430 $info['libraries'][$key]['_type'] = 'librarie';
432 $output = _drush_make_generate_makefile_contents($info['projects'], $info['libraries'], $info['core'], $info['defaults']);
437 $output = drush_make_convert_make_to_composer($info);
441 drush_print($output);
445 * Converts a composer.lock array into a traditional drush make array.
447 * @param array $composer_lock
448 * An array of composer.lock data.
450 * @param array $composer_json
451 * An array of composer.json data.
453 * @return array A traditional drush make info array.
454 * A traditional drush make info array.
456 function drush_make_convert_composer_to_make($composer_lock, $composer_json) {
462 'subdir' => 'contrib',
465 'projects' => array(),
466 'libraries' => array(),
469 // The make generation function requires that projects be grouped by type,
470 // or else duplicative project groups will be created.
475 $libraries = array();
476 foreach ($composer_lock['packages'] as $key => $package) {
477 if (strpos($package['name'], 'drupal/') === 0 && in_array($package['type'], array('drupal-core', 'drupal-theme', 'drupal-module', 'drupal-profile'))) {
478 $project_name = str_replace('drupal/', '', $package['name']);
480 switch ($package['type']) {
482 $project_name = 'drupal';
484 $group[$project_name]['type'] = 'core';
485 $info['core'] = substr($package['version'], 0, 1) . '.x';
489 $group[$project_name]['type'] = 'theme';
491 case 'drupal-module':
493 $group[$project_name]['type'] = 'module';
495 case 'drupal-profile':
497 $group[$project_name]['type'] = 'profile';
501 $group[$project_name]['download']['type'] = 'git';
502 $group[$project_name]['download']['url'] = $package['source']['url'];
503 // Dev versions should use git branch + revision, otherwise a tag is used.
504 if (strstr($package['version'], 'dev')) {
505 // 'dev-' prefix indicates a branch-alias. Stripping the dev prefix from
506 // the branch name is sufficient.
507 // @see https://getcomposer.org/doc/articles/aliases.md
508 if (strpos($package['version'], 'dev-') === 0) {
509 $group[$project_name]['download']['branch'] = substr($package['version'], 4);
511 // Otherwise, leave as is. Version may already use '-dev' suffix.
513 $group[$project_name]['download']['branch'] = $package['version'];
515 $group[$project_name]['download']['revision'] = $package['source']['reference'];
517 elseif ($package['type'] == 'drupal-core') {
518 // For 7.x tags, replace 7.xx.0 with 7.xx.
519 if ($info['core'] == '7.x') {
520 $group[$project_name]['download']['tag']= substr($package['version'], 0, 4);
523 $group[$project_name]['download']['tag'] = $package['version'];
527 // Make tag versioning drupal-friendly. 8.1.0-alpha1 => 8.x-1.0-alpha1.
528 $major_version = substr($package['version'], 0 ,1);
529 $the_rest = substr($package['version'], 2, strlen($package['version']));
530 $group[$project_name]['download']['tag'] = "$major_version.x-$the_rest";
533 if (!empty($package['extra']['patches_applied'])) {
534 foreach ($package['extra']['patches_applied'] as $desc => $url) {
535 $group[$project_name]['patch'][] = $url;
539 // Include any non-drupal libraries that exist in both .lock and .json.
540 elseif (!in_array($package['type'], array('composer-plugin', 'metapackage'))
541 && array_key_exists($package['name'], $composer_json['require'])) {
542 $project_name = $package['name'];
543 $libraries[$project_name]['type'] = 'library';
544 $libraries[$project_name]['download']['type'] = 'git';
545 $libraries[$project_name]['download']['url'] = $package['source']['url'];
546 $libraries[$project_name]['download']['branch'] = $package['version'];
547 $libraries[$project_name]['download']['revision'] = $package['source']['reference'];
551 $info['projects'] = $core + $modules + $themes;
552 $info['libraries'] = $libraries;
558 * Converts a drush info array to a composer.json array.
561 * A drush make info array.
564 * A json encoded composer.json schema object.
566 function drush_make_convert_make_to_composer($info) {
567 $core_major_version = substr($info['core'], 0, 1);
568 $core_project_name = $core_major_version == 7 ? 'drupal/drupal' : 'drupal/core';
570 // Add default projects.
572 'composer/installers' => '^1.0.20',
573 'cweagans/composer-patches' => '~1.0',
574 $core_project_name => str_replace('x', '*', $info['core']),
579 // Iterate over projects, populating composer-friendly array.
580 foreach ($info['projects'] as $project_name => $project) {
581 switch ($project['type']) {
583 $project['name'] = 'drupal/core';
584 $projects[$project['name']] = str_replace('x', '*', $project['version']);
588 $project['name'] = "drupal/$project_name";
589 $projects[$project['name']] = drush_make_convert_project_to_composer($project, $core_major_version);
593 // Add project patches.
594 if (!empty($project['patch'])) {
595 foreach($project['patch'] as $key => $patch) {
596 $patch_description = "Enter {$project['name']} patch #$key description here";
597 $patches[$project['name']][$patch_description] = $patch;
602 // Iterate over libraries, populating composer-friendly array.
603 if (!empty($info['libraries'])) {
604 foreach ($info['libraries'] as $library_name => $library) {
605 $library_name = 'Verify project name: ' . $library_name;
606 $projects[$library_name] = drush_make_convert_project_to_composer($library, $core_major_version);
611 'name' => 'Enter project name here',
612 'description' => 'Enter project description here',
614 'repositories' => array(
615 array('type' => 'composer', 'url' => 'https://packagist.drupal-composer.org'),
617 'require' => $projects,
618 'minimum-stability' => 'dev',
619 'prefer-stable' => TRUE,
621 'installer-paths' => array(
622 'core' => array('type:drupal-core'),
623 'docroot/modules/contrib/{$name}' => array('type:drupal-module'),
624 'docroot/profiles/contrib/{$name}' => array('type:drupal-profile'),
625 'docroot/themes/contrib/{$name}' => array('type:drupal-theme'),
626 'drush/contrib/{$name}' => array('type:drupal-drush'),
628 'patches' => $patches,
632 $output = json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
638 * Converts a make file project array into a composer project version string.
640 * @param array $original_project
641 * A project dependency, as defined in a make file.
643 * @param string $core_major_version
644 * The major core version. E.g., 6, 7, 8, etc.
647 * The project version, in composer syntax.
650 function drush_make_convert_project_to_composer($original_project, $core_major_version) {
651 // Typical specified version with major version "x" removed.
652 if (!empty($original_project['version'])) {
653 $version = str_replace('x', '0', $original_project['version']);
655 // Git branch or revision.
656 elseif (!empty($original_project['download'])) {
657 switch ($original_project['download']['type']) {
659 if (!empty($original_project['download']['branch'])) {
660 // @todo Determine if '0' will always be correct.
661 $version = str_replace('x', '0', $original_project['download']['branch']);
663 if (!empty($original_project['download']['tag'])) {
664 // @todo Determine if '0' will always be correct.
665 $version = str_replace('x', '0', $original_project['download']['tag']);
667 if (!empty($project['download']['revision'])) {
668 $version .= '#' . $original_project['download']['revision'];
673 $version = 'Enter correct project name and version number';
678 $version = "$core_major_version." . $version;
684 * Converts a drush info array to a YAML array.
687 * A drush make info array.
690 * A yaml encoded info array.
692 function drush_make_convert_make_to_yml($info) {
693 // Remove incorrect value.
694 unset($info['format']);
696 // Replace "*" with "~" for project versions.
697 foreach ($info['projects'] as $key => $project) {
698 if ($project['version'] == '*') {
699 $info['projects'][$key]['version'] = '~';
703 $dumper = drush_load_engine('outputformat', 'yaml');
704 $output = $dumper->format($info, array());
710 * Drush callback: hidden file to process an individual project.
712 * @param string $directory
713 * Directory where the project is being built.
715 function drush_make_process($directory) {
716 drush_get_engine('release_info');
718 // Set the temporary directory.
719 make_tmp(TRUE, $directory);
720 if (!$projects_location = drush_get_option('projects-location')) {
721 return drush_set_error('MAKE-PROCESS', dt('No projects passed to drush_make_process'));
723 $projects = json_decode(file_get_contents($projects_location), TRUE);
724 $manifest = drush_get_option('manifest', array());
726 foreach ($projects as $project) {
727 if ($instance = DrushMakeProject::getInstance($project['type'], $project)) {
728 $instance->setManifest($manifest);
732 make_error('PROJECT-TYPE', dt('Non-existent project type %type on project %project.', array('%type' => $project['type'], '%project' => $project['name'])));
738 * Gather additional data on all projects specified in the make file.
740 function make_prepare_projects($recursion, $info, $contrib_destination = '', $build_path = '', $make_dir = '') {
741 $release_info = drush_get_engine('release_info');
743 // Nothing to make if the project list is empty. Maybe complain about it.
744 if (empty($info['projects'])) {
745 if (drush_get_option('no-core') || $recursion) {
749 return drush_set_error('MAKE_NO_CORE', dt('No core project specified.'));
753 // Obtain translations to download along with the projects.
754 $translations = array();
755 if (isset($info['translations'])) {
756 $translations = $info['translations'];
758 if ($arg_translations = drush_get_option('translations', FALSE)) {
759 $translations = array_merge(explode(',', $arg_translations), $translations);
762 // Normalize projects.
764 $ignore_checksums = drush_get_option('ignore-checksums');
765 foreach ($info['projects'] as $key => $project) {
766 // Merge the known data onto the project info.
770 'core' => $info['core'],
771 'translations' => $translations,
772 'build_path' => $build_path,
773 'contrib_destination' => $contrib_destination,
775 'location' => drush_get_option('make-update-default-url', ReleaseInfo::DEFAULT_URL),
777 'directory_name' => '',
778 'make_directory' => $make_dir,
779 'options' => array(),
782 if ($ignore_checksums) {
783 unset($project['download']['md5']);
785 elseif (!empty($project['md5'])) {
786 $project['download']['md5'] = $project['md5'];
789 // If download components are specified, but not the download
790 // type, default to git.
791 if (isset($project['download']) && !isset($project['download']['type'])) {
792 $project['download']['type'] = 'git';
794 // Localization server.
795 if (!isset($project['l10n_url']) && ($project['location'] == ReleaseInfo::DEFAULT_URL)) {
796 $project['l10n_url'] = MAKE_DEFAULT_L10N_SERVER;
798 // Classify projects in core or contrib.
799 if ($project['type'] == 'core') {
800 $project['download_type'] = 'core';
802 elseif ($project['location'] != ReleaseInfo::DEFAULT_URL || !isset($project['download'])) {
803 $request = make_prepare_request($project);
804 $is_core = $release_info->checkProject($request, 'core');
805 $project['download_type'] = ($is_core ? 'core' : 'contrib');
806 $project['type'] = $is_core ? 'core' : $project['type'];
809 $project['download_type'] = ($project['name'] == 'drupal' ? 'core' : 'contrib');
811 $projects[$project['download_type']][$project['name']] = $project;
814 // Verify there're enough cores, but not too many.
815 $cores = !empty($projects['core']) ? count($projects['core']) : 0;
816 if (drush_get_option('no-core')) {
817 unset($projects['core']);
819 elseif ($cores == 0 && !$recursion) {
820 return drush_set_error('MAKE_NO_CORE', dt('No core project specified.'));
822 elseif ($cores == 1 && $recursion) {
823 unset($projects['core']);
825 elseif ($cores > 1) {
826 return drush_set_error('MAKE_MULTIPLE_CORES', dt('More than one core project specified.'));
829 // Set download type = pm for suitable projects.
830 foreach (array_keys($projects) as $project_type) {
831 foreach ($projects[$project_type] as $project) {
832 if (make_project_needs_release_info($project)) {
833 $request = make_prepare_request($project, $project_type);
834 $release = $release_info->selectReleaseBasedOnStrategy($request, '', 'ignore');
835 if ($release === FALSE) {
838 // Override default project type with data from update service.
839 if (!isset($info['projects'][$project['name']]['type'])) {
840 $project['type'] = $release_info->get($request)->getType();
843 if (!isset($project['download'])) {
844 $project['download'] = array(
846 'full_version' => $release['version'],
847 'download_link' => $release['download_link'],
848 'status url' => $request['status url'],
852 $projects[$project_type][$project['name']] = $project;
858 'contrib' => array(),
860 drush_set_option('DRUSH_MAKE_PROJECTS', array_merge($projects['core'], $projects['contrib']));
866 * Process all projects specified in the make file.
868 function make_projects($recursion, $contrib_destination, $info, $build_path, $make_dir) {
869 $projects = make_prepare_projects($recursion, $info, $contrib_destination, $build_path, $make_dir);
870 // Abort if there was an error processing projects.
871 if ($projects === FALSE) {
875 // Core is built in place, rather than using make-process.
876 if (!empty($projects['core']) && count($projects['core'])) {
877 $project = current($projects['core']);
878 $project = DrushMakeProject::getInstance('core', $project);
882 // Process all projects concurrently using make-process.
883 if (isset($projects['contrib'])) {
884 $concurrency = drush_get_option('concurrency', 1);
885 // Generate $concurrency sub-processes to do the actual work.
886 $invocations = array();
888 foreach ($projects['contrib'] as $project) {
889 $thread = ++$thread % $concurrency;
890 // Ensure that we've set this sub-process up.
891 if (!isset($invocations[$thread])) {
892 $invocations[$thread] = array(
897 'projects' => array(),
902 // Add the project to this sub-process.
903 $invocations[$thread]['options']['projects'][] = $project;
904 // Add the manifest so recursive downloads do not override projects.
905 $invocations[$thread]['options']['manifest'] = array_keys($projects['contrib']);
907 if (!empty($invocations)) {
909 $backend_options = array(
910 'concurrency' => $concurrency,
914 // Store projects in temporary files since passing this much data on the
915 // pipe buffer can break on certain systems.
916 _make_write_project_json($invocations);
918 $common_options = drush_redispatch_get_options();
919 // Merge in stdin options since we process makefiles recursively. See http://drupal.org/node/1510180.
920 $common_options = array_merge($common_options, drush_get_context('stdin'));
921 // Package handler should use 'wget'.
922 $common_options['package-handler'] = 'wget';
924 // Avoid any prompts from CLI.
925 $common_options['yes'] = TRUE;
927 // Use cache unless explicitly turned off.
928 if (!drush_get_option('no-cache', FALSE)) {
929 $common_options['cache'] = TRUE;
931 // Unless --verbose or --debug are passed, quiter backend output.
932 if (empty($common_options['verbose']) && empty($common_options['debug'])) {
933 $backend_options['#output-label'] = FALSE;
934 $backend_options['integrate'] = TRUE;
936 $results = drush_backend_invoke_concurrent($invocations, $common_options, $backend_options, 'make-process', '@none');
937 if (count($results['error_log'])) {
946 * Writes out project data to temporary files.
948 * @param array &$invocations
949 * An array containing projects sorted by thread.
951 function _make_write_project_json(array &$invocations) {
952 foreach ($invocations as $thread => $info) {
953 $projects = $info['options']['projects'];
954 unset($invocations[$thread]['options']['projects']);
955 $temp_file = drush_tempnam('make_projects');
956 file_put_contents($temp_file, json_encode($projects));
957 $invocations[$thread]['options']['projects-location'] = $temp_file;
962 * Gather additional data on all libraries specified in the make file.
964 function make_prepare_libraries($recursion, $info, $contrib_destination = '', $build_path = '', $make_dir = '') {
965 // Nothing to make if the libraries list is empty.
966 if (empty($info['libraries'])) {
970 $libraries = array();
971 $ignore_checksums = drush_get_option('ignore-checksums');
972 foreach ($info['libraries'] as $key => $library) {
973 if (!is_string($key) || !is_array($library)) {
974 // TODO Print a prettier message.
977 // Merge the known data onto the library info.
980 'core' => $info['core'],
981 'build_path' => $build_path,
982 'contrib_destination' => $contrib_destination,
984 'directory_name' => $key,
985 'make_directory' => $make_dir,
987 if ($ignore_checksums) {
988 unset($library['download']['md5']);
990 $libraries[$key] = $library;
993 drush_set_option('DRUSH_MAKE_LIBRARIES', $info['libraries']);
999 * Process all libraries specified in the make file.
1001 function make_libraries($recursion, $contrib_destination, $info, $build_path, $make_dir) {
1002 $libraries = make_prepare_libraries($recursion, $info, $contrib_destination, $build_path, $make_dir);
1003 if (empty($libraries)) {
1006 foreach ($libraries as $key => $library) {
1007 $class = DrushMakeProject::getInstance('library', $library);
1013 * The path where the final build will be placed.
1015 function make_build_path($build_path) {
1017 if (isset($saved_path)) {
1021 // Determine the base of the build.
1022 if (drush_get_option('tar')) {
1023 $build_path = dirname($build_path) . '/' . basename($build_path, '.tar.gz') . '.tar.gz';
1025 elseif (isset($build_path) && (!empty($build_path) || $build_path == '.')) {
1026 $build_path = rtrim($build_path, '/');
1028 // Allow tests to run without a specified base path.
1029 elseif (drush_get_option('test') || drush_confirm(dt("Make new site in the current directory?"))) {
1033 return drush_user_abort(dt('Build aborted.'));
1035 if ($build_path != '.' && file_exists($build_path) && !drush_get_option('no-core', FALSE)) {
1036 return drush_set_error('MAKE_PATH_EXISTS', dt('Base path %path already exists.', array('%path' => $build_path)));
1038 $saved_path = $build_path;
1043 * Move the completed build into place.
1045 function make_move_build($build_path) {
1046 $tmp_path = make_tmp();
1048 if ($build_path == '.' || (drush_get_option('no-core', FALSE) && file_exists($build_path))) {
1049 $info = drush_scan_directory($tmp_path . DIRECTORY_SEPARATOR . '__build__', '/./', array('.', '..'), 0, FALSE, 'filename', 0, TRUE);
1050 foreach ($info as $file) {
1051 $destination = $build_path . DIRECTORY_SEPARATOR . $file->basename;
1052 if (file_exists($destination)) {
1053 // To prevent the removal of top-level directories such as 'modules' or
1054 // 'themes', descend in a level if the file exists.
1055 // TODO: This only protects one level of directories from being removed.
1056 $overwrite = drush_get_option('overwrite', FALSE) ? FILE_EXISTS_OVERWRITE : FILE_EXISTS_MERGE;
1057 if (is_dir($destination)) {
1058 $files = drush_scan_directory($file->filename, '/./', array('.', '..'), 0, FALSE);
1059 foreach ($files as $file) {
1060 $ret = $ret && drush_copy_dir($file->filename, $destination . DIRECTORY_SEPARATOR . $file->basename, $overwrite);
1064 $ret = $ret && drush_copy_dir($file->filename, $destination, $overwrite);
1068 $ret = $ret && drush_copy_dir($file->filename, $destination);
1073 drush_mkdir(dirname($build_path));
1074 $ret = drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . '__build__', $tmp_path . DIRECTORY_SEPARATOR . basename($build_path), TRUE);
1075 $ret = $ret && drush_copy_dir($tmp_path . DIRECTORY_SEPARATOR . basename($build_path), $build_path);
1078 // Copying to final destination resets write permissions. Re-apply.
1079 if (drush_get_option('prepare-install')) {
1080 $default = $build_path . '/sites/default';
1081 chmod($default . '/settings.php', 0666);
1082 chmod($default . '/files', 0777);
1086 return drush_set_error('MAKE_CANNOT_MOVE_BUILD', dt("Cannot move build into place."));
1092 * Create a request array suitable for release_info engine.
1094 * This is a convenience function to easily integrate drush_make
1095 * with drush release_info engine.
1097 * @todo: refactor 'make' to internally work with release_info keys.
1099 * @param array $project
1101 * @param string $type
1102 * 'contrib' or 'core'.
1104 function make_prepare_request($project, $type = 'contrib') {
1106 'name' => $project['name'],
1107 'drupal_version' => $project['core'],
1108 'status url' => $project['location'],
1110 if ($project['version'] != '') {
1111 $request['project_version'] = $project['version'];
1112 $request['version'] = $type == 'core' ? $project['version'] : $project['core'] . '-' . $project['version'];
1118 * Determine if the release information is required for this
1119 * project. When it is determined that it is, this potentially results
1120 * in the use of pm-download to process the project.
1122 * If the location of the project is not customized (uses d.o), and
1123 * one of the following is true, then release information is required:
1125 * - $project['type'] has not been specified
1126 * - $project['download'] has not been specified
1128 * @see make_projects()
1130 function make_project_needs_release_info($project) {
1131 return isset($project['location'])
1132 // Only fetch release info if the project type is unknown OR if
1133 // download attributes are unspecified.
1134 && (!isset($project['type']) || !isset($project['download']));
1138 * Enables caching if not explicitly disabled.
1141 * The previous value of the 'cache' option.
1143 function _make_enable_cache() {
1144 $cache_before = drush_get_option('cache');
1145 if (!drush_get_option('no-cache', FALSE)) {
1146 drush_set_option('cache', TRUE);
1148 return $cache_before;