make.drush.inc

  1. 8.0.x commands/make/make.drush.inc
  2. 6.x commands/make/make.drush.inc
  3. 7.x commands/make/make.drush.inc
  4. 5.x commands/make/make.drush.inc
  5. master commands/make/make.drush.inc

Drush Make commands.

Functions

Namesort descending Description
drush_make Drush callback; make based on the makefile.
drush_make_convert Command callback; convert ini makefile to YAML.
drush_make_pre_make Implements drush_hook_pre_COMMAND().
drush_make_process Drush callback: hidden file to process an individual project.
drush_make_validate Validation callback for make command.
make_build_path The path where the final build will be placed.
make_drush_command Implements hook_drush_command().
make_drush_help Implements hook_drush_help().
make_libraries Process all libraries specified in the make file.
make_make_complete Command argument complete callback.
make_move_build Move the completed build into place.
make_prepare_libraries Gather additional data on all libraries specified in the make file.
make_prepare_projects Gather additional data on all projects specified in the make file.
make_prepare_request Create a request array suitable for release_info engine.
make_projects Process all projects specified in the make file.
make_project_needs_release_info Determine if the release information is required for this project. When it is determined that it is, this potentially results in the use of pm-download to process the project.
_make_enable_cache Enables caching if not explicitly disabled.
_make_write_project_json Writes out project data to temporary files.

Constants

Namesort descending Description
MAKE_API Make refuses to build makefiles whose api version is mismatched with make command.
MAKE_DEFAULT_L10N_SERVER Default localization server for downloading translations.

File

commands/make/make.drush.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Drush Make commands.
  5. */
  6. use Drush\Log\LogLevel;
  7. use Drush\UpdateService\ReleaseInfo;
  8. /**
  9. * Default localization server for downloading translations.
  10. */
  11. define('MAKE_DEFAULT_L10N_SERVER', 'http://ftp.drupal.org/files/translations/l10n_server.xml');
  12. /**
  13. * Make refuses to build makefiles whose api version is mismatched
  14. * with make command.
  15. */
  16. define('MAKE_API', 2);
  17. include_once 'make.utilities.inc';
  18. include_once 'make.download.inc';
  19. include_once 'make.project.inc';
  20. include_once 'generate.contents.make.inc';
  21. /**
  22. * Implements hook_drush_help().
  23. */
  24. function make_drush_help($section) {
  25. switch ($section) {
  26. case 'meta:make:title':
  27. return dt('Make commands');
  28. case 'meta:make:summary':
  29. return dt('Manage Drupal codebases using manifests of projects and libraries.');
  30. case 'drush:make':
  31. 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.');
  32. case 'drush:make-generate':
  33. 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"');
  34. }
  35. }
  36. /**
  37. * Implements hook_drush_command().
  38. */
  39. function make_drush_command() {
  40. $projects = array(
  41. 'description' => 'Restrict the make to this comma-separated list of projects. To specify all projects, pass *.',
  42. 'example-value' => 'views,ctools',
  43. );
  44. $libraries = array(
  45. 'description' => 'Restrict the make to this comma-separated list of libraries. To specify all libraries, pass *.',
  46. 'example-value' => 'tinymce',
  47. );
  48. $items['make'] = array(
  49. 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  50. 'description' => 'Turns a makefile into a working Drupal codebase.',
  51. 'arguments' => array(
  52. 'makefile' => 'Filename of the makefile to use for this build.',
  53. 'build path' => 'The path at which to build the makefile.',
  54. ),
  55. 'examples' => array(
  56. 'drush make example.make example' => 'Build the example.make makefile in the example directory.',
  57. 'drush make --no-core --contrib-destination=. installprofile.make' => 'Build an installation profile within an existing Drupal site',
  58. 'drush make http://example.com/example.make example' => 'Build the remote example.make makefile in the example directory.',
  59. 'drush make example.make --no-build --lock=example.lock' => 'Write a new makefile to example.lock. All project versions will be resolved.',
  60. ),
  61. 'options' => array(
  62. 'version' => 'Print the make API version and exit.',
  63. 'concurrency' => array(
  64. 'description' => 'Set the number of concurrent projects that will be processed at the same time. The default is 1.',
  65. 'example-value' => '1',
  66. ),
  67. '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.',
  68. 'force-complete' => 'Force a complete build even if errors occur.',
  69. 'ignore-checksums' => 'Ignore md5 checksums for downloads.',
  70. 'md5' => array(
  71. 'description' => 'Output an md5 hash of the current build after completion. Use --md5=print to print to stdout.',
  72. 'example-value' => 'print',
  73. 'value' => 'optional',
  74. ),
  75. 'make-update-default-url' => 'The default location to load the XML update information from.',
  76. 'no-build' => 'Do not build a codebase. Makes the `build path` argument optional.',
  77. 'no-cache' => 'Do not use the pm-download caching (defaults to cache enabled).',
  78. 'no-clean' => 'Leave temporary build directories in place instead of cleaning up after completion.',
  79. 'no-core' => 'Do not require a Drupal core project to be specified.',
  80. '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.',
  81. 'no-patch-txt' => 'Do not write a PATCHES.txt file in the directory of each patched project.',
  82. 'no-gitinfofile' => 'Do not modify .info files when cloning from Git.',
  83. 'force-gitinfofile' => 'Force a modification of .info files when cloning from Git even if repository isn\'t hosted on Drupal.org.',
  84. 'no-gitprojectinfo' => 'Do not inject project info into .info files when cloning from Git.',
  85. 'overwrite' => 'Overwrite existing directories. Default is to merge.',
  86. 'prepare-install' => 'Prepare the built site for installation. Generate a properly permissioned settings.php and files directory.',
  87. 'tar' => 'Generate a tar archive of the build. The output filename will be [build path].tar.gz.',
  88. 'test' => 'Run a temporary test build and clean up.',
  89. 'translations' => 'Retrieve translations for the specified comma-separated list of language(s) if available for all projects.',
  90. 'working-copy' => 'Preserves VCS directories, like .git, for projects downloaded using such methods.',
  91. '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).',
  92. 'projects' => $projects,
  93. 'libraries' => $libraries,
  94. 'allow-override' => array(
  95. 'description' => 'Restrict the make options to a comma-separated list. Defaults to unrestricted.',
  96. ),
  97. 'lock' => array(
  98. '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.',
  99. 'example-value' => 'example.make.lock',
  100. ),
  101. 'shallow-clone' => array(
  102. '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.',
  103. ),
  104. 'bundle-lockfile' => array(
  105. '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',
  106. 'example-value' => 'sites/all/drush/example.make.lock',
  107. ),
  108. 'format' => array(
  109. 'description' => 'The format for generated lockfiles. Options are "yaml" or "ini". Defaults to "yaml".',
  110. 'example-value' => 'ini',
  111. ),
  112. 'core-quick-drupal' => array(
  113. 'description' => 'Return project info for use by core-quick-drupal.',
  114. 'hidden' => TRUE,
  115. ),
  116. ),
  117. 'engines' => array('release_info'),
  118. 'topics' => array('docs-make', 'docs-make-example'),
  119. );
  120. $items['make-generate'] = array(
  121. 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
  122. 'description' => 'Generate a makefile from the current Drupal site.',
  123. 'examples' => array(
  124. 'drush generate-makefile example.make' => 'Generate a makefile with ALL projects versioned (should a project have a known version number)',
  125. 'drush generate-makefile example.make --exclude-versions' => 'Generate a makefile with NO projects versioned',
  126. 'drush generate-makefile example.make --exclude-versions=drupal,views,cck' => 'Generate a makefile with ALL projects versioned EXCEPT core, Views and CCK',
  127. '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.',
  128. ),
  129. 'options' => array(
  130. 'exclude-versions' => 'Exclude all version numbers (default is include all version numbers) or optionally specify a list of projects to exclude from versioning',
  131. 'include-versions' => 'Include a specific list of projects, while all other projects remain unversioned in the makefile (so implies --exclude-versions)',
  132. 'format' => array(
  133. 'description' => 'The format for generated makefile. Options are "yaml" or "ini". Defaults to "yaml".',
  134. 'example-value' => 'ini',
  135. ),
  136. ),
  137. 'engines' => array('release_info'),
  138. 'aliases' => array('generate-makefile'),
  139. );
  140. $items['make-convert'] = array(
  141. 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  142. 'description' => 'Convert a legacy makefile into YAML format.',
  143. 'arguments' => array(
  144. 'makefile' => 'Filename of the makefile to convert.',
  145. ),
  146. 'options' => array(
  147. 'projects' => $projects,
  148. 'libraries' => $libraries,
  149. ),
  150. 'required-arguments' => TRUE,
  151. 'examples' => array(
  152. 'drush make-convert example.make' => 'Convert example.make to example.make.yml',
  153. ),
  154. );
  155. // Hidden command to build a group of projects.
  156. $items['make-process'] = array(
  157. 'hidden' => TRUE,
  158. 'arguments' => array(
  159. 'directory' => 'The temporary working directory to use',
  160. ),
  161. 'options' => array(
  162. 'projects-location' => 'Name of a temporary file containing json-encoded output of make_projects().',
  163. 'manifest' => 'An array of projects already being processed.',
  164. ),
  165. 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  166. 'engines' => array('release_info'),
  167. );
  168. $items['make-update'] = array(
  169. 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  170. 'description' => 'Process a makefile and outputs an equivalent makefile with projects version resolved to latest available.',
  171. 'arguments' => array(
  172. 'makefile' => 'Filename of the makefile to use for this build.',
  173. ),
  174. 'options' => array(
  175. 'result-file' => array(
  176. 'description' => 'Save to a file. If not provided, the updated makefile will be dumped to stdout.',
  177. 'example-value' => 'updated.make',
  178. ),
  179. 'format' => array(
  180. 'description' => 'The format for generated lockfiles. Options are "yaml" or "ini". Defaults to "yaml".',
  181. 'example-value' => 'ini',
  182. ),
  183. ),
  184. 'engines' => array('release_info', 'update_status'),
  185. );
  186. $items['make-lock'] = array(
  187. 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  188. 'description' => 'Process a makefile and outputs an equivalent makefile with projects version *resolved*. Respects pinned versions.',
  189. 'arguments' => array(
  190. 'makefile' => 'Filename of the makefile to use for this build.',
  191. ),
  192. 'options' => array(
  193. 'result-file' => array(
  194. 'description' => 'Save to a file. If not provided, the lockfile will be dumped to stdout.',
  195. 'example-value' => 'platform.lock',
  196. ),
  197. 'format' => array(
  198. 'description' => 'The format for generated lockfiles. Options are "yaml" or "ini". Defaults to "yaml".',
  199. 'example-value' => 'ini',
  200. ),
  201. ),
  202. 'allow-additional-options' => TRUE,
  203. 'engines' => array('release_info', 'update_status'),
  204. );
  205. // Add docs topic.
  206. $docs_dir = drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH);
  207. $items['docs-make'] = array(
  208. 'description' => 'Drush Make overview with examples',
  209. 'hidden' => TRUE,
  210. 'topic' => TRUE,
  211. 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  212. 'callback' => 'drush_print_file',
  213. 'callback arguments' => array($docs_dir . '/docs/make.md'),
  214. );
  215. $items['docs-make-example'] = array(
  216. 'description' => 'Drush Make example makefile',
  217. 'hidden' => TRUE,
  218. 'topic' => TRUE,
  219. 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  220. 'callback' => 'drush_print_file',
  221. 'callback arguments' => array($docs_dir . '/examples/example.make.yml'),
  222. );
  223. return $items;
  224. }
  225. /**
  226. * Command argument complete callback.
  227. *
  228. * @return array
  229. * Strong glob of files to complete on.
  230. */
  231. function make_make_complete() {
  232. return array(
  233. 'files' => array(
  234. 'directories' => array(
  235. 'pattern' => '*',
  236. 'flags' => GLOB_ONLYDIR,
  237. ),
  238. 'make' => array(
  239. 'pattern' => '*.make',
  240. ),
  241. ),
  242. );
  243. }
  244. /**
  245. * Validation callback for make command.
  246. */
  247. function drush_make_validate($makefile = NULL, $build_path = NULL) {
  248. // Don't validate if --version option is supplied.
  249. if (drush_get_option('version', FALSE)) {
  250. return;
  251. }
  252. if (drush_get_option('shallow-clone', FALSE) && drush_get_option('working-copy', FALSE)) {
  253. return drush_set_error('MAKE_SHALLOW_CLONE_WORKING_COPY_CONFLICT', dt('You cannot use "--shallow-clone" and "--working-copy" options together.'));
  254. }
  255. // Error out if the build path is not valid and --no-build was not supplied.
  256. if (!drush_get_option('no-build', FALSE) && !make_build_path($build_path)) {
  257. return FALSE;
  258. }
  259. }
  260. /**
  261. * Implements drush_hook_pre_COMMAND().
  262. *
  263. * If --version option is supplied, print it and prevent execution of the command.
  264. */
  265. function drush_make_pre_make($makefile = NULL, $build_path = NULL) {
  266. if (drush_get_option('version', FALSE)) {
  267. drush_print(dt('Drush make API version !version', array('!version' => MAKE_API)));
  268. drush_print_pipe(MAKE_API);
  269. // Prevent command execution.
  270. return FALSE;
  271. }
  272. }
  273. /**
  274. * Drush callback; make based on the makefile.
  275. */
  276. function drush_make($makefile = NULL, $build_path = NULL) {
  277. // Set the cache option based on our '--no-cache' option.
  278. _make_enable_cache();
  279. // Build.
  280. if (!drush_get_option('no-build', FALSE)) {
  281. $info = make_parse_info_file($makefile);
  282. drush_log(dt('Beginning to build !makefile.', array('!makefile' => $makefile)), LogLevel::OK);
  283. // Default contrib destination depends on Drupal core version.
  284. $core_version = str_replace('.x', '', $info['core'][0]);
  285. $sitewide = drush_drupal_sitewide_directory($core_version);
  286. $contrib_destination = drush_get_option('contrib-destination', $sitewide);
  287. $build_path = make_build_path($build_path);
  288. $make_dir = realpath(dirname($makefile));
  289. $success = make_projects(FALSE, $contrib_destination, $info, $build_path, $make_dir);
  290. if ($success) {
  291. make_libraries(FALSE, $contrib_destination, $info, $build_path, $make_dir);
  292. if (drush_get_option('prepare-install')) {
  293. make_prepare_install($build_path);
  294. }
  295. if ($option = drush_get_option('md5')) {
  296. $md5 = make_md5();
  297. if ($option === 'print') {
  298. drush_print($md5);
  299. }
  300. else {
  301. drush_log(dt('Build hash: %md5', array('%md5' => $md5)), LogLevel::OK);
  302. }
  303. }
  304. // Only take final build steps if not in testing mode.
  305. if (!drush_get_option('test')) {
  306. if (drush_get_option('tar')) {
  307. make_tar($build_path);
  308. }
  309. else {
  310. make_move_build($build_path);
  311. }
  312. }
  313. make_clean_tmp();
  314. }
  315. else {
  316. return make_error('MAKE_PROJECTS_FAILED', dt('Drush Make failed to download all projects. See the log above for the specific errors.'));
  317. }
  318. }
  319. // Process --lock and --bundle-lockfile
  320. $lockfiles = array();
  321. if ($result_file = drush_get_option('bundle-lockfile', FALSE)) {
  322. if ($result_file === TRUE) {
  323. $result_file = 'sites/all/drush/platform.lock';
  324. }
  325. $lockfiles[] = $build_path . '/' . $result_file;
  326. }
  327. if ($result_file = drush_get_option('lock', FALSE)) {
  328. $lockfiles[] = $result_file;
  329. }
  330. if (count($lockfiles)) {
  331. foreach ($lockfiles as $lockfile) {
  332. if ($lockfile !== TRUE) {
  333. $result_file = drush_normalize_path($lockfile);
  334. drush_mkdir(dirname($result_file), $required = TRUE);
  335. drush_set_option('result-file', $result_file);
  336. }
  337. drush_invoke('make-lock', $makefile);
  338. drush_unset_option('result-file');
  339. }
  340. }
  341. // Used by core-quick-drupal command.
  342. // @see drush_core_quick_drupal().
  343. if (drush_get_option('core-quick-drupal', FALSE)) {
  344. return $info;
  345. }
  346. }
  347. /**
  348. * Command callback; convert ini makefile to YAML.
  349. */
  350. function drush_make_convert($makefile) {
  351. drush_log(dt('Beginning to convert !makefile.', array('!makefile' => $makefile)), LogLevel::OK);
  352. $file = $makefile . '.yml';
  353. $info = make_parse_info_file($makefile);
  354. // Remove incorrect value.
  355. unset($info['format']);
  356. $dumper = drush_load_engine('outputformat', 'yaml');
  357. $yaml = $dumper->format($info, array());
  358. if (file_put_contents($file, $yaml)) {
  359. drush_log(dt("Wrote make file @file", array('@file' => $file)), LogLevel::OK);
  360. return $file;
  361. }
  362. else {
  363. make_error('FILE_ERROR', dt("Unable to write file !file", array('!file' => $file)));
  364. }
  365. }
  366. /**
  367. * Drush callback: hidden file to process an individual project.
  368. *
  369. * @param string $directory
  370. * Directory where the project is being built.
  371. */
  372. function drush_make_process($directory) {
  373. drush_get_engine('release_info');
  374. // Set the temporary directory.
  375. make_tmp(TRUE, $directory);
  376. if (!$projects_location = drush_get_option('projects-location')) {
  377. return drush_set_error('MAKE-PROCESS', dt('No projects passed to drush_make_process'));
  378. }
  379. $projects = json_decode(file_get_contents($projects_location), TRUE);
  380. $manifest = drush_get_option('manifest', array());
  381. foreach ($projects as $project) {
  382. if ($instance = DrushMakeProject::getInstance($project['type'], $project)) {
  383. $instance->setManifest($manifest);
  384. $instance->make();
  385. }
  386. else {
  387. make_error('PROJECT-TYPE', dt('Non-existent project type %type on project %project.', array('%type' => $project['type'], '%project' => $project['name'])));
  388. }
  389. }
  390. }
  391. /**
  392. * Gather additional data on all projects specified in the make file.
  393. */
  394. function make_prepare_projects($recursion, $info, $contrib_destination = '', $build_path = '', $make_dir = '') {
  395. $release_info = drush_get_engine('release_info');
  396. // Nothing to make if the project list is empty. Maybe complain about it.
  397. if (empty($info['projects'])) {
  398. if (drush_get_option('no-core') || $recursion) {
  399. return TRUE;
  400. }
  401. else {
  402. return drush_set_error('MAKE_NO_CORE', dt('No core project specified.'));
  403. }
  404. }
  405. // Obtain translations to download along with the projects.
  406. $translations = array();
  407. if (isset($info['translations'])) {
  408. $translations = $info['translations'];
  409. }
  410. if ($arg_translations = drush_get_option('translations', FALSE)) {
  411. $translations = array_merge(explode(',', $arg_translations), $translations);
  412. }
  413. // Normalize projects.
  414. $projects = array();
  415. $ignore_checksums = drush_get_option('ignore-checksums');
  416. foreach ($info['projects'] as $key => $project) {
  417. // Merge the known data onto the project info.
  418. $project += array(
  419. 'name' => $key,
  420. 'type' => 'module',
  421. 'core' => $info['core'],
  422. 'translations' => $translations,
  423. 'build_path' => $build_path,
  424. 'contrib_destination' => $contrib_destination,
  425. 'version' => '',
  426. 'location' => drush_get_option('make-update-default-url', ReleaseInfo::DEFAULT_URL),
  427. 'subdir' => '',
  428. 'directory_name' => '',
  429. 'make_directory' => $make_dir,
  430. 'options' => array(),
  431. );
  432. // MD5 Checksum.
  433. if ($ignore_checksums) {
  434. unset($project['download']['md5']);
  435. }
  436. elseif (!empty($project['md5'])) {
  437. $project['download']['md5'] = $project['md5'];
  438. }
  439. // If download components are specified, but not the download
  440. // type, default to git.
  441. if (isset($project['download']) && !isset($project['download']['type'])) {
  442. $project['download']['type'] = 'git';
  443. }
  444. // Localization server.
  445. if (!isset($project['l10n_url']) && ($project['location'] == ReleaseInfo::DEFAULT_URL)) {
  446. $project['l10n_url'] = MAKE_DEFAULT_L10N_SERVER;
  447. }
  448. // Classify projects in core or contrib.
  449. if ($project['type'] == 'core') {
  450. $project['download_type'] = 'core';
  451. }
  452. elseif ($project['location'] != ReleaseInfo::DEFAULT_URL || !isset($project['download'])) {
  453. $request = make_prepare_request($project);
  454. $is_core = $release_info->checkProject($request, 'core');
  455. $project['download_type'] = ($is_core ? 'core' : 'contrib');
  456. $project['type'] = $is_core ? 'core' : $project['type'];
  457. }
  458. else {
  459. $project['download_type'] = ($project['name'] == 'drupal' ? 'core' : 'contrib');
  460. }
  461. $projects[$project['download_type']][$project['name']] = $project;
  462. }
  463. // Verify there're enough cores, but not too many.
  464. $cores = !empty($projects['core']) ? count($projects['core']) : 0;
  465. if (drush_get_option('no-core')) {
  466. unset($projects['core']);
  467. }
  468. elseif ($cores == 0 && !$recursion) {
  469. return drush_set_error('MAKE_NO_CORE', dt('No core project specified.'));
  470. }
  471. elseif ($cores == 1 && $recursion) {
  472. unset($projects['core']);
  473. }
  474. elseif ($cores > 1) {
  475. return drush_set_error('MAKE_MULTIPLE_CORES', dt('More than one core project specified.'));
  476. }
  477. // Set download type = pm for suitable projects.
  478. foreach (array_keys($projects) as $project_type) {
  479. foreach ($projects[$project_type] as $project) {
  480. if (make_project_needs_release_info($project)) {
  481. $request = make_prepare_request($project, $project_type);
  482. $release = $release_info->selectReleaseBasedOnStrategy($request, '', 'ignore');
  483. if ($release === FALSE) {
  484. return FALSE;
  485. }
  486. // Override default project type with data from update service.
  487. if (!isset($info['projects'][$project['name']]['type'])) {
  488. $project['type'] = $release_info->get($request)->getType();
  489. }
  490. if (!isset($project['download'])) {
  491. $project['download'] = array(
  492. 'type' => 'pm',
  493. 'full_version' => $release['version'],
  494. 'download_link' => $release['download_link'],
  495. 'status url' => $request['status url'],
  496. );
  497. }
  498. }
  499. $projects[$project_type][$project['name']] = $project;
  500. }
  501. }
  502. if (!$recursion) {
  503. $projects += array(
  504. 'core' => array(),
  505. 'contrib' => array(),
  506. );
  507. drush_set_option('DRUSH_MAKE_PROJECTS', array_merge($projects['core'], $projects['contrib']));
  508. }
  509. return $projects;
  510. }
  511. /**
  512. * Process all projects specified in the make file.
  513. */
  514. function make_projects($recursion, $contrib_destination, $info, $build_path, $make_dir) {
  515. $projects = make_prepare_projects($recursion, $info, $contrib_destination, $build_path, $make_dir);
  516. // Abort if there was an error processing projects.
  517. if ($projects === FALSE) {
  518. return FALSE;
  519. }
  520. // Core is built in place, rather than using make-process.
  521. if (!empty($projects['core']) && count($projects['core'])) {
  522. $project = current($projects['core']);
  523. $project = DrushMakeProject::getInstance('core', $project);
  524. $project->make();
  525. }
  526. // Process all projects concurrently using make-process.
  527. if (isset($projects['contrib'])) {
  528. $concurrency = drush_get_option('concurrency', 1);
  529. // Generate $concurrency sub-processes to do the actual work.
  530. $invocations = array();
  531. $thread = 0;
  532. foreach ($projects['contrib'] as $project) {
  533. $thread = ++$thread % $concurrency;
  534. // Ensure that we've set this sub-process up.
  535. if (!isset($invocations[$thread])) {
  536. $invocations[$thread] = array(
  537. 'args' => array(
  538. make_tmp(),
  539. ),
  540. 'options' => array(
  541. 'projects' => array(),
  542. ),
  543. 'site' => array(),
  544. );
  545. }
  546. // Add the project to this sub-process.
  547. $invocations[$thread]['options']['projects'][] = $project;
  548. // Add the manifest so recursive downloads do not override projects.
  549. $invocations[$thread]['options']['manifest'] = array_keys($projects['contrib']);
  550. }
  551. if (!empty($invocations)) {
  552. // Backend options.
  553. $backend_options = array(
  554. 'concurrency' => $concurrency,
  555. 'method' => 'POST',
  556. );
  557. // Store projects in temporary files since passing this much data on the
  558. // pipe buffer can break on certain systems.
  559. _make_write_project_json($invocations);
  560. $common_options = drush_redispatch_get_options();
  561. // Merge in stdin options since we process makefiles recursively. See http://drupal.org/node/1510180.
  562. $common_options = array_merge($common_options, drush_get_context('stdin'));
  563. // Package handler should use 'wget'.
  564. $common_options['package-handler'] = 'wget';
  565. // Avoid any prompts from CLI.
  566. $common_options['yes'] = TRUE;
  567. // Use cache unless explicitly turned off.
  568. if (!drush_get_option('no-cache', FALSE)) {
  569. $common_options['cache'] = TRUE;
  570. }
  571. // Unless --verbose or --debug are passed, quiter backend output.
  572. if (empty($common_options['verbose']) && empty($common_options['debug'])) {
  573. $backend_options['#output-label'] = FALSE;
  574. $backend_options['integrate'] = TRUE;
  575. }
  576. $results = drush_backend_invoke_concurrent($invocations, $common_options, $backend_options, 'make-process', '@none');
  577. if (count($results['error_log'])) {
  578. return FALSE;
  579. }
  580. }
  581. }
  582. return TRUE;
  583. }
  584. /**
  585. * Writes out project data to temporary files.
  586. *
  587. * @param array &$invocations
  588. * An array containing projects sorted by thread.
  589. */
  590. function _make_write_project_json(array &$invocations) {
  591. foreach ($invocations as $thread => $info) {
  592. $projects = $info['options']['projects'];
  593. unset($invocations[$thread]['options']['projects']);
  594. $temp_file = drush_tempnam('make_projects');
  595. file_put_contents($temp_file, json_encode($projects));
  596. $invocations[$thread]['options']['projects-location'] = $temp_file;
  597. }
  598. }
  599. /**
  600. * Gather additional data on all libraries specified in the make file.
  601. */
  602. function make_prepare_libraries($recursion, $info, $contrib_destination = '', $build_path = '', $make_dir = '') {
  603. // Nothing to make if the libraries list is empty.
  604. if (empty($info['libraries'])) {
  605. return;
  606. }
  607. $libraries = array();
  608. $ignore_checksums = drush_get_option('ignore-checksums');
  609. foreach ($info['libraries'] as $key => $library) {
  610. if (!is_string($key) || !is_array($library)) {
  611. // TODO Print a prettier message.
  612. continue;
  613. }
  614. // Merge the known data onto the library info.
  615. $library += array(
  616. 'name' => $key,
  617. 'core' => $info['core'],
  618. 'build_path' => $build_path,
  619. 'contrib_destination' => $contrib_destination,
  620. 'subdir' => '',
  621. 'directory_name' => $key,
  622. 'make_directory' => $make_dir,
  623. );
  624. if ($ignore_checksums) {
  625. unset($library['download']['md5']);
  626. }
  627. $libraries[$key] = $library;
  628. }
  629. if (!$recursion) {
  630. drush_set_option('DRUSH_MAKE_LIBRARIES', $info['libraries']);
  631. }
  632. return $libraries;
  633. }
  634. /**
  635. * Process all libraries specified in the make file.
  636. */
  637. function make_libraries($recursion, $contrib_destination, $info, $build_path, $make_dir) {
  638. $libraries = make_prepare_libraries($recursion, $info, $contrib_destination, $build_path, $make_dir);
  639. if (empty($libraries)) {
  640. return;
  641. }
  642. foreach ($libraries as $key => $library) {
  643. $class = DrushMakeProject::getInstance('library', $library);
  644. $class->make();
  645. }
  646. }
  647. /**
  648. * The path where the final build will be placed.
  649. */
  650. function make_build_path($build_path) {
  651. static $saved_path;
  652. if (isset($saved_path)) {
  653. return $saved_path;
  654. }
  655. // Determine the base of the build.
  656. if (drush_get_option('tar')) {
  657. $build_path = dirname($build_path) . '/' . basename($build_path, '.tar.gz') . '.tar.gz';
  658. }
  659. elseif (isset($build_path) && (!empty($build_path) || $build_path == '.')) {
  660. $build_path = rtrim($build_path, '/');
  661. }
  662. // Allow tests to run without a specified base path.
  663. elseif (drush_get_option('test') || drush_confirm(dt("Make new site in the current directory?"))) {
  664. $build_path = '.';
  665. }
  666. else {
  667. return drush_user_abort(dt('Build aborted.'));
  668. }
  669. if ($build_path != '.' && file_exists($build_path) && !drush_get_option('no-core', FALSE)) {
  670. return drush_set_error('MAKE_PATH_EXISTS', dt('Base path %path already exists.', array('%path' => $build_path)));
  671. }
  672. $saved_path = $build_path;
  673. return $build_path;
  674. }
  675. /**
  676. * Move the completed build into place.
  677. */
  678. function make_move_build($build_path) {
  679. $tmp_path = make_tmp();
  680. $ret = TRUE;
  681. if ($build_path == '.' || (drush_get_option('no-core', FALSE) && file_exists($build_path))) {
  682. $info = drush_scan_directory($tmp_path . DIRECTORY_SEPARATOR . '__build__', '/./', array('.', '..'), 0, FALSE, 'filename', 0, TRUE);
  683. foreach ($info as $file) {
  684. $destination = $build_path . DIRECTORY_SEPARATOR . $file->basename;
  685. if (file_exists($destination)) {
  686. // To prevent the removal of top-level directories such as 'modules' or
  687. // 'themes', descend in a level if the file exists.
  688. // TODO: This only protects one level of directories from being removed.
  689. $overwrite = drush_get_option('overwrite', FALSE) ? FILE_EXISTS_OVERWRITE : FILE_EXISTS_MERGE;
  690. if (is_dir($destination)) {
  691. $files = drush_scan_directory($file->filename, '/./', array('.', '..'), 0, FALSE);
  692. foreach ($files as $file) {
  693. $ret = $ret && drush_copy_dir($file->filename, $destination . DIRECTORY_SEPARATOR . $file->basename, $overwrite);
  694. }
  695. }
  696. else {
  697. $ret = $ret && drush_copy_dir($file->filename, $destination, $overwrite);
  698. }
  699. }
  700. else {
  701. $ret = $ret && drush_copy_dir($file->filename, $destination);
  702. }
  703. }
  704. }
  705. else {
  706. drush_mkdir(dirname($build_path));
  707. $ret = drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . '__build__', $tmp_path . DIRECTORY_SEPARATOR . basename($build_path), TRUE);
  708. $ret = $ret && drush_copy_dir($tmp_path . DIRECTORY_SEPARATOR . basename($build_path), $build_path);
  709. }
  710. // Copying to final destination resets write permissions. Re-apply.
  711. if (drush_get_option('prepare-install')) {
  712. $default = $build_path . '/sites/default';
  713. chmod($default . '/settings.php', 0666);
  714. chmod($default . '/files', 0777);
  715. }
  716. if (!$ret) {
  717. return drush_set_error('MAKE_CANNOT_MOVE_BUILD', dt("Cannot move build into place."));
  718. }
  719. return $ret;
  720. }
  721. /**
  722. * Create a request array suitable for release_info engine.
  723. *
  724. * This is a convenience function to easily integrate drush_make
  725. * with drush release_info engine.
  726. *
  727. * @todo: refactor 'make' to internally work with release_info keys.
  728. *
  729. * @param array $project
  730. * Project array.
  731. * @param string $type
  732. * 'contrib' or 'core'.
  733. */
  734. function make_prepare_request($project, $type = 'contrib') {
  735. $request = array(
  736. 'name' => $project['name'],
  737. 'drupal_version' => $project['core'],
  738. 'status url' => $project['location'],
  739. );
  740. if ($project['version'] != '') {
  741. $request['project_version'] = $project['version'];
  742. $request['version'] = $type == 'core' ? $project['version'] : $project['core'] . '-' . $project['version'];
  743. }
  744. return $request;
  745. }
  746. /**
  747. * Determine if the release information is required for this
  748. * project. When it is determined that it is, this potentially results
  749. * in the use of pm-download to process the project.
  750. *
  751. * If the location of the project is not customized (uses d.o), and
  752. * one of the following is true, then release information is required:
  753. *
  754. * - $project['type'] has not been specified
  755. * - $project['download'] has not been specified
  756. *
  757. * @see make_projects()
  758. */
  759. function make_project_needs_release_info($project) {
  760. return isset($project['location'])
  761. // Only fetch release info if the project type is unknown OR if
  762. // download attributes are unspecified.
  763. && (!isset($project['type']) || !isset($project['download']));
  764. }
  765. /**
  766. * Enables caching if not explicitly disabled.
  767. *
  768. * @return bool
  769. * The previous value of the 'cache' option.
  770. */
  771. function _make_enable_cache() {
  772. $cache_before = drush_get_option('cache');
  773. if (!drush_get_option('no-cache', FALSE)) {
  774. drush_set_option('cache', TRUE);
  775. }
  776. return $cache_before;
  777. }