make.download.inc

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

Download-specific functions for Drush Make.

Functions

Namesort descending Description
make_download_bzr Checks out a Bazaar repository to the specified download location.
make_download_factory Downloads the given package to the destination directory.
make_download_file Downloads a file to the specified location.
make_download_file_unpack Unpacks a file to the specified download location.
make_download_get For backwards compatibility.
make_download_git Checks out a git repository to the specified download location.
make_download_pm Download project using drush's pm-download command.
make_download_svn Checks out an SVN repository to the specified download location.
_make_download_file Wrapper to drush_download_file().
_make_download_file_move Move a downloaded and unpacked file or directory into place.
_make_hash Calculate the hash of a string for a given algorithm.
_make_verify_checksums Test that any supplied hash values match the hash of the file content.

File

commands/make/make.download.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Download-specific functions for Drush Make.
  5. */
  6. /**
  7. * Downloads the given package to the destination directory.
  8. *
  9. * @return mixed
  10. * The destination path on success, FALSE on failure.
  11. */
  12. function make_download_factory($name, $type, $download, $download_location) {
  13. $function = 'make_download_' . $download['type'];
  14. if (function_exists($function)) {
  15. return $function($name, $type, $download, $download_location);
  16. }
  17. else {
  18. return FALSE;
  19. }
  20. }
  21. /**
  22. * Download project using drush's pm-download command.
  23. */
  24. function make_download_pm($name, $type, $download, $download_location) {
  25. $full_project_version = $name . '-' . $download['full_version'];
  26. $options = array(
  27. 'destination' => dirname($download_location),
  28. 'yes' => TRUE,
  29. 'package-handler' => 'wget',
  30. 'source' => $download['status url'],
  31. // This is only relevant for profiles, but we always want the variant to
  32. // be 'profile-only' so we don't end up with extra copies of core.
  33. 'variant' => $type == 'core' ? 'full' : 'profile-only',
  34. 'cache' => TRUE,
  35. );
  36. if ($type == 'core') {
  37. $options['drupal-project-rename'] = basename($download_location);
  38. }
  39. if (drush_get_option('no-cache', FALSE)) {
  40. unset($options['cache']);
  41. }
  42. $backend_options = array();
  43. if (!drush_get_option(array('verbose', 'debug'), FALSE)) {
  44. $backend_options['integrate'] = TRUE;
  45. $backend_options['log'] = FALSE;
  46. }
  47. // Perform actual download with `drush pm-download`.
  48. $return = drush_invoke_process('@none', 'pm-download', array($full_project_version), $options, $backend_options);
  49. if (empty($return['error_log'])) {
  50. // @todo Report the URL we used for download. See
  51. // http://drupal.org/node/1452672.
  52. drush_log(dt('@project downloaded.', array('@project' => $full_project_version)), 'ok');
  53. }
  54. }
  55. /**
  56. * Downloads a file to the specified location.
  57. *
  58. * @return mixed
  59. * The destination directory on success, FALSE on failure.
  60. */
  61. function make_download_file($name, $type, $download, $download_location, $cache_duration = DRUSH_CACHE_LIFETIME_DEFAULT) {
  62. if ($filename = _make_download_file($download['url'], $cache_duration)) {
  63. if (!drush_get_option('ignore-checksums') && !_make_verify_checksums($download, $filename)) {
  64. return FALSE;
  65. }
  66. drush_log(dt('@project downloaded from @url.', array('@project' => $name, '@url' => $download['url'])), 'ok');
  67. $download_filename = isset($download['filename']) ? $download['filename'] : '';
  68. $subtree = isset($download['subtree']) ? $download['subtree'] : NULL;
  69. return make_download_file_unpack($filename, $download_location, $download_filename, $subtree);
  70. }
  71. make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url'])));
  72. return FALSE;
  73. }
  74. /**
  75. * Wrapper to drush_download_file().
  76. *
  77. * @param string $download
  78. * The url of the file to download.
  79. * @param int $cache_duration
  80. * The time in seconds to cache the resultant download.
  81. *
  82. * @return string
  83. * The location of the downloaded file, or FALSE on failure.
  84. */
  85. function _make_download_file($download, $cache_duration = DRUSH_CACHE_LIFETIME_DEFAULT) {
  86. if (drush_get_option('no-cache', FALSE)) {
  87. $cache_duration = 0;
  88. }
  89. $tmp_path = make_tmp();
  90. // Ensure that we aren't including the querystring when generating a filename
  91. // to save our download to.
  92. $file = basename(current(explode('?', $download, 2)));
  93. return drush_download_file($download, $tmp_path . '/' . $file, $cache_duration);
  94. }
  95. /**
  96. * Unpacks a file to the specified download location.
  97. *
  98. * @return mixed
  99. * The download location on success, FALSE on failure.
  100. */
  101. function make_download_file_unpack($filename, $download_location, $name, $subtree = NULL) {
  102. $success = FALSE;
  103. if (drush_file_is_tarball($filename)) {
  104. $tmp_location = drush_tempdir();
  105. if (!drush_tarball_extract($filename, $tmp_location)) {
  106. return FALSE;
  107. }
  108. if ($subtree) {
  109. $tmp_location .= '/' . $subtree;
  110. if (!file_exists($tmp_location)) {
  111. return drush_set_error('DRUSH_MAKE_SUBTREE_NOT_FOUND', dt('Directory !subtree not found within !file', array('!subtree' => $subtree, '!file' => $filename)));
  112. }
  113. }
  114. else {
  115. $files = scandir($tmp_location);
  116. unset($files[0]); // . directory
  117. unset($files[1]); // .. directory
  118. if ((count($files) == 1) && is_dir($tmp_location . '/' . current($files))) {
  119. $tmp_location .= '/' . current($files);
  120. }
  121. }
  122. $success = drush_move_dir($tmp_location, $download_location, TRUE);
  123. // Remove the tarball.
  124. if (file_exists($filename)) {
  125. drush_delete_dir($filename, TRUE);
  126. }
  127. }
  128. else {
  129. // If this is an individual file, and no filename has been specified,
  130. // assume the original name.
  131. if (is_file($filename) && !$name) {
  132. $name = basename($filename);
  133. }
  134. // The destination directory has already been created by
  135. // findDownloadLocation().
  136. $destination = $download_location . ($name ? '/' . $name : '');
  137. $success = drush_move_dir($filename, $destination, TRUE);
  138. }
  139. return $success ? $download_location : FALSE;
  140. }
  141. /**
  142. * Move a downloaded and unpacked file or directory into place.
  143. *
  144. * TODO merge with core drush methods.
  145. */
  146. function _make_download_file_move($tmp_path, $filename, $download_location, $subtree = NULL) {
  147. $lines = drush_scan_directory($tmp_path, '/./', array('.', '..'), 0, FALSE, 'filename', 0, TRUE);
  148. $main_directory = basename($download_location);
  149. if (count($lines) == 1) {
  150. $directory = array_shift($lines);
  151. if ($directory->basename != $main_directory) {
  152. drush_move_dir($directory->filename, $tmp_path . DIRECTORY_SEPARATOR . $main_directory, TRUE);
  153. }
  154. drush_copy_dir($tmp_path . DIRECTORY_SEPARATOR . $main_directory . DIRECTORY_SEPARATOR . $subtree, $download_location, TRUE);
  155. drush_delete_dir($tmp_path, TRUE);
  156. }
  157. elseif (count($lines) > 1) {
  158. drush_delete_dir($download_location, TRUE);
  159. drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $subtree, $download_location, TRUE);
  160. }
  161. // Remove the tarball.
  162. if (file_exists($filename)) {
  163. drush_delete_dir($filename, TRUE);
  164. }
  165. if (file_exists($tmp_path)) {
  166. drush_delete_dir($tmp_path, TRUE);
  167. }
  168. return TRUE;
  169. }
  170. /**
  171. * For backwards compatibility.
  172. */
  173. function make_download_get($name, $type, $download, $download_location) {
  174. return make_download_file($name, $type, $download, $download_location);
  175. }
  176. /**
  177. * Checks out a git repository to the specified download location.
  178. *
  179. * Allowed parameters in $download, in order of precedence:
  180. * - 'tag'
  181. * - 'revision'
  182. * - 'branch'
  183. *
  184. * This will also attempt to write out release information to the
  185. * .info file if the 'no-gitinfofile' option is FALSE. If
  186. * $download['full_version'] is present, this will be used, otherwise,
  187. * version will be set in this order of precedence:
  188. * - 'tag'
  189. * - 'branch'
  190. * - 'revision'
  191. *
  192. * @return mixed
  193. * The download location on success, FALSE otherwise.
  194. */
  195. function make_download_git($name, $type, $download, $download_location) {
  196. $tmp_path = make_tmp();
  197. $wc = drush_get_option('working-copy');
  198. // If no download URL specified, assume anonymous clone from git.drupal.org.
  199. $download['url'] = isset($download['url']) ? $download['url'] : "http://git.drupal.org/project/$name.git";
  200. // If no working-copy download URL specified, assume it is the same.
  201. $download['wc_url'] = isset($download['wc_url']) ? $download['wc_url'] : $download['url'];
  202. // If not a working copy, and if --no-cache has not been explicitly
  203. // declared, create a new git reference cache of the remote repository,
  204. // or update the existing cache to fetch recent changes.
  205. // @see package_handler_download_project()
  206. $cache = !$wc && !drush_get_option('no-cache', FALSE);
  207. if ($cache && ($git_cache = drush_directory_cache('git'))) {
  208. $project_cache = $git_cache . '/' . $name . '-' . md5($download['url']);
  209. // Set up a new cache, if it doesn't exist.
  210. if (!file_exists($project_cache)) {
  211. $command = 'git clone --mirror';
  212. if (drush_get_context('DRUSH_VERBOSE')) {
  213. $command .= ' --verbose --progress';
  214. }
  215. $command .= ' %s %s';
  216. drush_shell_cd_and_exec($git_cache, $command, $download['url'], $project_cache);
  217. }
  218. else {
  219. // Update the --mirror clone.
  220. drush_shell_cd_and_exec($project_cache, 'git remote update');
  221. }
  222. $git_cache = $project_cache;
  223. }
  224. // Use working-copy download URL if --working-copy specified.
  225. $url = $wc ? $download['wc_url'] : $download['url'];
  226. $tmp_location = drush_tempdir() . '/' . basename($download_location);
  227. $command = 'git clone %s %s';
  228. if (drush_get_context('DRUSH_VERBOSE')) {
  229. $command .= ' --verbose --progress';
  230. }
  231. if ($cache) {
  232. $command .= ' --reference ' . drush_escapeshellarg($git_cache);
  233. }
  234. // Before we can checkout anything, we need to clone the repository.
  235. if (!drush_shell_exec($command, $url, $tmp_location)) {
  236. make_error('DOWNLOAD_ERROR', dt('Unable to clone @project from @url.', array('@project' => $name, '@url' => $url)));
  237. return FALSE;
  238. }
  239. drush_log(dt('@project cloned from @url.', array('@project' => $name, '@url' => $url)), 'ok');
  240. // Get the current directory (so we can move back later).
  241. $cwd = getcwd();
  242. // Change into the working copy of the cloned repo.
  243. chdir($tmp_location);
  244. // We want to use the most specific target possible, so first try a refspec.
  245. if (!empty($download['refspec'])) {
  246. if (drush_shell_exec("git fetch %s %s", $url, $download['refspec'])) {
  247. drush_log(dt("Fetched refspec !refspec.", array('!refspec' => $download['refspec'])), 'ok');
  248. if (drush_shell_exec("git checkout FETCH_HEAD")) {
  249. drush_log(dt("Checked out FETCH_HEAD."), 'info');
  250. }
  251. }
  252. else {
  253. make_error('DOWNLOAD_ERROR', dt("Unable to fetch the refspec @refspec from @project.", array('@refspec' => $download['refspec'], '@project' => $name)));
  254. }
  255. }
  256. // If there wasn't a refspec, try a tag.
  257. elseif (!empty($download['tag'])) {
  258. // @TODO: change checkout to refs path.
  259. if (drush_shell_exec("git checkout %s", 'refs/tags/' . $download['tag'])) {
  260. drush_log(dt("Checked out tag @tag.", array('@tag' => $download['tag'])), 'ok');
  261. }
  262. else {
  263. make_error('DOWNLOAD_ERROR', dt("Unable to check out tag @tag.", array('@tag' => $download['tag'])));
  264. }
  265. }
  266. // If there wasn't a tag, try a specific revision hash.
  267. elseif (!empty($download['revision'])) {
  268. if (drush_shell_exec("git checkout %s", $download['revision'])) {
  269. drush_log(dt("Checked out revision @revision.", array('@revision' => $download['revision'])), 'ok');
  270. }
  271. else {
  272. make_error('DOWNLOAD_ERROR', dt("Unable to checkout revision @revision", array('@revision' => $download['revision'])));
  273. }
  274. }
  275. // If not, see if we at least have a branch.
  276. elseif (!empty($download['branch'])) {
  277. if (drush_shell_exec("git checkout %s", $download['branch']) && (trim(implode(drush_shell_exec_output())) != '')) {
  278. drush_log(dt("Checked out branch @branch.", array('@branch' => $download['branch'])), 'ok');
  279. }
  280. elseif (drush_shell_exec("git checkout -b %s %s", $download['branch'], 'origin/' . $download['branch'])) {
  281. drush_log(dt('Checked out branch origin/@branch.', array('@branch' => $download['branch'])), 'ok');
  282. }
  283. else {
  284. make_error('DOWNLOAD_ERROR', dt('Unable to check out branch @branch.', array('@branch' => $download['branch'])));
  285. }
  286. }
  287. if (!empty($download['submodule'])) {
  288. $command = 'git submodule update';
  289. foreach ($download['submodule'] as $option) {
  290. $command .= ' --%s';
  291. }
  292. if (call_user_func_array('drush_shell_exec', array_merge(array($command), $download['submodule']))) {
  293. drush_log(dt('Initialized registered submodules.'), 'ok');
  294. }
  295. else {
  296. make_error('DOWNLOAD_ERROR', dt('Unable to initialize submodules.'));
  297. }
  298. }
  299. // Move back to last current directory (first line).
  300. chdir($cwd);
  301. // Move the directory into the final resting location.
  302. drush_copy_dir($tmp_location, $download_location, TRUE);
  303. return dirname($tmp_location);
  304. }
  305. /**
  306. * Checks out a Bazaar repository to the specified download location.
  307. *
  308. * @return mixed
  309. * The download location on success, FALSE otherwise.
  310. */
  311. function make_download_bzr($name, $type, $download, $download_location) {
  312. $tmp_path = make_tmp();
  313. $tmp_location = drush_tempdir() . '/' . basename($download_location);
  314. if (!empty($download['url'])) {
  315. $args = array();
  316. $command = 'bzr';
  317. if (drush_get_option('working-copy')) {
  318. $command .= ' branch --use-existing-dir';
  319. }
  320. else {
  321. $command .= ' export';
  322. }
  323. if (isset($download['revision'])) {
  324. $command .= ' -r %s';
  325. $args[] = $download['revision'];
  326. }
  327. $command .= ' %s %s';
  328. if (drush_get_option('working-copy')) {
  329. $args[] = $download['url'];
  330. $args[] = $tmp_location;
  331. }
  332. else {
  333. $args[] = $tmp_location;
  334. $args[] = $download['url'];
  335. }
  336. array_unshift($args, $command);
  337. if (call_user_func_array('drush_shell_exec', $args)) {
  338. drush_log(dt('@project downloaded from @url.', array('@project' => $name, '@url' => $download['url'])), 'ok');
  339. drush_copy_dir($tmp_location, $download_location, TRUE);
  340. return dirname($download_location);
  341. }
  342. }
  343. else {
  344. $download['url'] = dt("unspecified location");
  345. }
  346. make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url'])));
  347. drush_delete_dir(dirname($tmp_location), TRUE);
  348. return FALSE;
  349. }
  350. /**
  351. * Checks out an SVN repository to the specified download location.
  352. *
  353. * @return mixed
  354. * The download location on success, FALSE otherwise.
  355. */
  356. function make_download_svn($name, $type, $download, $download_location) {
  357. if (!empty($download['url'])) {
  358. if (!empty($download['interactive'])) {
  359. $function = 'drush_shell_exec_interactive';
  360. }
  361. else {
  362. $options = ' --non-interactive';
  363. $function = 'drush_shell_exec';
  364. }
  365. if (!isset($download['force']) || $download['force']) {
  366. $options = ' --force';
  367. }
  368. if (drush_get_option('working-copy')) {
  369. $command = 'svn' . $options . ' checkout';
  370. }
  371. else {
  372. $command = 'svn' . $options . ' export';
  373. }
  374. $args = array();
  375. if (isset($download['revision'])) {
  376. $command .= ' -r%s';
  377. $args[] = $download['revision'];
  378. }
  379. $command .= ' %s %s';
  380. $args[] = $download['url'];
  381. $args[] = $download_location;
  382. if (!empty($download['username'])) {
  383. $command .= ' --username %s';
  384. $args[] = $download['username'];
  385. if (!empty($download['password'])) {
  386. $command .= ' --password %s';
  387. $args[] = $download['password'];
  388. }
  389. }
  390. array_unshift($args, $command);
  391. $result = call_user_func_array($function, $args);
  392. if ($result) {
  393. $args = array(
  394. '@project' => $name,
  395. '@command' => $command,
  396. '@url' => $download['url'],
  397. );
  398. drush_log(dt('@project @command from @url.', $args), 'ok');
  399. return $download_location;
  400. }
  401. else {
  402. $download['url'] = dt("unspecified location");
  403. }
  404. }
  405. else {
  406. make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url'])));
  407. return FALSE;
  408. }
  409. }
  410. /**
  411. * Test that any supplied hash values match the hash of the file content.
  412. *
  413. * Unsupported hash algorithms are reported as failure.
  414. */
  415. function _make_verify_checksums($info, $filename) {
  416. $hash_algos = array('md5', 'sha1', 'sha256', 'sha512');
  417. // We only have something to do if a key is an
  418. // available function.
  419. if (array_intersect(array_keys($info), $hash_algos)) {
  420. $content = file_get_contents($filename);
  421. foreach ($hash_algos as $algo) {
  422. if (!empty($info[$algo])) {
  423. $hash = _make_hash($algo, $content);
  424. if ($hash !== $info[$algo]) {
  425. $args = array(
  426. '@algo' => $algo,
  427. '@file' => basename($filename),
  428. '@expected' => $info[$algo],
  429. '@hash' => $hash,
  430. );
  431. make_error('DOWNLOAD_ERROR', dt('Checksum @algo verification failed for @file. Expected @expected, received @hash.', $args));
  432. return FALSE;
  433. }
  434. }
  435. }
  436. }
  437. return TRUE;
  438. }
  439. /**
  440. * Calculate the hash of a string for a given algorithm.
  441. */
  442. function _make_hash($algo, $string) {
  443. switch ($algo) {
  444. case 'md5':
  445. return md5($string);
  446. case 'sha1':
  447. return sha1($string);
  448. default:
  449. return function_exists('hash') ? hash($algo, $string) : '';
  450. }
  451. }