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