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';
}