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