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 ascending Description
_make_write_project_json Writes out project data to temporary files.
_make_enable_cache Enables caching if not explicitly disabled.
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_projects Process all projects specified in the make file.
make_prepare_request Create a request array suitable for release_info engine.
make_prepare_projects Gather additional data on all projects specified in the make file.
make_prepare_libraries Gather additional data on all libraries specified in the make file.
make_move_build Move the completed build into place.
make_make_complete Command argument complete callback.
make_libraries Process all libraries specified in the make file.
make_drush_help Implements hook_drush_help().
make_drush_command Implements hook_drush_command().
make_build_path The path where the final build will be placed.
drush_make_validate Validation callback for make command.
drush_make_process Drush callback: hidden file to process an individual project.
drush_make_pre_make Implements drush_hook_pre_COMMAND().
drush_make_convert_project_to_composer Converts a make file project array into a composer project version string.
drush_make_convert_make_to_yml Converts a drush info array to a YAML array.
drush_make_convert_make_to_composer Converts a drush info array to a composer.json array.
drush_make_convert_composer_to_make Converts a composer.lock array into a traditional drush make array.
drush_make_convert Command callback; convert make file format.
drush_make Drush callback; make based on the makefile.

Constants

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

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. 'includes' => 'A list of makefiles to include at build-time.',
  117. 'overrides' => 'A list of makefiles to that can override values in other makefiles.',
  118. ),
  119. 'engines' => array('release_info'),
  120. 'topics' => array('docs-make', 'docs-make-example'),
  121. );
  122. $items['make-generate'] = array(
  123. 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
  124. 'description' => 'Generate a makefile from the current Drupal site.',
  125. 'examples' => array(
  126. 'drush generate-makefile example.make' => 'Generate a makefile with ALL projects versioned (should a project have a known version number)',
  127. 'drush generate-makefile example.make --exclude-versions' => 'Generate a makefile with NO projects versioned',
  128. 'drush generate-makefile example.make --exclude-versions=drupal,views,cck' => 'Generate a makefile with ALL projects versioned EXCEPT core, Views and CCK',
  129. '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.',
  130. ),
  131. 'options' => array(
  132. 'exclude-versions' => 'Exclude all version numbers (default is include all version numbers) or optionally specify a list of projects to exclude from versioning',
  133. 'include-versions' => 'Include a specific list of projects, while all other projects remain unversioned in the makefile (so implies --exclude-versions)',
  134. 'format' => array(
  135. 'description' => 'The format for generated makefile. Options are "yaml" or "ini". Defaults to "yaml".',
  136. 'example-value' => 'ini',
  137. ),
  138. ),
  139. 'engines' => array('release_info'),
  140. 'aliases' => array('generate-makefile'),
  141. );
  142. $items['make-convert'] = array(
  143. 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  144. 'description' => 'Convert a legacy makefile into another format. Defaults to converting .make => .make.yml.',
  145. 'arguments' => array(
  146. 'makefile' => 'Filename of the makefile to convert.',
  147. ),
  148. 'options' => array(
  149. 'format' => 'The format to which the make file should be converted. Accepted values include make, composer, and yml.',
  150. ),
  151. 'required-arguments' => TRUE,
  152. 'examples' => array(
  153. 'drush make-convert example.make --format=composer > composer.json' => 'Convert example.make to composer.json',
  154. 'drush make-convert example.make --format=yml > example.make.yml' => 'Convert example.make to example.make.yml',
  155. 'drush make-convert composer.lock --format=make > example.make' => 'Convert composer.lock example.make',
  156. ),
  157. );
  158. // Hidden command to build a group of projects.
  159. $items['make-process'] = array(
  160. 'hidden' => TRUE,
  161. 'arguments' => array(
  162. 'directory' => 'The temporary working directory to use',
  163. ),
  164. 'options' => array(
  165. 'projects-location' => 'Name of a temporary file containing json-encoded output of make_projects().',
  166. 'manifest' => 'An array of projects already being processed.',
  167. ),
  168. 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  169. 'engines' => array('release_info'),
  170. );
  171. $items['make-update'] = array(
  172. 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  173. 'description' => 'Process a makefile and outputs an equivalent makefile with projects version resolved to latest available.',
  174. 'arguments' => array(
  175. 'makefile' => 'Filename of the makefile to use for this build.',
  176. ),
  177. 'options' => array(
  178. 'result-file' => array(
  179. 'description' => 'Save to a file. If not provided, the updated makefile will be dumped to stdout.',
  180. 'example-value' => 'updated.make',
  181. ),
  182. 'format' => array(
  183. 'description' => 'The format for generated lockfiles. Options are "yaml" or "ini". Defaults to "yaml".',
  184. 'example-value' => 'ini',
  185. ),
  186. 'includes' => 'A list of makefiles to include at build-time.',
  187. ),
  188. 'engines' => array('release_info', 'update_status'),
  189. );
  190. $items['make-lock'] = array(
  191. 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  192. 'description' => 'Process a makefile and outputs an equivalent makefile with projects version *resolved*. Respects pinned versions.',
  193. 'arguments' => array(
  194. 'makefile' => 'Filename of the makefile to use for this build.',
  195. ),
  196. 'options' => array(
  197. 'result-file' => array(
  198. 'description' => 'Save to a file. If not provided, the lockfile will be dumped to stdout.',
  199. 'example-value' => 'platform.lock',
  200. ),
  201. 'format' => array(
  202. 'description' => 'The format for generated lockfiles. Options are "yaml" or "ini". Defaults to "yaml".',
  203. 'example-value' => 'ini',
  204. ),
  205. 'includes' => 'A list of makefiles to include at build-time.',
  206. ),
  207. 'allow-additional-options' => TRUE,
  208. 'engines' => array('release_info', 'update_status'),
  209. );
  210. // Add docs topic.
  211. $docs_dir = drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH);
  212. $items['docs-make'] = array(
  213. 'description' => 'Drush Make overview with examples',
  214. 'hidden' => TRUE,
  215. 'topic' => TRUE,
  216. 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  217. 'callback' => 'drush_print_file',
  218. 'callback arguments' => array($docs_dir . '/docs/make.md'),
  219. );
  220. $items['docs-make-example'] = array(
  221. 'description' => 'Drush Make example makefile',
  222. 'hidden' => TRUE,
  223. 'topic' => TRUE,
  224. 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  225. 'callback' => 'drush_print_file',
  226. 'callback arguments' => array($docs_dir . '/examples/example.make.yml'),
  227. );
  228. return $items;
  229. }
  230. /**
  231. * Command argument complete callback.
  232. *
  233. * @return array
  234. * Strong glob of files to complete on.
  235. */
  236. function make_make_complete() {
  237. return array(
  238. 'files' => array(
  239. 'directories' => array(
  240. 'pattern' => '*',
  241. 'flags' => GLOB_ONLYDIR,
  242. ),
  243. 'make' => array(
  244. 'pattern' => '*.make',
  245. ),
  246. ),
  247. );
  248. }
  249. /**
  250. * Validation callback for make command.
  251. */
  252. function drush_make_validate($makefile = NULL, $build_path = NULL) {
  253. // Don't validate if --version option is supplied.
  254. if (drush_get_option('version', FALSE)) {
  255. return;
  256. }
  257. if (drush_get_option('shallow-clone', FALSE) && drush_get_option('working-copy', FALSE)) {
  258. return drush_set_error('MAKE_SHALLOW_CLONE_WORKING_COPY_CONFLICT', dt('You cannot use "--shallow-clone" and "--working-copy" options together.'));
  259. }
  260. // Error out if the build path is not valid and --no-build was not supplied.
  261. if (!drush_get_option('no-build', FALSE) && !make_build_path($build_path)) {
  262. return FALSE;
  263. }
  264. }
  265. /**
  266. * Implements drush_hook_pre_COMMAND().
  267. *
  268. * If --version option is supplied, print it and prevent execution of the command.
  269. */
  270. function drush_make_pre_make($makefile = NULL, $build_path = NULL) {
  271. if (drush_get_option('version', FALSE)) {
  272. drush_print(dt('Drush make API version !version', array('!version' => MAKE_API)));
  273. drush_print_pipe(MAKE_API);
  274. // Prevent command execution.
  275. return FALSE;
  276. }
  277. }
  278. /**
  279. * Drush callback; make based on the makefile.
  280. */
  281. function drush_make($makefile = NULL, $build_path = NULL) {
  282. // Set the cache option based on our '--no-cache' option.
  283. _make_enable_cache();
  284. // Build.
  285. if (!drush_get_option('no-build', FALSE)) {
  286. $info = make_parse_info_file($makefile);
  287. drush_log(dt('Beginning to build !makefile.', array('!makefile' => $makefile)), LogLevel::OK);
  288. // Default contrib destination depends on Drupal core version.
  289. $core_version = str_replace('.x', '', $info['core'][0]);
  290. $sitewide = drush_drupal_sitewide_directory($core_version);
  291. $contrib_destination = drush_get_option('contrib-destination', $sitewide);
  292. $build_path = make_build_path($build_path);
  293. $make_dir = realpath(dirname($makefile));
  294. $success = make_projects(FALSE, $contrib_destination, $info, $build_path, $make_dir);
  295. if ($success) {
  296. make_libraries(FALSE, $contrib_destination, $info, $build_path, $make_dir);
  297. if (drush_get_option('prepare-install')) {
  298. make_prepare_install($build_path);
  299. }
  300. if ($option = drush_get_option('md5')) {
  301. $md5 = make_md5();
  302. if ($option === 'print') {
  303. drush_print($md5);
  304. }
  305. else {
  306. drush_log(dt('Build hash: %md5', array('%md5' => $md5)), LogLevel::OK);
  307. }
  308. }
  309. // Only take final build steps if not in testing mode.
  310. if (!drush_get_option('test')) {
  311. if (drush_get_option('tar')) {
  312. make_tar($build_path);
  313. }
  314. else {
  315. make_move_build($build_path);
  316. }
  317. }
  318. make_clean_tmp();
  319. }
  320. else {
  321. return make_error('MAKE_PROJECTS_FAILED', dt('Drush Make failed to download all projects. See the log above for the specific errors.'));
  322. }
  323. }
  324. // Process --lock and --bundle-lockfile
  325. $lockfiles = array();
  326. if ($result_file = drush_get_option('bundle-lockfile', FALSE)) {
  327. if ($result_file === TRUE) {
  328. $result_file = 'sites/all/drush/platform.make';
  329. }
  330. $lockfiles[] = $build_path . '/' . $result_file;
  331. }
  332. if ($result_file = drush_get_option('lock', FALSE)) {
  333. $lockfiles[] = $result_file;
  334. }
  335. if (count($lockfiles)) {
  336. foreach ($lockfiles as $lockfile) {
  337. if ($lockfile !== TRUE) {
  338. $result_file = drush_normalize_path($lockfile);
  339. drush_mkdir(dirname($result_file), $required = TRUE);
  340. drush_set_option('result-file', $result_file);
  341. }
  342. drush_invoke('make-lock', $makefile);
  343. drush_unset_option('result-file');
  344. }
  345. }
  346. // Used by core-quick-drupal command.
  347. // @see drush_core_quick_drupal().
  348. if (drush_get_option('core-quick-drupal', FALSE)) {
  349. return $info;
  350. }
  351. }
  352. /**
  353. * Command callback; convert make file format.
  354. */
  355. function drush_make_convert($source) {
  356. $dest_format = drush_get_option('format', 'yml');
  357. // Load source data.
  358. $source_format = pathinfo($source, PATHINFO_EXTENSION);
  359. if ($source_format == $dest_format || $source_format == 'lock' && $dest_format == 'composer') {
  360. drush_print('The source format cannot be the same as the destination format.');
  361. }
  362. // Obtain drush make $info array, converting if necessary.
  363. switch ($source_format) {
  364. case 'make':
  365. case 'yml':
  366. case 'yaml':
  367. $info = make_parse_info_file($source);
  368. break;
  369. case 'lock':
  370. $composer_json_file = str_replace('lock', 'json', $source);
  371. if (!file_exists($composer_json_file)) {
  372. drush_print('Please ensure that a composer.json file is in the same directory as the specified composer.lock file.');
  373. return FALSE;
  374. }
  375. $composer_json = json_decode(make_get_data($composer_json_file), TRUE);
  376. $composer_lock = json_decode(make_get_data($source), TRUE);
  377. $info = drush_make_convert_composer_to_make($composer_lock, $composer_json);
  378. break;
  379. case 'json':
  380. drush_print('Please use composer.lock instead of composer.json as source for conversion.');
  381. return FALSE;
  382. break;
  383. }
  384. // Output into destination formation.
  385. switch ($dest_format) {
  386. case 'yml':
  387. case 'yaml':
  388. $output = drush_make_convert_make_to_yml($info);
  389. break;
  390. case 'make':
  391. foreach ($info['projects'] as $key => $project) {
  392. $info['projects'][$key]['_type'] = $info['projects'][$key]['type'];
  393. }
  394. foreach ($info['libraries'] as $key => $library) {
  395. $info['libraries'][$key]['_type'] = 'librarie';
  396. }
  397. $output = _drush_make_generate_makefile_contents($info['projects'], $info['libraries'], $info['core'], $info['defaults']);
  398. break;
  399. case 'composer':
  400. $output = drush_make_convert_make_to_composer($info);
  401. break;
  402. }
  403. drush_print($output);
  404. }
  405. /**
  406. * Converts a drush info array to a YAML array.
  407. *
  408. * @param array $info
  409. * A drush make info array.
  410. *
  411. * @return string
  412. * A yaml encoded info array.
  413. */
  414. function drush_make_convert_make_to_yml($info) {
  415. // Remove incorrect value.
  416. unset($info['format']);
  417. // Replace "*" with "~" for project versions.
  418. foreach ($info['projects'] as $key => $project) {
  419. if ($project['version'] == '*') {
  420. $info['projects'][$key]['version'] = '~';
  421. }
  422. }
  423. $dumper = drush_load_engine('outputformat', 'yaml');
  424. $output = $dumper->format($info, array());
  425. return $output;
  426. }
  427. /**
  428. * Converts a drush info array to a composer.json array.
  429. *
  430. * @param array $info
  431. * A drush make info array.
  432. *
  433. * @return string
  434. * A json encoded composer.json schema object.
  435. */
  436. function drush_make_convert_make_to_composer($info) {
  437. $core_major_version = substr($info['core'], 0, 1);
  438. $core_project_name = $core_major_version == 7 ? 'drupal/drupal' : 'drupal/core';
  439. // Add default projects.
  440. $projects = array(
  441. 'composer/installers' => '^1.0.20',
  442. 'cweagans/composer-patches' => '~1.0',
  443. $core_project_name => str_replace('x', '*', $info['core']),
  444. );
  445. $patches = array();
  446. // Iterate over projects, populating composer-friendly array.
  447. foreach ($info['projects'] as $project_name => $project) {
  448. switch ($project['type']) {
  449. case 'core':
  450. $project['name'] = 'drupal/core';
  451. $projects[$project['name']] = str_replace('x', '*', $project['version']);
  452. break;
  453. default:
  454. $project['name'] = "drupal/$project_name";
  455. $projects[$project['name']] = drush_make_convert_project_to_composer($project, $core_major_version);
  456. break;
  457. }
  458. // Add project patches.
  459. if (!empty($project['patch'])) {
  460. foreach($project['patch'] as $key => $patch) {
  461. $patch_description = "Enter {$project['name']} patch #$key description here";
  462. $patches[$project['name']][$patch_description] = $patch;
  463. }
  464. }
  465. }
  466. // Iterate over libraries, populating composer-friendly array.
  467. if (!empty($info['libraries'])) {
  468. foreach ($info['libraries'] as $library_name => $library) {
  469. $library_name = 'Verify project name: ' . $library_name;
  470. $projects[$library_name] = drush_make_convert_project_to_composer($library);
  471. }
  472. }
  473. $output = array(
  474. 'name' => 'Enter project name here',
  475. 'description' => 'Enter project description here',
  476. 'type' => 'project',
  477. 'repositories' => array(
  478. array('type' => 'composer', 'url' => 'https://packagist.drupal-composer.org'),
  479. ),
  480. 'require' => $projects,
  481. 'minimum-stability' => 'dev',
  482. 'prefer-stable' => TRUE,
  483. 'extra' => array(
  484. 'installer-paths' => array(
  485. 'core' => array('type:drupal-core'),
  486. 'docroot/modules/contrib/{$name}' => array('type:drupal-module'),
  487. 'docroot/profiles/contrib/{$name}' => array('type:drupal-profile'),
  488. 'docroot/themes/contrib/{$name}' => array('type:drupal-theme'),
  489. 'drush/contrib/{$name}' => array('type:drupal-drush'),
  490. ),
  491. 'patches' => $patches,
  492. ),
  493. );
  494. $output = json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
  495. return $output;
  496. }
  497. /**
  498. * Converts a make file project array into a composer project version string.
  499. *
  500. * @param array $original_project
  501. * A project dependency, as defined in a make file.
  502. *
  503. * @param string $core_major_version
  504. * The major core version. E.g., 6, 7, 8, etc.
  505. *
  506. * @return string
  507. * The project version, in composer syntax.
  508. *
  509. */
  510. function drush_make_convert_project_to_composer($original_project, $core_major_version) {
  511. // Typical specified version with major version "x" removed.
  512. if (!empty($original_project['version'])) {
  513. $version = str_replace('x', '0', $original_project['version']);
  514. }
  515. // Git branch or revision.
  516. elseif (!empty($original_project['download'])) {
  517. switch ($original_project['download']['type']) {
  518. case 'git':
  519. if (!empty($original_project['download']['branch'])) {
  520. // @todo Determine if '0' will always be correct.
  521. $version = str_replace('x', '0', $original_project['download']['branch']);
  522. }
  523. if (!empty($original_project['download']['tag'])) {
  524. // @todo Determine if '0' will always be correct.
  525. $version = str_replace('x', '0', $original_project['download']['tag']);
  526. }
  527. if (!empty($project['download']['revision'])) {
  528. $version .= '#' . $original_project['download']['revision'];
  529. }
  530. break;
  531. default:
  532. $version = 'Enter correct project name and version number';
  533. break;
  534. }
  535. }
  536. $version = "$core_major_version." . $version;
  537. return $version;
  538. }
  539. /**
  540. * Converts a composer.lock array into a traditional drush make array.
  541. *
  542. * @param array $composer_lock
  543. * An array of composer.lock data.
  544. *
  545. * @param array $composer_json
  546. * An array of composer.json data.
  547. *
  548. * @return array A traditional drush make info array.
  549. * A traditional drush make info array.
  550. */
  551. function drush_make_convert_composer_to_make($composer_lock, $composer_json) {
  552. $info = array(
  553. 'core' => array(),
  554. 'api' => 2,
  555. 'defaults' => array(
  556. 'projects' => array(
  557. 'subdir' => 'contrib',
  558. ),
  559. ),
  560. 'projects' => array(),
  561. 'libraries' => array(),
  562. );
  563. // The make generation function requires that projects be grouped by type,
  564. // or else duplicative project groups will be created.
  565. $core = array();
  566. $modules = array();
  567. $themes = array();
  568. $profiles = array();
  569. $libraries = array();
  570. foreach ($composer_lock['packages'] as $key => $package) {
  571. if (strpos($package['name'], 'drupal/') === 0 && in_array($package['type'], array('drupal-core', 'drupal-theme', 'drupal-module', 'drupal-profile'))) {
  572. $project_name = str_replace('drupal/', '', $package['name']);
  573. switch ($package['type']) {
  574. case 'drupal-core':
  575. $project_name = 'drupal';
  576. $group =& $core;
  577. $group[$project_name]['type'] = 'core';
  578. $info['core'] = substr($package['version'], 0, 1) . '.x';
  579. break;
  580. case 'drupal-theme':
  581. $group =& $themes;
  582. $group[$project_name]['type'] = 'theme';
  583. break;
  584. case 'drupal-module':
  585. $group =& $modules;
  586. $group[$project_name]['type'] = 'module';
  587. break;
  588. case 'drupal-profile':
  589. $group =& $profiles;
  590. $group[$project_name]['type'] = 'profile';
  591. break;
  592. }
  593. $group[$project_name]['download']['type'] = 'git';
  594. $group[$project_name]['download']['url'] = $package['source']['url'];
  595. // Dev versions should use git branch + revision, otherwise a tag is used.
  596. if (strstr($package['version'], 'dev')) {
  597. // 'dev-' prefix indicates a branch-alias. Stripping the dev prefix from
  598. // the branch name is sufficient.
  599. // @see https://getcomposer.org/doc/articles/aliases.md
  600. if (strpos($package['version'], 'dev-') === 0) {
  601. $group[$project_name]['download']['branch'] = substr($package['version'], 4);
  602. }
  603. // Otherwise, leave as is. Version may already use '-dev' suffix.
  604. else {
  605. $group[$project_name]['download']['branch'] = $package['version'];
  606. }
  607. $group[$project_name]['download']['revision'] = $package['source']['reference'];
  608. }
  609. elseif ($package['type'] == 'drupal-core') {
  610. // For 7.x tags, replace 7.xx.0 with 7.xx.
  611. if ($info['core'] == '7.x') {
  612. $group[$project_name]['download']['tag']= substr($package['version'], 0, 4);
  613. }
  614. else {
  615. $group[$project_name]['download']['tag'] = $package['version'];
  616. }
  617. }
  618. else {
  619. // Make tag versioning drupal-friendly. 8.1.0-alpha1 => 8.x-1.0-alpha1.
  620. $major_version = substr($package['version'], 0 ,1);
  621. $the_rest = substr($package['version'], 2, strlen($package['version']));
  622. $group[$project_name]['download']['tag'] = "$major_version.x-$the_rest";
  623. }
  624. if (!empty($package['extra']['patches_applied'])) {
  625. foreach ($package['extra']['patches_applied'] as $desc => $url) {
  626. $group[$project_name]['patch'][] = $url;
  627. }
  628. }
  629. }
  630. // Include any non-drupal libraries that exist in both .lock and .json.
  631. elseif (!in_array($package['type'], array('composer-plugin', 'metapackage'))
  632. && array_key_exists($package['name'], $composer_json['require'])) {
  633. $project_name = $package['name'];
  634. $libraries[$project_name]['type'] = 'library';
  635. $libraries[$project_name]['download']['type'] = 'git';
  636. $libraries[$project_name]['download']['url'] = $package['source']['url'];
  637. $libraries[$project_name]['download']['branch'] = $package['version'];
  638. $libraries[$project_name]['download']['revision'] = $package['source']['reference'];
  639. }
  640. }
  641. $info['projects'] = $core + $modules + $themes;
  642. $info['libraries'] = $libraries;
  643. return $info;
  644. }
  645. /**
  646. * Drush callback: hidden file to process an individual project.
  647. *
  648. * @param string $directory
  649. * Directory where the project is being built.
  650. */
  651. function drush_make_process($directory) {
  652. drush_get_engine('release_info');
  653. // Set the temporary directory.
  654. make_tmp(TRUE, $directory);
  655. if (!$projects_location = drush_get_option('projects-location')) {
  656. return drush_set_error('MAKE-PROCESS', dt('No projects passed to drush_make_process'));
  657. }
  658. $projects = json_decode(file_get_contents($projects_location), TRUE);
  659. $manifest = drush_get_option('manifest', array());
  660. foreach ($projects as $project) {
  661. if ($instance = DrushMakeProject::getInstance($project['type'], $project)) {
  662. $instance->setManifest($manifest);
  663. $instance->make();
  664. }
  665. else {
  666. make_error('PROJECT-TYPE', dt('Non-existent project type %type on project %project.', array('%type' => $project['type'], '%project' => $project['name'])));
  667. }
  668. }
  669. }
  670. /**
  671. * Gather additional data on all projects specified in the make file.
  672. */
  673. function make_prepare_projects($recursion, $info, $contrib_destination = '', $build_path = '', $make_dir = '') {
  674. $release_info = drush_get_engine('release_info');
  675. // Nothing to make if the project list is empty. Maybe complain about it.
  676. if (empty($info['projects'])) {
  677. if (drush_get_option('no-core') || $recursion) {
  678. return TRUE;
  679. }
  680. else {
  681. return drush_set_error('MAKE_NO_CORE', dt('No core project specified.'));
  682. }
  683. }
  684. // Obtain translations to download along with the projects.
  685. $translations = array();
  686. if (isset($info['translations'])) {
  687. $translations = $info['translations'];
  688. }
  689. if ($arg_translations = drush_get_option('translations', FALSE)) {
  690. $translations = array_merge(explode(',', $arg_translations), $translations);
  691. }
  692. // Normalize projects.
  693. $projects = array();
  694. $ignore_checksums = drush_get_option('ignore-checksums');
  695. foreach ($info['projects'] as $key => $project) {
  696. // Merge the known data onto the project info.
  697. $project += array(
  698. 'name' => $key,
  699. 'type' => 'module',
  700. 'core' => $info['core'],
  701. 'translations' => $translations,
  702. 'build_path' => $build_path,
  703. 'contrib_destination' => $contrib_destination,
  704. 'version' => '',
  705. 'location' => drush_get_option('make-update-default-url', ReleaseInfo::DEFAULT_URL),
  706. 'subdir' => '',
  707. 'directory_name' => '',
  708. 'make_directory' => $make_dir,
  709. 'options' => array(),
  710. );
  711. // MD5 Checksum.
  712. if ($ignore_checksums) {
  713. unset($project['download']['md5']);
  714. }
  715. elseif (!empty($project['md5'])) {
  716. $project['download']['md5'] = $project['md5'];
  717. }
  718. // If download components are specified, but not the download
  719. // type, default to git.
  720. if (isset($project['download']) && !isset($project['download']['type'])) {
  721. $project['download']['type'] = 'git';
  722. }
  723. // Localization server.
  724. if (!isset($project['l10n_url']) && ($project['location'] == ReleaseInfo::DEFAULT_URL)) {
  725. $project['l10n_url'] = MAKE_DEFAULT_L10N_SERVER;
  726. }
  727. // Classify projects in core or contrib.
  728. if ($project['type'] == 'core') {
  729. $project['download_type'] = 'core';
  730. }
  731. elseif ($project['location'] != ReleaseInfo::DEFAULT_URL || !isset($project['download'])) {
  732. $request = make_prepare_request($project);
  733. $is_core = $release_info->checkProject($request, 'core');
  734. $project['download_type'] = ($is_core ? 'core' : 'contrib');
  735. $project['type'] = $is_core ? 'core' : $project['type'];
  736. }
  737. else {
  738. $project['download_type'] = ($project['name'] == 'drupal' ? 'core' : 'contrib');
  739. }
  740. $projects[$project['download_type']][$project['name']] = $project;
  741. }
  742. // Verify there're enough cores, but not too many.
  743. $cores = !empty($projects['core']) ? count($projects['core']) : 0;
  744. if (drush_get_option('no-core')) {
  745. unset($projects['core']);
  746. }
  747. elseif ($cores == 0 && !$recursion) {
  748. return drush_set_error('MAKE_NO_CORE', dt('No core project specified.'));
  749. }
  750. elseif ($cores == 1 && $recursion) {
  751. unset($projects['core']);
  752. }
  753. elseif ($cores > 1) {
  754. return drush_set_error('MAKE_MULTIPLE_CORES', dt('More than one core project specified.'));
  755. }
  756. // Set download type = pm for suitable projects.
  757. foreach (array_keys($projects) as $project_type) {
  758. foreach ($projects[$project_type] as $project) {
  759. if (make_project_needs_release_info($project)) {
  760. $request = make_prepare_request($project, $project_type);
  761. $release = $release_info->selectReleaseBasedOnStrategy($request, '', 'ignore');
  762. if ($release === FALSE) {
  763. return FALSE;
  764. }
  765. // Override default project type with data from update service.
  766. if (!isset($info['projects'][$project['name']]['type'])) {
  767. $project['type'] = $release_info->get($request)->getType();
  768. }
  769. if (!isset($project['download'])) {
  770. $project['download'] = array(
  771. 'type' => 'pm',
  772. 'full_version' => $release['version'],
  773. 'download_link' => $release['download_link'],
  774. 'status url' => $request['status url'],
  775. );
  776. }
  777. }
  778. $projects[$project_type][$project['name']] = $project;
  779. }
  780. }
  781. if (!$recursion) {
  782. $projects += array(
  783. 'core' => array(),
  784. 'contrib' => array(),
  785. );
  786. drush_set_option('DRUSH_MAKE_PROJECTS', array_merge($projects['core'], $projects['contrib']));
  787. }
  788. return $projects;
  789. }
  790. /**
  791. * Process all projects specified in the make file.
  792. */
  793. function make_projects($recursion, $contrib_destination, $info, $build_path, $make_dir) {
  794. $projects = make_prepare_projects($recursion, $info, $contrib_destination, $build_path, $make_dir);
  795. // Abort if there was an error processing projects.
  796. if ($projects === FALSE) {
  797. return FALSE;
  798. }
  799. // Core is built in place, rather than using make-process.
  800. if (!empty($projects['core']) && count($projects['core'])) {
  801. $project = current($projects['core']);
  802. $project = DrushMakeProject::getInstance('core', $project);
  803. $project->make();
  804. }
  805. // Process all projects concurrently using make-process.
  806. if (isset($projects['contrib'])) {
  807. $concurrency = drush_get_option('concurrency', 1);
  808. // Generate $concurrency sub-processes to do the actual work.
  809. $invocations = array();
  810. $thread = 0;
  811. foreach ($projects['contrib'] as $project) {
  812. $thread = ++$thread % $concurrency;
  813. // Ensure that we've set this sub-process up.
  814. if (!isset($invocations[$thread])) {
  815. $invocations[$thread] = array(
  816. 'args' => array(
  817. make_tmp(),
  818. ),
  819. 'options' => array(
  820. 'projects' => array(),
  821. ),
  822. 'site' => array(),
  823. );
  824. }
  825. // Add the project to this sub-process.
  826. $invocations[$thread]['options']['projects'][] = $project;
  827. // Add the manifest so recursive downloads do not override projects.
  828. $invocations[$thread]['options']['manifest'] = array_keys($projects['contrib']);
  829. }
  830. if (!empty($invocations)) {
  831. // Backend options.
  832. $backend_options = array(
  833. 'concurrency' => $concurrency,
  834. 'method' => 'POST',
  835. );
  836. // Store projects in temporary files since passing this much data on the
  837. // pipe buffer can break on certain systems.
  838. _make_write_project_json($invocations);
  839. $common_options = drush_redispatch_get_options();
  840. // Merge in stdin options since we process makefiles recursively. See http://drupal.org/node/1510180.
  841. $common_options = array_merge($common_options, drush_get_context('stdin'));
  842. // Package handler should use 'wget'.
  843. $common_options['package-handler'] = 'wget';
  844. // Avoid any prompts from CLI.
  845. $common_options['yes'] = TRUE;
  846. // Use cache unless explicitly turned off.
  847. if (!drush_get_option('no-cache', FALSE)) {
  848. $common_options['cache'] = TRUE;
  849. }
  850. // Unless --verbose or --debug are passed, quiter backend output.
  851. if (empty($common_options['verbose']) && empty($common_options['debug'])) {
  852. $backend_options['#output-label'] = FALSE;
  853. $backend_options['integrate'] = TRUE;
  854. }
  855. $results = drush_backend_invoke_concurrent($invocations, $common_options, $backend_options, 'make-process', '@none');
  856. if (count($results['error_log'])) {
  857. return FALSE;
  858. }
  859. }
  860. }
  861. return TRUE;
  862. }
  863. /**
  864. * Writes out project data to temporary files.
  865. *
  866. * @param array &$invocations
  867. * An array containing projects sorted by thread.
  868. */
  869. function _make_write_project_json(array &$invocations) {
  870. foreach ($invocations as $thread => $info) {
  871. $projects = $info['options']['projects'];
  872. unset($invocations[$thread]['options']['projects']);
  873. $temp_file = drush_tempnam('make_projects');
  874. file_put_contents($temp_file, json_encode($projects));
  875. $invocations[$thread]['options']['projects-location'] = $temp_file;
  876. }
  877. }
  878. /**
  879. * Gather additional data on all libraries specified in the make file.
  880. */
  881. function make_prepare_libraries($recursion, $info, $contrib_destination = '', $build_path = '', $make_dir = '') {
  882. // Nothing to make if the libraries list is empty.
  883. if (empty($info['libraries'])) {
  884. return;
  885. }
  886. $libraries = array();
  887. $ignore_checksums = drush_get_option('ignore-checksums');
  888. foreach ($info['libraries'] as $key => $library) {
  889. if (!is_string($key) || !is_array($library)) {
  890. // TODO Print a prettier message.
  891. continue;
  892. }
  893. // Merge the known data onto the library info.
  894. $library += array(
  895. 'name' => $key,
  896. 'core' => $info['core'],
  897. 'build_path' => $build_path,
  898. 'contrib_destination' => $contrib_destination,
  899. 'subdir' => '',
  900. 'directory_name' => $key,
  901. 'make_directory' => $make_dir,
  902. );
  903. if ($ignore_checksums) {
  904. unset($library['download']['md5']);
  905. }
  906. $libraries[$key] = $library;
  907. }
  908. if (!$recursion) {
  909. drush_set_option('DRUSH_MAKE_LIBRARIES', $info['libraries']);
  910. }
  911. return $libraries;
  912. }
  913. /**
  914. * Process all libraries specified in the make file.
  915. */
  916. function make_libraries($recursion, $contrib_destination, $info, $build_path, $make_dir) {
  917. $libraries = make_prepare_libraries($recursion, $info, $contrib_destination, $build_path, $make_dir);
  918. if (empty($libraries)) {
  919. return;
  920. }
  921. foreach ($libraries as $key => $library) {
  922. $class = DrushMakeProject::getInstance('library', $library);
  923. $class->make();
  924. }
  925. }
  926. /**
  927. * The path where the final build will be placed.
  928. */
  929. function make_build_path($build_path) {
  930. static $saved_path;
  931. if (isset($saved_path)) {
  932. return $saved_path;
  933. }
  934. // Determine the base of the build.
  935. if (drush_get_option('tar')) {
  936. $build_path = dirname($build_path) . '/' . basename($build_path, '.tar.gz') . '.tar.gz';
  937. }
  938. elseif (isset($build_path) && (!empty($build_path) || $build_path == '.')) {
  939. $build_path = rtrim($build_path, '/');
  940. }
  941. // Allow tests to run without a specified base path.
  942. elseif (drush_get_option('test') || drush_confirm(dt("Make new site in the current directory?"))) {
  943. $build_path = '.';
  944. }
  945. else {
  946. return drush_user_abort(dt('Build aborted.'));
  947. }
  948. if ($build_path != '.' && file_exists($build_path) && !drush_get_option('no-core', FALSE)) {
  949. return drush_set_error('MAKE_PATH_EXISTS', dt('Base path %path already exists.', array('%path' => $build_path)));
  950. }
  951. $saved_path = $build_path;
  952. return $build_path;
  953. }
  954. /**
  955. * Move the completed build into place.
  956. */
  957. function make_move_build($build_path) {
  958. $tmp_path = make_tmp();
  959. $ret = TRUE;
  960. if ($build_path == '.' || (drush_get_option('no-core', FALSE) && file_exists($build_path))) {
  961. $info = drush_scan_directory($tmp_path . DIRECTORY_SEPARATOR . '__build__', '/./', array('.', '..'), 0, FALSE, 'filename', 0, TRUE);
  962. foreach ($info as $file) {
  963. $destination = $build_path . DIRECTORY_SEPARATOR . $file->basename;
  964. if (file_exists($destination)) {
  965. // To prevent the removal of top-level directories such as 'modules' or
  966. // 'themes', descend in a level if the file exists.
  967. // TODO: This only protects one level of directories from being removed.
  968. $overwrite = drush_get_option('overwrite', FALSE) ? FILE_EXISTS_OVERWRITE : FILE_EXISTS_MERGE;
  969. if (is_dir($destination)) {
  970. $files = drush_scan_directory($file->filename, '/./', array('.', '..'), 0, FALSE);
  971. foreach ($files as $file) {
  972. $ret = $ret && drush_copy_dir($file->filename, $destination . DIRECTORY_SEPARATOR . $file->basename, $overwrite);
  973. }
  974. }
  975. else {
  976. $ret = $ret && drush_copy_dir($file->filename, $destination, $overwrite);
  977. }
  978. }
  979. else {
  980. $ret = $ret && drush_copy_dir($file->filename, $destination);
  981. }
  982. }
  983. }
  984. else {
  985. drush_mkdir(dirname($build_path));
  986. $ret = drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . '__build__', $tmp_path . DIRECTORY_SEPARATOR . basename($build_path), TRUE);
  987. $ret = $ret && drush_copy_dir($tmp_path . DIRECTORY_SEPARATOR . basename($build_path), $build_path);
  988. }
  989. // Copying to final destination resets write permissions. Re-apply.
  990. if (drush_get_option('prepare-install')) {
  991. $default = $build_path . '/sites/default';
  992. chmod($default . '/settings.php', 0666);
  993. chmod($default . '/files', 0777);
  994. }
  995. if (!$ret) {
  996. return drush_set_error('MAKE_CANNOT_MOVE_BUILD', dt("Cannot move build into place."));
  997. }
  998. return $ret;
  999. }
  1000. /**
  1001. * Create a request array suitable for release_info engine.
  1002. *
  1003. * This is a convenience function to easily integrate drush_make
  1004. * with drush release_info engine.
  1005. *
  1006. * @todo: refactor 'make' to internally work with release_info keys.
  1007. *
  1008. * @param array $project
  1009. * Project array.
  1010. * @param string $type
  1011. * 'contrib' or 'core'.
  1012. */
  1013. function make_prepare_request($project, $type = 'contrib') {
  1014. $request = array(
  1015. 'name' => $project['name'],
  1016. 'drupal_version' => $project['core'],
  1017. 'status url' => $project['location'],
  1018. );
  1019. if ($project['version'] != '') {
  1020. $request['project_version'] = $project['version'];
  1021. $request['version'] = $type == 'core' ? $project['version'] : $project['core'] . '-' . $project['version'];
  1022. }
  1023. return $request;
  1024. }
  1025. /**
  1026. * Determine if the release information is required for this
  1027. * project. When it is determined that it is, this potentially results
  1028. * in the use of pm-download to process the project.
  1029. *
  1030. * If the location of the project is not customized (uses d.o), and
  1031. * one of the following is true, then release information is required:
  1032. *
  1033. * - $project['type'] has not been specified
  1034. * - $project['download'] has not been specified
  1035. *
  1036. * @see make_projects()
  1037. */
  1038. function make_project_needs_release_info($project) {
  1039. return isset($project['location'])
  1040. // Only fetch release info if the project type is unknown OR if
  1041. // download attributes are unspecified.
  1042. && (!isset($project['type']) || !isset($project['download']));
  1043. }
  1044. /**
  1045. * Enables caching if not explicitly disabled.
  1046. *
  1047. * @return bool
  1048. * The previous value of the 'cache' option.
  1049. */
  1050. function _make_enable_cache() {
  1051. $cache_before = drush_get_option('cache');
  1052. if (!drush_get_option('no-cache', FALSE)) {
  1053. drush_set_option('cache', TRUE);
  1054. }
  1055. return $cache_before;
  1056. }