function package_release($release_nid, $project, $repository, $version, $label) { global $tmp_dir, $drupal_root, $dest_root, $dest_rel; global $tar, $gzip, $rm, $ln, $mkdir; global $license; $project_directory_item = versioncontrol_get_item($repository, $project['directory'], array('label' => $label)); if (empty($project_directory_item)) { wd_err('ERROR: Could not retrieve project directory item.'); } // In Version Control API, item paths start with a slash. $relative_project_dir = escapeshellcmd(substr($project['directory'], 1)); $uri = escapeshellcmd($project['uri']); ///TODO: drupal.org specific hack, get rid of it somehow $is_core = $site_name == 'drupal.org' && $repository['repo_id'] == 1 && $uri == 'drupal'; $is_contrib = $site_name == 'drupal.org' && $repository['repo_id'] == 2; $id = $uri . '-' . $version; $view_link = l(t('view'), 'node/' . $release_nid); $file_name = $id . '.tar.gz'; $file_path = $dest_rel . '/' . $file_name; $full_dest = $dest_root . '/' . $file_path; $export_dir = $tmp_dir . '/' . $id; if ($is_contrib) { ///TODO: drupal.org specific hack, get rid of it somehow $export_dir = $tmp_dir . '/' . $relative_project_dir; } $success = versioncontrol_export_directory($repository, $project_directory_item, $export_dir); if (!$success) { wd_err('ERROR: %dir @ !labeltype %labelname could not be exported', array('%dir' => $export_dir, '!labeltype' => $label['type'] == VERSIONCONTROL_OPERATION_BRANCH ? t('branch') : t('tag'), '%labelname' => $label['name']), $view_link); return FALSE; } $info_files = array(); // Files to ignore when checking timestamps: $exclude = array('.', '..', 'LICENSE.txt'); $youngest = file_find_youngest($export_dir, 0, $exclude, $info_files); if (is_file($full_dest) && filectime($full_dest) + 300 > $youngest) { // The existing tarball for this release is newer than the youngest // file in the directory, we're done. return FALSE; } ///TODO: drupal.org specific hack, get rid of it somehow // Fix any .info files. foreach ($info_files as $file) { if (!fix_info_file_version($file, $uri, $version)) { wd_err('ERROR: Failed to update version in %file, aborting packaging', array('%file' => $file), $view_link); return FALSE; } } // Do we want a subdirectory in the tarball or not? $tarball_needs_subdir = TRUE; ///TODO: drupal.org specific hack, get rid of it somehow if ($is_contrib) { // Link not copy, since we want to preserve the date. if (!drupal_exec("{$ln} -sf {$license} {$export_dir}/LICENSE.txt")) { return FALSE; } $parts = split('/', $relative_project_dir); $contrib_type = $parts[1]; // modules, themes, theme-engines, or translations if ($contrib_type == 'translations' && $project['uri'] != 'drupal-pot') { ///TODO: drupal.org specific hack (contd.), get rid of it somehow // Translation projects are packaged differently based on core version. if (intval($version) == 6) { if (!($to_tar = package_release_contrib_d6_translation($export_dir, $uri, $version, $view_link))) { // Return on error. return FALSE; } $tarball_needs_subdir = FALSE; } elseif (!($to_tar = package_release_contrib_pre_d6_translation($export_dir, $uri, $version, $view_link))) { // Return on error. return FALSE; } } } if (empty($to_tar)) { // Not a translation: just grab the whole directory. $to_tar = basename($export_dir); } // We want tar to get a relative list of paths, so we tell it to change into // the parent directory, except for D6 translations which get special cased. $tar_dir = $tarball_needs_subdir ? dirname($export_dir) : $export_dir; // 'h' is for dereference, we want to include the files, not the links if (!drupal_exec("{$tar} -ch --directory {$tar_dir} --file=- {$to_tar} | {$gzip} -9 --no-name > {$full_dest}")) { return FALSE; } // As soon as the tarball exists, we want to update the DB about it. package_release_update_node($release_nid, $file_path); wd_msg("%id has changed, re-packaged.", array('%id' => $id), $view_link); // Don't consider failure to remove this directory a build failure. drupal_exec("{$rm} -rf {$export_dir}"); return TRUE; }
function package_release_contrib($type, $nid, $project_short_name, $version, $tag, $release_dir) { global $tmp_dir, $repositories, $dest_root, $dest_rel; global $cvs, $tar, $gzip, $rm, $ln; global $drush, $drush_make_dir; global $license, $trans_install; // Files to ignore when checking timestamps: $exclude = array('.', '..', 'LICENSE.txt'); $parts = split('/', $release_dir); // modules, themes, theme-engines, profiles, or translations $contrib_type = $parts[1]; // specific directory (same as uri) $project_short_name = $parts[2]; $project_build_root = "{$tmp_dir}/{$project_short_name}"; $cvs_export_dir = "{$repositories[DRUPAL_CONTRIB_REPOSITORY_ID]['modules']}/{$contrib_type}/{$project_short_name}"; $release_file_id = $project_short_name . '-' . $version; $release_node_view_link = l(t('view'), 'node/' . $nid); $file_path_tgz = $dest_rel . '/' . $release_file_id . '.tar.gz'; $full_dest_tgz = $dest_root . '/' . $file_path_tgz; // Remember if the tar.gz version of this release file already exists. $tgz_exists = is_file($full_dest_tgz); // Clean up any old build directory if it exists. // Don't use drupal_exec or return if this fails, we expect it to be empty. exec("{$rm} -rf {$project_build_root}"); // Make a fresh build directory and move inside it. if (!mkdir($project_build_root) || !drupal_chdir($project_build_root)) { return 'error'; } // Checkout this release from CVS, and see if we need to rebuild it if (!drupal_exec("{$cvs} -q export -r {$tag} -d {$project_short_name} {$cvs_export_dir}")) { return 'error'; } if (!is_dir("{$project_build_root}/{$project_short_name}")) { wd_err("ERROR: %dir does not exist after cvs export -r %tag -d %dir %cvs_export_dir", array('%dir' => $project_short_name, '%rev' => $tag, '%cvs_export_dir' => $cvs_export_dir), $release_node_view_link); return 'error'; } $info_files = array(); $youngest = file_find_youngest($project_short_name, 0, $exclude, $info_files); if ($type == 'branch' && $tgz_exists && filemtime($full_dest_tgz) + 300 > $youngest) { // The existing tarball for this release is newer than the youngest // file in the directory, we're done. return 'no-op'; } // Update any .info files with packaging metadata. foreach ($info_files as $file) { if (!fix_info_file_version($file, $project_short_name, $version)) { wd_err("ERROR: Failed to update version in %file, aborting packaging", array('%file' => $file), $release_node_view_link); return 'error'; } } // Link not copy, since we want to preserve the date... if (!drupal_exec("{$ln} -sf {$license} {$project_short_name}/LICENSE.txt")) { return 'error'; } // Do we want a subdirectory in the tarball or not? $tarball_needs_subdir = TRUE; if ($contrib_type == 'translations' && $project_short_name != 'drupal-pot') { // Translation projects are packaged differently based on core version. if (intval($version) > 5) { if (!($to_tar = package_release_contrib_d6_translation($project_short_name, $version, $release_node_view_link))) { // Return on error. return 'error'; } $tarball_needs_subdir = FALSE; } elseif (!($to_tar = package_release_contrib_pre_d6_translation($project_short_name, $version, $release_node_view_link))) { // Return on error. return 'error'; } } else { // Not a translation: just grab the whole directory. $to_tar = $project_short_name; } if (!$tarball_needs_subdir) { if (!drupal_chdir($project_short_name)) { return 'error'; } } // 'h' is for dereference, we want to include the files, not the links if (!drupal_exec("{$tar} -ch --file=- {$to_tar} | {$gzip} -9 --no-name > {$full_dest_tgz}")) { return 'error'; } $files[] = $file_path_tgz; // Start with no package contents, since this is only valid for profiles. $package_contents = array(); // This is a profile, so invoke the drush_make routines to package core // and/or any other contrib releases specified in the profile's .make file. if ($contrib_type == 'profiles') { // Move inside the profile directory. if (!drupal_chdir("{$project_build_root}/{$project_short_name}")) { return 'error'; } // In order for extended packaging to take place, the profile must have a // file named drupal-org.make in the main directory of their profile. $drupalorg_makefile = 'drupal-org.make'; if (file_exists($drupalorg_makefile)) { // Search the .make file for the required 'core' attribute. $info = drupal_parse_info_file($drupalorg_makefile); // Only proceed if a core release was found. if (!isset($info['core'])) { wd_err("ERROR: %profile does not have the required 'core' attribute.", array('%profile' => $release_file_id), $release_node_view_link); return 'error'; } else { // Basic sanity check for the format of the attribute. The CVS checkout // attempt of core will handle the rest of the validation (ie, it will // fail if a non-existant tag is specified. if (!preg_match("/^(\\d+)\\.(\\d+)(-[a-z0-9]+)?\$/", $info['core'], $matches)) { wd_err("ERROR: %profile specified an invalid 'core' attribute -- both API version and release are required.", array('%profile' => $release_file_id), $release_node_view_link); return 'error'; } else { // Compare the Drupal API version in the profile's version string with // the API version of core specificed in the .make file -- these should // match. $profile_api_version = $matches[1]; $parts = explode('.', $version); $release_api_version = $parts[0]; if ($profile_api_version != $release_api_version) { wd_err("ERROR: %profile specified an invalid 'core' attribute -- the API version must match the API version of the release.", array('%profile' => $release_file_id), $release_node_view_link); return 'error'; } } // NO-CORE DISTRIBUTION. $no_core_id = "{$release_file_id}-no-core"; // Build the drupal file path and the full file path. $no_core_file_path = "{$dest_rel}/{$no_core_id}.tar.gz"; $no_core_full_dest = "{$dest_root}/{$no_core_file_path}"; // Run drush_make to build the profile's contents. // --drupal-org: Invoke drupal.org specific validation/processing. // --drupal-org-build-root: Let the script know where to place it's // build-related files. // --drupal-org-log-errors-to-file: Store build errors for later output. // --drupal-org-log-package-items-to-file: Store package items for // later recording in the database. if (!drupal_exec("{$drush} --include={$drush_make_dir} make --drupal-org --drupal-org-build-root={$project_build_root} --drupal-org-log-errors-to-file --drupal-org-log-package-items-to-file {$drupalorg_makefile} .")) { // The build failed, get any output error messages and include them // in the packaging error report. $build_errors_file = "{$project_build_root}/build_errors.txt"; if (file_exists($build_errors_file)) { $lines = file($build_errors_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($lines as $line) { wd_err("ERROR: {$line}"); } } wd_err("ERROR: Build for %profile failed.", array('%profile' => $no_core_id), $release_node_view_link); return 'error'; } // Change into the profile build directory. if (!drupal_chdir($project_build_root)) { return 'error'; } // Package the no-core distribution. // 'h' is for dereference, we want to include the files, not the links if (!drupal_exec("{$tar} -ch --file=- {$project_short_name} | {$gzip} -9 --no-name > {$no_core_full_dest}")) { return 'error'; } $files[] = $no_core_file_path; // CORE DISTRIBUTION. // Write a small .make file used to build core. $core_version = $info['core']; $core_build_dir = "drupal-{$core_version}"; $core_makefile = "{$core_build_dir}.make"; file_put_contents($core_makefile, core_make_file($core_version)); // Run drush_make to build core. if (!drupal_exec("{$drush} --include={$drush_make_dir} make {$core_makefile} {$core_build_dir}")) { // The build failed, bail out. wd_err("ERROR: Build for %core failed.", array('%core' => $core_build_dir), $release_node_view_link); return 'error'; } // Move the profile into place inside core. if (!rename($project_short_name, "{$core_build_dir}/profiles/{$project_short_name}")) { return 'error'; } $core_id = "{$release_file_id}-core"; // Build the drupal file path and the full file path. $core_file_path = "{$dest_rel}/{$core_id}.tar.gz"; $core_full_dest = "{$dest_root}/{$core_file_path}"; // Package the core distribution. // 'h' is for dereference, we want to include the files, not the links if (!drupal_exec("{$tar} -ch --file=- {$core_build_dir} | {$gzip} -9 --no-name > {$core_full_dest}")) { return 'error'; } $files[] = $core_file_path; // Development releases may have changed package contents -- clear out // their package item summary so a fresh item summary will be inserted. if ($type == 'branch' && module_exists('project_package')) { db_query("DELETE FROM {project_package_local_release_item} WHERE package_nid = %d", $nid); } // Core was built without the drupal.org drush extension, so the // package item for core isn't in the package contents file. Retrieve // it manually. $core_tag = 'DRUPAL-' . str_replace('.', '-', $core_version); if (!($core_release_nid = db_result(db_query("SELECT nid FROM {project_release_nodes} WHERE tag = '%s'", $core_tag)))) { return 'error'; } $package_contents[] = $core_release_nid; // Retrieve the package contents for the release. $package_contents_file = "{$project_build_root}/package_contents.txt"; if (file_exists($package_contents_file)) { $lines = file($package_contents_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($lines as $line) { if (is_numeric($line)) { $package_contents[] = $line; } } } else { wd_err("ERROR: %file does not exist for %profile release.", array('%file' => $package_contents_file, '%profile' => $release_file_id), $release_node_view_link); return 'error'; } } } else { wd_msg("No makefile for %profile profile -- skipping extended packaging.", array('%profile' => $release_file_id), $release_node_view_link); } } // As soon as the tarball exists, update the DB package_release_update_node($nid, $files, $package_contents); if ($tgz_exists) { wd_msg("%id has changed, re-packaged.", array('%id' => $release_file_id), $view_link); } else { wd_msg("Packaged %id.", array('%id' => $release_file_id), $view_link); } // Don't consider failure to remove this directory a build failure. drupal_exec("{$rm} -rf {$project_build_root}"); return 'success'; }