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_post_make Implements drush_hook_post_COMMAND() for the make command.
drush_make_process Drush callback: hidden file to process an individual project.
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_request Create a request array for use with release_info_fetch().
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.

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. /**
  7. * Default localization server for downloading translations.
  8. */
  9. define('MAKE_DEFAULT_L10N_SERVER', 'http://localize.drupal.org/l10n_server.xml');
  10. /**
  11. * Make refuses to build makefiles whose api version is mismatched
  12. * with make command.
  13. */
  14. define('MAKE_API', 2);
  15. include_once 'make.utilities.inc';
  16. include_once 'make.download.inc';
  17. include_once 'make.project.inc';
  18. /**
  19. * Implements hook_drush_command().
  20. */
  21. function make_drush_command() {
  22. $items['make'] = array(
  23. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  24. 'description' => 'Turns a makefile into a working Drupal codebase.',
  25. 'arguments' => array(
  26. 'makefile' => 'Filename of the makefile to use for this build.',
  27. 'build path' => 'The path at which to build the makefile.',
  28. ),
  29. 'examples' => array(
  30. 'drush make example.make example' => 'Build the example.make makefile in the example directory.',
  31. 'drush make --no-core --contrib-destination=. installprofile.make' => 'Build an installation profile within an existing Drupal site',
  32. 'drush make http://example.com/example.make example' => 'Build the remote example.make makefile in the example directory.',
  33. ),
  34. 'options' => array(
  35. 'version' => 'Print the make API version and exit.',
  36. 'concurrency' => array(
  37. 'description' => 'Set the number of concurrent projects that will be processed at the same time. The default is 1.',
  38. 'example-value' => '1',
  39. ),
  40. 'contrib-destination' => 'Specify a path under which modules and themes should be placed. Defaults to sites/all.',
  41. 'force-complete' => 'Force a complete build even if errors occur.',
  42. 'ignore-checksums' => 'Ignore md5 checksums for downloads.',
  43. 'md5' => array(
  44. 'description' => 'Output an md5 hash of the current build after completion. Use --md5=print to print to stdout.',
  45. 'example-value' => 'print',
  46. 'value' => 'optional',
  47. ),
  48. 'make-update-default-url' => 'The default location to load the XML update information from.',
  49. 'no-cache' => 'Do not use the pm-download caching (defaults to cache enabled).',
  50. 'no-clean' => 'Leave temporary build directories in place instead of cleaning up after completion.',
  51. 'no-core' => 'Do not require a Drupal core project to be specified.',
  52. 'no-patch-txt' => 'Do not write a PATCHES.txt file in the directory of each patched project.',
  53. 'no-gitinfofile' => 'Do not modify .info files when cloning from Git.',
  54. 'prepare-install' => 'Prepare the built site for installation. Generate a properly permissioned settings.php and files directory.',
  55. 'tar' => 'Generate a tar archive of the build. The output filename will be [build path].tar.gz.',
  56. 'test' => 'Run a temporary test build and clean up.',
  57. 'translations' => 'Retrieve translations for the specified comma-separated list of language(s) if available for all projects.',
  58. 'working-copy' => 'Preserves VCS directories, like .git, for projects downloaded using such methods.',
  59. '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).',
  60. 'projects' => array(
  61. 'description' => 'Restrict the make to this comma-separated list of projects. To specify all projects, pass *.',
  62. 'example' => 'views,ctools',
  63. ),
  64. 'libraries' => array(
  65. 'description' => 'Restrict the make to this comma-separated list of libraries. To specify all libraries, pass *.',
  66. 'example' => 'tinymce',
  67. ),
  68. ),
  69. 'engines' => array('release_info'),
  70. 'topics' => array('docs-make', 'docs-make-example'),
  71. );
  72. $items['make-generate'] = array(
  73. 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
  74. 'description' => 'Generate a makefile from the current Drupal site.',
  75. 'examples' => array(
  76. 'drush generate-makefile example.make' => 'Generate a makefile with ALL projects versioned (should a project have a known version number)',
  77. 'drush generate-makefile example.make --exclude-versions' => 'Generate a makefile with NO projects versioned',
  78. 'drush generate-makefile example.make --exclude-versions=drupal,views,cck' => 'Generate a makefile with ALL projects versioned EXCEPT core, Views and CCK',
  79. '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.',
  80. ),
  81. 'options' => array(
  82. 'exclude-versions' => 'Exclude all version numbers (default is include all version numbers) or optionally specify a list of projects to exclude from versioning',
  83. 'include-versions' => 'Include a specific list of projects, while all other projects remain unversioned in the makefile (so implies --exclude-versions)',
  84. ),
  85. 'engines' => array('release_info'),
  86. 'aliases' => array('generate-makefile'),
  87. );
  88. // Hidden command to build a single project.
  89. $items['make-process'] = array(
  90. 'hidden' => TRUE,
  91. 'arguments' => array(
  92. 'directory' => 'The temporary working directory to use',
  93. ),
  94. 'options' => array(
  95. 'project' => 'The project array as generated by make_projects()',
  96. ),
  97. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  98. 'engines' => array('release_info'),
  99. );
  100. // Add docs topic.
  101. $docs_dir = drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH);
  102. $items['docs-make'] = array(
  103. 'description' => 'Drush Make overview with examples',
  104. 'hidden' => TRUE,
  105. 'topic' => TRUE,
  106. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  107. 'callback' => 'drush_print_file',
  108. 'callback arguments' => array($docs_dir . '/docs/make.txt'),
  109. );
  110. $items['docs-make-example'] = array(
  111. 'description' => 'Drush Make example makefile',
  112. 'hidden' => TRUE,
  113. 'topic' => TRUE,
  114. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  115. 'callback' => 'drush_print_file',
  116. 'callback arguments' => array($docs_dir . '/examples/example.make'),
  117. );
  118. return $items;
  119. }
  120. /**
  121. * Implements hook_drush_help().
  122. */
  123. function make_drush_help($section) {
  124. switch ($section) {
  125. case 'drush:make':
  126. return 'Turns a makefile into a Drupal codebase. For a full description of options and makefile syntax, see docs/make.txt and examples/example.make.';
  127. case 'drush:make-generate':
  128. return '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"';
  129. }
  130. }
  131. /**
  132. * Command argument complete callback.
  133. *
  134. * @return array
  135. * Strong glob of files to complete on.
  136. */
  137. function make_make_complete() {
  138. return array(
  139. 'files' => array(
  140. 'directories' => array(
  141. 'pattern' => '*',
  142. 'flags' => GLOB_ONLYDIR,
  143. ),
  144. 'make' => array(
  145. 'pattern' => '*.make',
  146. ),
  147. ),
  148. );
  149. }
  150. /**
  151. * Drush callback; make based on the makefile.
  152. */
  153. function drush_make($makefile = NULL, $build_path = NULL) {
  154. // If --version option is supplied, print it and bail.
  155. if (drush_get_option('version', FALSE)) {
  156. drush_print(dt('drush make API version !version', array('!version' => MAKE_API)));
  157. drush_print_pipe(MAKE_API);
  158. return;
  159. }
  160. if (!($build_path = make_build_path($build_path))) {
  161. return FALSE;
  162. }
  163. $info = make_parse_info_file($makefile);
  164. // Support making just a portion of a make file.
  165. $include_only = array(
  166. 'projects' => array_filter(drush_get_option_list('projects')),
  167. 'libraries' => array_filter(drush_get_option_list('libraries')),
  168. );
  169. $info = make_prune_info_file($info, $include_only);
  170. if ($info === FALSE || ($info = make_validate_info_file($info)) === FALSE) {
  171. return FALSE;
  172. }
  173. drush_log(dt('Beginning to build !makefile.', array('!makefile' => $makefile)), 'ok');
  174. if (make_projects(FALSE, drush_get_option('contrib-destination', 'sites/all'), $info, $build_path)) {
  175. make_libraries(drush_get_option('contrib-destination', 'sites/all'), $info, $build_path);
  176. if (drush_get_option('prepare-install')) {
  177. make_prepare_install($build_path);
  178. }
  179. }
  180. return $info;
  181. }
  182. /**
  183. * Drush callback: hidden file to process an individual project.
  184. */
  185. function drush_make_process($directory) {
  186. // Set the temporary directory.
  187. make_tmp(TRUE, $directory);
  188. $project = drush_get_option('project', FALSE);
  189. if ($instance = DrushMakeProject::getInstance($project['type'], $project)) {
  190. $instance->make();
  191. }
  192. else {
  193. make_error('PROJECT-TYPE', dt('Non-existent project type %type on project %project', array('%type' => $project['type'], '%project' => $project['name'])));
  194. }
  195. }
  196. /**
  197. * Implements drush_hook_post_COMMAND() for the make command.
  198. */
  199. function drush_make_post_make($makefile = NULL, $build_path = NULL) {
  200. if (drush_get_option('version')) {
  201. return;
  202. }
  203. if (!($build_path = make_build_path($build_path))) {
  204. return;
  205. }
  206. if ($option = drush_get_option('md5')) {
  207. $md5 = make_md5();
  208. if ($option === 'print') {
  209. drush_print($md5);
  210. }
  211. else {
  212. drush_log(dt('Build hash: %md5', array('%md5' => $md5)), 'ok');
  213. }
  214. }
  215. // Only take final build steps if not in testing mode.
  216. if (!drush_get_option('test')) {
  217. if (drush_get_option('tar')) {
  218. make_tar($build_path);
  219. }
  220. else {
  221. make_move_build($build_path);
  222. }
  223. }
  224. make_clean_tmp();
  225. }
  226. /**
  227. * Process all projects specified in the make file.
  228. */
  229. function make_projects($recursion, $contrib_destination, $info, $build_path) {
  230. $projects = array();
  231. if (empty($info['projects'])) {
  232. if (drush_get_option('no-core') || $recursion) {
  233. return TRUE;
  234. }
  235. else {
  236. drush_set_error('MAKE_NO_CORE', dt('No core project specified.'));
  237. return FALSE;
  238. }
  239. }
  240. $ignore_checksums = drush_get_option('ignore-checksums');
  241. $translations = array();
  242. if (isset($info['translations'])) {
  243. $translations = $info['translations'];
  244. }
  245. if ($arg_translations = drush_get_option('translations', FALSE)) {
  246. $translations = array_merge(explode(',', $arg_translations), $translations);
  247. }
  248. foreach ($info['projects'] as $key => $project) {
  249. $md5 = '';
  250. if (isset($project['md5'])) {
  251. $md5 = $project['md5'];
  252. }
  253. // Merge the known data onto the project info.
  254. $project += array(
  255. 'name' => $key,
  256. 'core' => $info['core'],
  257. 'translations' => $translations,
  258. 'build_path' => $build_path,
  259. 'contrib_destination' => $contrib_destination,
  260. 'version' => '',
  261. 'location' => drush_get_option('make-update-default-url', RELEASE_INFO_DEFAULT_URL),
  262. 'subdir' => '',
  263. 'directory_name' => '',
  264. );
  265. // If download components are specified, but not the download
  266. // type, default to git. Additionally, if the 'revision' parameter is
  267. // passed at the top level, this is short-hand for download revision.
  268. if (isset($project['revision']) && !isset($project['download']['revision'])) {
  269. $project['download']['revision'] = $project['revision'];
  270. }
  271. if (isset($project['download']) && !isset($project['download']['type'])) {
  272. $project['download']['type'] = 'git';
  273. }
  274. if (!isset($project['l10n_url']) && ($project['location'] == RELEASE_INFO_DEFAULT_URL)) {
  275. $project['l10n_url'] = MAKE_DEFAULT_L10N_SERVER;
  276. }
  277. // For convenience: define $request to be compatible with release_info
  278. // engine.
  279. // TODO: refactor to enforce 'make' to internally work with release_info
  280. // keys.
  281. $request = make_prepare_request($project);
  282. if ($project['location'] != RELEASE_INFO_DEFAULT_URL && !isset($project['type'])) {
  283. // Set the cache option based on our '--no-cache' option.
  284. $cache_before = drush_get_option('cache');
  285. if (!drush_get_option('no-cache', FALSE)) {
  286. drush_set_option('cache', TRUE);
  287. }
  288. $project_type = release_info_check_project($request, 'core');
  289. // Restore the previous '--cache' option value.
  290. drush_set_option('cache', $cache_before);
  291. $project['download_type'] = ($project_type ? 'core' : 'contrib');
  292. }
  293. elseif (!empty($project['type'])) {
  294. $project['download_type'] = ($project['type'] == 'core' ? 'core' : 'contrib');
  295. }
  296. else {
  297. $project['download_type'] = ($project['name'] == 'drupal' ? 'core' : 'contrib');
  298. }
  299. $projects[$project['download_type']][$project['name']] = $project;
  300. }
  301. $cores = !empty($projects['core']) ? count($projects['core']) : 0;
  302. if (drush_get_option('no-core')) {
  303. unset($projects['core']);
  304. }
  305. elseif ($cores == 0 && !$recursion) {
  306. drush_set_error('MAKE_NO_CORE', dt('No core project specified.'));
  307. return FALSE;
  308. }
  309. elseif ($cores == 1 && $recursion) {
  310. unset($projects['core']);
  311. }
  312. elseif ($cores > 1) {
  313. drush_set_error('MAKE_MULTIPLE_CORES', dt('More than one core project specified.'));
  314. return FALSE;
  315. }
  316. foreach ($projects as $type => $type_projects) {
  317. foreach ($type_projects as $project) {
  318. if (make_project_needs_release_info($project)) {
  319. // For convenience: define $request to be compatible with release_info
  320. // engine.
  321. // TODO: refactor to enforce 'make' to internally work with release_info
  322. // keys.
  323. $request = make_prepare_request($project, $type);
  324. // Set the cache option based on our '--no-cache' option.
  325. $cache_before = drush_get_option('cache');
  326. if (!drush_get_option('no-cache', FALSE)) {
  327. drush_set_option('cache', TRUE);
  328. }
  329. $release = release_info_fetch($request);
  330. // Restore the previous '--cache' option value.
  331. drush_set_option('cache', $cache_before);
  332. if (!isset($project['type'])) {
  333. // Translate release_info key for project_type to drush make.
  334. $project['type'] = $request['project_type'];
  335. }
  336. if (!isset($project['download'])) {
  337. $project['download'] = array(
  338. 'type' => 'pm',
  339. 'full_version' => $release['version'],
  340. 'download_link' => $release['download_link'],
  341. 'status url' => $request['status url'],
  342. );
  343. }
  344. }
  345. if (!empty($md5)) {
  346. $project['download']['md5'] = $md5;
  347. }
  348. if ($ignore_checksums) {
  349. unset($project['download']['md5']);
  350. }
  351. $projects[($project['type'] == 'core' ? 'core' : 'contrib')][$project['name']] = $project;
  352. }
  353. }
  354. // Core is built in place, rather than using make-process.
  355. if (isset($projects['core'])) {
  356. foreach ($projects['core'] as $project) {
  357. if ($instance = DrushMakeProject::getInstance($project['type'], $project)) {
  358. $project = $instance;
  359. }
  360. else {
  361. make_error('PROJECT-TYPE', dt('Non-existent project type %type on project %project', array('%type' => $project['type'], '%project' => $project['name'])));
  362. }
  363. $project->make();
  364. }
  365. }
  366. // Process all projects concurrently using make-process.
  367. if (isset($projects['contrib'])) {
  368. $invocations = array();
  369. foreach ($projects['contrib'] as $project) {
  370. $invocations[] = array(
  371. 'args' => array(
  372. make_tmp(),
  373. ),
  374. 'options' => array(
  375. 'project' => $project,
  376. ),
  377. 'site' => array(),
  378. );
  379. }
  380. if (!empty($invocations)) {
  381. // Backend options.
  382. $backend_options = array(
  383. 'concurrency' => drush_get_option('concurrency', 1),
  384. 'method' => 'POST',
  385. );
  386. $common_options = drush_redispatch_get_options();
  387. // Merge in stdin options since we process makefiles recursively. See http://drupal.org/node/1510180.
  388. $common_options = array_merge($common_options, drush_get_context('stdin'));
  389. // Package handler should use 'wget'.
  390. $common_options['package-handler'] = 'wget';
  391. // Avoid any prompts from CLI.
  392. $common_options['yes'] = TRUE;
  393. // Use cache unless explicitly turned off.
  394. if (!drush_get_option('no-cache', FALSE)) {
  395. $common_options['cache'] = TRUE;
  396. }
  397. // Unless --verbose or --debug are passed, quiter backend output.
  398. if (empty($common_options['verbose']) && empty($common_options['debug'])) {
  399. $backend_options['#output-label'] = FALSE;
  400. $backend_options['integrate'] = TRUE;
  401. }
  402. drush_backend_invoke_concurrent($invocations, $common_options, $backend_options, 'make-process', '@none');
  403. }
  404. }
  405. return TRUE;
  406. }
  407. /**
  408. * Process all libraries specified in the make file.
  409. */
  410. function make_libraries($contrib_destination, $info, $build_path) {
  411. if (empty($info['libraries'])) {
  412. return;
  413. }
  414. $ignore_checksums = drush_get_option('ignore-checksums');
  415. foreach ($info['libraries'] as $key => $library) {
  416. if (!is_string($key) || !is_array($library)) {
  417. // TODO Print a prettier message.
  418. continue;
  419. }
  420. // Merge the known data onto the library info.
  421. $library += array(
  422. 'name' => $key,
  423. 'core' => $info['core'],
  424. 'build_path' => $build_path,
  425. 'contrib_destination' => $contrib_destination,
  426. 'subdir' => '',
  427. 'directory_name' => $key,
  428. );
  429. if ($ignore_checksums) {
  430. unset($library['download']['md5']);
  431. }
  432. $class = DrushMakeProject::getInstance('library', $library);
  433. $class->make();
  434. }
  435. }
  436. /**
  437. * The path where the final build will be placed.
  438. */
  439. function make_build_path($build_path) {
  440. static $saved_path;
  441. if (isset($saved_path)) {
  442. return $saved_path;
  443. }
  444. // Determine the base of the build.
  445. if (drush_get_option('tar')) {
  446. $build_path = dirname($build_path) . '/' . basename($build_path, '.tar.gz') . '.tar.gz';
  447. }
  448. elseif (isset($build_path) && (!empty($build_path) || $build_path == '.')) {
  449. $build_path = rtrim($build_path, '/');
  450. }
  451. // Allow tests to run without a specified base path.
  452. elseif (drush_get_option('test') || drush_confirm(dt("Make new site in the current directory?"))) {
  453. $build_path = '.';
  454. }
  455. else {
  456. return drush_user_abort(dt('Build aborted.'));
  457. }
  458. if ($build_path != '.' && file_exists($build_path)) {
  459. return drush_set_error('MAKE_PATH_EXISTS', dt('Base path %path already exists', array('%path' => $build_path)));
  460. }
  461. $saved_path = $build_path;
  462. return $build_path;
  463. }
  464. /**
  465. * Move the completed build into place.
  466. */
  467. function make_move_build($build_path) {
  468. $tmp_path = make_tmp();
  469. $ret = TRUE;
  470. if ($build_path == '.') {
  471. $info = drush_scan_directory($tmp_path . DIRECTORY_SEPARATOR . '__build__', '/./', array('.', '..'), 0, FALSE, 'filename', 0, TRUE);
  472. foreach ($info as $file) {
  473. $destination = $build_path . DIRECTORY_SEPARATOR . $file->basename;
  474. if (file_exists($destination)) {
  475. // To prevent the removal of top-level directories such as 'modules' or
  476. // 'themes', descend in a level if the file exists.
  477. // TODO: This only protects one level of directories from being removed.
  478. $files = drush_scan_directory($file->filename, '/./', array('.', '..'), 0, FALSE);
  479. foreach ($files as $file) {
  480. $ret = $ret && drush_copy_dir($file->filename, $destination . DIRECTORY_SEPARATOR . $file->basename, FILE_EXISTS_MERGE);
  481. }
  482. }
  483. else {
  484. $ret = $ret && drush_copy_dir($file->filename, $destination);
  485. }
  486. }
  487. }
  488. else {
  489. drush_mkdir(dirname($build_path));
  490. $ret = drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . '__build__', $tmp_path . DIRECTORY_SEPARATOR . basename($build_path), TRUE);
  491. $ret = $ret && drush_copy_dir($tmp_path . DIRECTORY_SEPARATOR . basename($build_path), $build_path);
  492. }
  493. // Copying to final destination resets write permissions. Re-apply.
  494. if (drush_get_option('prepare-install')) {
  495. $default = $build_path . '/sites/default';
  496. chmod($default . '/settings.php', 0666);
  497. chmod($default . '/files', 0777);
  498. }
  499. if (!$ret) {
  500. drush_set_error('MAKE_CANNOT_MOVE_BUILD', dt("Cannot move build into place"));
  501. }
  502. return $ret;
  503. }
  504. /**
  505. * Create a request array for use with release_info_fetch().
  506. *
  507. * @param array $project
  508. * Project array.
  509. * @param string $type
  510. * 'contrib' or 'core'.
  511. */
  512. function make_prepare_request($project, $type = 'contrib') {
  513. $request = array(
  514. 'name' => $project['name'],
  515. 'drupal_version' => $project['core'],
  516. 'status url' => $project['location'],
  517. );
  518. if ($project['version'] != '') {
  519. $request['project_version'] = $project['version'];
  520. $request['version'] = $type == 'core' ? $project['version'] : $project['core'] . '-' . $project['version'];
  521. }
  522. return $request;
  523. }
  524. /**
  525. * Determine if the release information is required for this
  526. * project. When it is determined that it is, this potentially results
  527. * in the use of pm-download to process the project.
  528. *
  529. * If the location of the project is not customized (uses d.o), and
  530. * one of the following is true, then release information is required:
  531. *
  532. * - $project['type'] has not been specified
  533. * - $project['download'] has not been specified
  534. *
  535. * @see make_projects()
  536. */
  537. function make_project_needs_release_info($project) {
  538. return isset($project['location'])
  539. // Only fetch release info if the project type is unknown OR if
  540. // download attributes are unspecified.
  541. && (!isset($project['type']) || !isset($project['download']));
  542. }