make.utilities.inc

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

General utility functions for Drush Make.

Functions

Namesort ascending Description
_make_is_override_allowed Check if makefile overrides are allowed
_get_working_copy_option Gather any working copy options.
make_valid_url Verify the syntax of the given URL.
make_validate_info_file Validate the make file.
make_tmp Find, and possibly create, a temporary directory.
make_tar @todo drush_archive_dump() also makes a tar. Consolidate?
make_safe_path Checks an attribute's path to ensure it's not maliciously crafted.
make_prune_info_file Remove entries in the info file in accordance with the options passed in. Entries are either explicitly 'allowed' (with the $include_only parameter) in which case all *other* entries will be excluded.
make_prepare_install Prepare a Drupal installation, copying default.settings.php to settings.php.
make_parse_info_file Parse Drupal info file format.
make_md5 Calculate a cksum on each file in the build, and md5 the resulting hashes.
make_get_data Get data based on the source.
make_error Logs an error unless the --force-complete command line option is specified.
make_clean_tmp Removes the temporary build directory. On failed builds, this is handled by drush_register_file_for_deletion().
make_apply_defaults Apply any defaults.

File

commands/make/make.utilities.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * General utility functions for Drush Make.
  5. */
  6. /**
  7. * Parse Drupal info file format.
  8. *
  9. * Copied with modifications from includes/common.inc.
  10. *
  11. * @see drupal_parse_info_file
  12. */
  13. function make_parse_info_file($makefile, $parsed = TRUE, $makefile_options = array()) {
  14. if (!($data = make_get_data($makefile))) {
  15. return drush_set_error('MAKE_INVALID_MAKE_FILE', dt('Invalid or empty make file: !makefile', array('!makefile' => $makefile)));
  16. }
  17. if (!($info = _drush_drupal_parse_info_file($data))) {
  18. return FALSE;
  19. }
  20. // $makefile_options are from projects[projectname][options][...] = value in
  21. // the makefile that included us, whereas $info['options'] is from
  22. // options[...] = value in this makefile.
  23. if (!empty($info['options'])) {
  24. $makefile_options = array_merge($makefile_options, $info['options']);
  25. }
  26. if (!empty($makefile_options)) {
  27. foreach ($makefile_options as $key => $value) {
  28. if (_make_is_override_allowed($key)) {
  29. // n.b. 'custom' context has lower priority than 'cli', so
  30. // options entered on the command line will "mask" makefile options.
  31. drush_set_option($key, $value, 'custom');
  32. }
  33. }
  34. }
  35. if (!empty($info['includes'])) {
  36. $include_path = dirname($makefile);
  37. $includes = array();
  38. if (!empty($info['includes']) && is_array($info['includes'])) {
  39. foreach ($info['includes'] as $include) {
  40. if (is_string($include)) {
  41. if (make_valid_url($include, TRUE) && ($file = make_parse_info_file($include, FALSE))) {
  42. $includes[] = $file;
  43. }
  44. elseif (file_exists($include_path . '/' . $include) && ($file = make_parse_info_file($include_path . '/' . $include, FALSE))) {
  45. $includes[] = $file;
  46. }
  47. elseif (file_exists($include) && ($file = make_parse_info_file($include, FALSE))) {
  48. $includes[] = $file;
  49. }
  50. else {
  51. make_error('BUILD_ERROR', dt("Include file missing: !include", array('!include' => $include)));
  52. }
  53. }
  54. elseif (!empty($include) && is_array($include)) {
  55. if ($include['download']['type'] = 'git') {
  56. $tmp_dir = make_tmp();
  57. make_download_git($include['makefile'], $include['download']['type'], $include['download'], $tmp_dir);
  58. $makefile = $tmp_dir . '/' . $include['makefile'];
  59. if (file_exists($makefile) && $file = make_parse_info_file($makefile, FALSE)) {
  60. $includes[] = $file;
  61. }
  62. else {
  63. make_error('BUILD_ERROR', dt("Include file missing: !include", array('!include' => $makefile)));
  64. }
  65. }
  66. }
  67. }
  68. }
  69. $includes[] = $data;
  70. $data = implode("\n", $includes);
  71. $info = _drush_drupal_parse_info_file($data);
  72. }
  73. if ($parsed) {
  74. return $info;
  75. }
  76. else {
  77. return $data;
  78. }
  79. }
  80. /**
  81. * Remove entries in the info file in accordance with the options passed in.
  82. * Entries are either explicitly 'allowed' (with the $include_only parameter) in
  83. * which case all *other* entries will be excluded.
  84. *
  85. * @param array $info
  86. * A parsed info file.
  87. *
  88. * @param array $include_only
  89. * (Optional) Array keyed by entry type (e.g. 'libraries') against an array of
  90. * allowed keys for that type. The special value '*' means 'all entries of
  91. * this type'. If this parameter is omitted, no entries will be excluded.
  92. *
  93. * @return array
  94. * The $info array, pruned if necessary.
  95. */
  96. function make_prune_info_file($info, $include_only = array()) {
  97. // We may get passed FALSE in some cases.
  98. // Also we cannot prune an empty array, so no point in this code running!
  99. if (empty($info)) {
  100. return $info;
  101. }
  102. // We will accrue an explanation of our activities here.
  103. $msg = array();
  104. $msg['scope'] = dt("Drush make restricted to the following entries:");
  105. $pruned = FALSE;
  106. if (count(array_filter($include_only))) {
  107. $pruned = TRUE;
  108. foreach ($include_only as $type => $keys) {
  109. if (!isset($info[$type])) {
  110. continue;
  111. }
  112. // For translating
  113. // dt("Projects");
  114. // dt("Libraries");
  115. $type_title = dt(ucfirst($type));
  116. // Handle the special '*' value.
  117. if (in_array('*', $keys)) {
  118. $msg[$type] = dt("!entry_type: <All>", array('!entry_type' => $type_title));
  119. }
  120. // Handle a (possibly empty) array of keys to include/exclude.
  121. else {
  122. $info[$type] = array_intersect_key($info[$type], array_fill_keys($keys, 1));
  123. unset($msg[$type]);
  124. if (!empty($info[$type])) {
  125. $msg[$type] = dt("!entry_type: !make_entries", array('!entry_type' => $type_title, '!make_entries' => implode(', ', array_keys($info[$type]))));
  126. }
  127. }
  128. }
  129. }
  130. if ($pruned) {
  131. // Make it clear to the user what's going on.
  132. drush_log(implode("\n", $msg), 'ok');
  133. // Throw an error if these restrictions reduced the make to nothing.
  134. if (empty($info['projects']) && empty($info['libraries'])) {
  135. // This error mentions the options explicitly to make it as clear as
  136. // possible to the user why this error has occurred.
  137. make_error('BUILD_ERROR', dt("All projects and libraries have been excluded. Review the 'projects' and 'libraries' options."));
  138. }
  139. }
  140. return $info;
  141. }
  142. /**
  143. * Validate the make file.
  144. */
  145. function make_validate_info_file($info) {
  146. // Assume no errors to start.
  147. $errors = FALSE;
  148. if (empty($info['core'])) {
  149. make_error('BUILD_ERROR', dt("The 'core' attribute is required"));
  150. $errors = TRUE;
  151. }
  152. // Standardize on core.
  153. elseif (preg_match('/^(\d+)(\.(x|(\d+)(-[a-z0-9]+)?))?$/', $info['core'], $matches)) {
  154. // An exact version of core has been specified, so pass that to an
  155. // internal variable for storage.
  156. if (isset($matches[4])) {
  157. $info['core_release'] = $info['core'];
  158. }
  159. // Format the core attribute consistently.
  160. $info['core'] = $matches[1] . '.x';
  161. }
  162. else {
  163. make_error('BUILD_ERROR', dt("The 'core' attribute !core has an incorrect format.", array('!core' => $info['core'])));
  164. $errors = TRUE;
  165. }
  166. if (!isset($info['api'])) {
  167. $info['api'] = MAKE_API;
  168. drush_log(dt("You need to specify an API version of two in your makefile:\napi = !api", array("!api" => MAKE_API)), 'warning');
  169. }
  170. elseif ($info['api'] != MAKE_API) {
  171. make_error('BUILD_ERROR', dt("The specified API attribute is incompatible with this version of Drush Make."));
  172. $errors = TRUE;
  173. }
  174. $names = array();
  175. // Process projects.
  176. if (isset($info['projects'])) {
  177. if (!is_array($info['projects'])) {
  178. make_error('BUILD_ERROR', dt("'projects' attribute must be an array."));
  179. $errors = TRUE;
  180. }
  181. else {
  182. // Filter out entries that have been forcibly removed via [foo] = FALSE.
  183. $info['projects'] = array_filter($info['projects']);
  184. foreach ($info['projects'] as $project => $project_data) {
  185. // Project has an attributes array.
  186. if (is_string($project) && is_array($project_data)) {
  187. if (in_array($project, $names)) {
  188. make_error('BUILD_ERROR', dt("Project !project defined twice (remove the first projects[] = !project).", array('!project' => $project)));
  189. $errors = TRUE;
  190. }
  191. $names[] = $project;
  192. foreach ($project_data as $attribute => $value) {
  193. // Prevent malicious attempts to access other areas of the
  194. // filesystem.
  195. if (in_array($attribute, array('subdir', 'directory_name', 'contrib_destination')) && !make_safe_path($value)) {
  196. $args = array(
  197. '!path' => $value,
  198. '!attribute' => $attribute,
  199. '!project' => $project,
  200. );
  201. make_error('BUILD_ERROR', dt("Illegal path !path for '!attribute' attribute in project !project.", $args));
  202. $errors = TRUE;
  203. }
  204. }
  205. }
  206. // Cover if there is no project info, it's just a project name.
  207. elseif (is_numeric($project) && is_string($project_data)) {
  208. if (in_array($project_data, $names)) {
  209. make_error('BUILD_ERROR', dt("Project !project defined twice (remove the first projects[] = !project).", array('!project' => $project_data)));
  210. $errors = TRUE;
  211. }
  212. $names[] = $project_data;
  213. unset($info['projects'][$project]);
  214. $info['projects'][$project_data] = array();
  215. }
  216. // Convert shorthand project version style to array format.
  217. elseif (is_string($project_data)) {
  218. if (in_array($project, $names)) {
  219. make_error('BUILD_ERROR', dt("Project !project defined twice (remove the first projects[] = !project).", array('!project' => $project)));
  220. $errors = TRUE;
  221. }
  222. $names[] = $project;
  223. $info['projects'][$project] = array('version' => $project_data);
  224. }
  225. else {
  226. make_error('BUILD_ERROR', dt('Project !project incorrectly specified.', array('!project' => $project)));
  227. $errors = TRUE;
  228. }
  229. }
  230. }
  231. }
  232. if (isset($info['libraries'])) {
  233. if (!is_array($info['libraries'])) {
  234. make_error('BUILD_ERROR', dt("'libraries' attribute must be an array."));
  235. $errors = TRUE;
  236. }
  237. else {
  238. // Filter out entries that have been forcibly removed via [foo] = FALSE.
  239. $info['libraries'] = array_filter($info['libraries']);
  240. foreach ($info['libraries'] as $library => $library_data) {
  241. if (is_array($library_data)) {
  242. foreach ($library_data as $attribute => $value) {
  243. // Unset disallowed attributes.
  244. if (in_array($attribute, array('contrib_destination'))) {
  245. unset($info['libraries'][$library][$attribute]);
  246. }
  247. // Prevent malicious attempts to access other areas of the
  248. // filesystem.
  249. elseif (in_array($attribute, array('contrib-destination', 'directory_name')) && !make_safe_path($value)) {
  250. $args = array(
  251. '!path' => $value,
  252. '!attribute' => $attribute,
  253. '!library' => $library,
  254. );
  255. make_error('BUILD_ERROR', dt("Illegal path !path for '!attribute' attribute in library !library.", $args));
  256. $errors = TRUE;
  257. }
  258. }
  259. }
  260. }
  261. }
  262. }
  263. // Apply defaults after projects[] array has been expanded, but prior to
  264. // external validation.
  265. make_apply_defaults($info);
  266. foreach (drush_command_implements('make_validate_info') as $module) {
  267. $function = $module . '_make_validate_info';
  268. $return = $function($info);
  269. if ($return) {
  270. $info = $return;
  271. }
  272. else {
  273. $errors = TRUE;
  274. }
  275. }
  276. if ($errors) {
  277. return FALSE;
  278. }
  279. return $info;
  280. }
  281. /**
  282. * Verify the syntax of the given URL.
  283. *
  284. * Copied verbatim from includes/common.inc
  285. *
  286. * @see valid_url
  287. */
  288. function make_valid_url($url, $absolute = FALSE) {
  289. if ($absolute) {
  290. return (bool) preg_match("
  291. /^ # Start at the beginning of the text
  292. (?:ftp|https?):\/\/ # Look for ftp, http, or https schemes
  293. (?: # Userinfo (optional) which is typically
  294. (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password
  295. (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination
  296. )?
  297. (?:
  298. (?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address
  299. |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address
  300. )
  301. (?::[0-9]+)? # Server port number (optional)
  302. (?:[\/|\?]
  303. (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional)
  304. *)?
  305. $/xi", $url);
  306. }
  307. else {
  308. return (bool) preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
  309. }
  310. }
  311. /**
  312. * Find, and possibly create, a temporary directory.
  313. *
  314. * @param boolean $set
  315. * Must be TRUE to create a directory.
  316. * @param string $directory
  317. * Pass in a directory to use. This is required if using any
  318. * concurrent operations.
  319. *
  320. * @todo Merge with drush_tempdir().
  321. */
  322. function make_tmp($set = TRUE, $directory = NULL) {
  323. static $tmp_dir;
  324. if (isset($directory) && !isset($tmp_dir)) {
  325. $tmp_dir = $directory;
  326. }
  327. if (!isset($tmp_dir) && $set) {
  328. $tmp_dir = drush_find_tmp();
  329. if (strrpos($tmp_dir, '/') == strlen($tmp_dir) - 1) {
  330. $tmp_dir .= 'make_tmp_' . time() . '_' . uniqid();
  331. }
  332. else {
  333. $tmp_dir .= '/make_tmp_' . time() . '_' . uniqid();
  334. }
  335. if (!drush_get_option('no-clean', FALSE)) {
  336. drush_register_file_for_deletion($tmp_dir);
  337. }
  338. if (file_exists($tmp_dir)) {
  339. return make_tmp(TRUE);
  340. }
  341. // Create the directory.
  342. drush_mkdir($tmp_dir);
  343. }
  344. return $tmp_dir;
  345. }
  346. /**
  347. * Removes the temporary build directory. On failed builds, this is handled by
  348. * drush_register_file_for_deletion().
  349. */
  350. function make_clean_tmp() {
  351. if (!($tmp_dir = make_tmp(FALSE))) {
  352. return;
  353. }
  354. if (!drush_get_option('no-clean', FALSE)) {
  355. drush_delete_dir($tmp_dir);
  356. }
  357. else {
  358. drush_log(dt('Temporary directory: !dir', array('!dir' => $tmp_dir)), 'ok');
  359. }
  360. }
  361. /**
  362. * Prepare a Drupal installation, copying default.settings.php to settings.php.
  363. */
  364. function make_prepare_install($build_path) {
  365. $default = make_tmp() . '/__build__/sites/default';
  366. drush_copy_dir($default . DIRECTORY_SEPARATOR . 'default.settings.php', $default . DIRECTORY_SEPARATOR . 'settings.php', TRUE);
  367. drush_mkdir($default . '/files');
  368. chmod($default . DIRECTORY_SEPARATOR . 'settings.php', 0666);
  369. chmod($default . DIRECTORY_SEPARATOR . 'files', 0777);
  370. }
  371. /**
  372. * Calculate a cksum on each file in the build, and md5 the resulting hashes.
  373. */
  374. function make_md5() {
  375. return drush_dir_md5(make_tmp());
  376. }
  377. /**
  378. * @todo drush_archive_dump() also makes a tar. Consolidate?
  379. */
  380. function make_tar($build_path) {
  381. $tmp_path = make_tmp();
  382. drush_mkdir(dirname($build_path));
  383. $filename = basename($build_path);
  384. $dirname = basename($build_path, '.tar.gz');
  385. // Move the build directory to a more human-friendly name, so that tar will
  386. // use it instead.
  387. drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . '__build__', $tmp_path . DIRECTORY_SEPARATOR . $dirname, TRUE);
  388. // Only move the tar file to it's final location if it's been built
  389. // successfully.
  390. if (drush_shell_exec("%s -C %s -Pczf %s %s", drush_get_tar_executable(), $tmp_path, $tmp_path . '/' . $filename, $dirname)) {
  391. drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $filename, $build_path, TRUE);
  392. };
  393. // Move the build directory back to it's original location for consistency.
  394. drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $dirname, $tmp_path . DIRECTORY_SEPARATOR . '__build__');
  395. }
  396. /**
  397. * Logs an error unless the --force-complete command line option is specified.
  398. */
  399. function make_error($error_code, $message) {
  400. if (drush_get_option('force-complete')) {
  401. drush_log("$error_code: $message -- build forced", 'warning');
  402. }
  403. else {
  404. drush_set_error($error_code, $message);
  405. }
  406. }
  407. /**
  408. * Checks an attribute's path to ensure it's not maliciously crafted.
  409. *
  410. * @param string $path
  411. * The path to check.
  412. */
  413. function make_safe_path($path) {
  414. return !preg_match("+^/|^\.\.|/\.\./+", $path);
  415. }
  416. /**
  417. * Get data based on the source.
  418. *
  419. * This is a helper function to abstract the retrieval of data, so that it can
  420. * come from files, STDIN, etc. Currently supports filepath and STDIN.
  421. *
  422. * @param string $data_source
  423. * The path to a file, or '-' for STDIN.
  424. *
  425. * @return string
  426. * The raw data as a string.
  427. */
  428. function make_get_data($data_source) {
  429. if ($data_source == '-') {
  430. // See http://drupal.org/node/499758 before changing this.
  431. $stdin = fopen('php://stdin', 'r');
  432. $data = '';
  433. $has_input = FALSE;
  434. while ($line = fgets($stdin)) {
  435. $has_input = TRUE;
  436. $data .= $line;
  437. }
  438. if ($has_input) {
  439. return $data;
  440. }
  441. return FALSE;
  442. }
  443. // Local file.
  444. elseif (!strpos($data_source, '://')) {
  445. $data = file_get_contents($data_source);
  446. }
  447. // Remote file.
  448. else {
  449. $file = _make_download_file($data_source);
  450. $data = file_get_contents($file);
  451. drush_op('unlink', $file);
  452. }
  453. return $data;
  454. }
  455. /**
  456. * Apply any defaults.
  457. *
  458. * @param array &$info
  459. * A parsed make array.
  460. */
  461. function make_apply_defaults(&$info) {
  462. if (isset($info['defaults'])) {
  463. $defaults = $info['defaults'];
  464. unset($info['defaults']);
  465. foreach ($defaults as $type => $default_data) {
  466. if (isset($info[$type])) {
  467. foreach ($info[$type] as $project => $data) {
  468. $info[$type][$project] += $default_data;
  469. }
  470. }
  471. else {
  472. drush_log(dt("Unknown attribute '@type' in defaults array", array('@type' => $type)), 'warning');
  473. }
  474. }
  475. }
  476. }
  477. /**
  478. * Check if makefile overrides are allowed
  479. *
  480. * @param array $option
  481. * The option to check.
  482. */
  483. function _make_is_override_allowed ($option) {
  484. $allow_override = drush_get_option('allow-override', 'all');
  485. if ($allow_override == 'all') {
  486. $allow_override = array();
  487. }
  488. elseif (!is_array($allow_override)) {
  489. $allow_override = _convert_csv_to_array($allow_override);
  490. }
  491. if ((empty($allow_override)) || ((in_array($option, $allow_override)) && (!in_array('none', $allow_override)))) {
  492. return TRUE;
  493. }
  494. drush_log(dt("'!option' not allowed; use --allow-override=!option or --allow-override=all to permit", array("!option" => $option)), 'warning');
  495. return FALSE;
  496. }
  497. /**
  498. * Gather any working copy options.
  499. *
  500. * @param array $download
  501. * The download array.
  502. */
  503. function _get_working_copy_option($download) {
  504. $wc = '';
  505. if (_make_is_override_allowed('working-copy') && isset ($download['working-copy'])) {
  506. $wc = $download['working-copy'];
  507. }
  508. else {
  509. $wc = drush_get_option('working-copy');
  510. }
  511. return $wc;
  512. }