diff options
-rw-r--r-- | .github/workflows/promote_to_rc.yml | 47 | ||||
-rw-r--r-- | cli/tools/upgrade.rs | 62 | ||||
-rw-r--r-- | tools/deno.lock.json | 68 | ||||
-rw-r--r-- | tools/release/promote_to_rc.ts | 212 |
4 files changed, 356 insertions, 33 deletions
diff --git a/.github/workflows/promote_to_rc.yml b/.github/workflows/promote_to_rc.yml new file mode 100644 index 000000000..9bd269d10 --- /dev/null +++ b/.github/workflows/promote_to_rc.yml @@ -0,0 +1,47 @@ +name: promote_to_rc + +on: + workflow_dispatch: + inputs: + commitHash: + description: 'Commit to promote to the Release Candidate' + required: true + +jobs: + promote-to-rc: + name: Promote to Release Candidate + runs-on: ubuntu-latest + if: github.repository == 'denoland/deno' + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.DENOBOT_PAT }} + submodules: recursive + + - name: Authenticate with Google Cloud + uses: google-github-actions/auth@v1 + with: + project_id: denoland + credentials_json: ${{ secrets.GCP_SA_KEY }} + export_environment_variables: true + create_credentials_file: true + + - name: Setup gcloud + uses: google-github-actions/setup-gcloud@v1 + with: + project_id: denoland + + - name: Install deno + uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + + - name: Promote to RC + run: | + deno run -A ./tools/release/promote_to_rc.ts ${{github.event.inputs.releaseKind}} + + - name: Upload archives to dl.deno.land + run: | + gsutil -h "Cache-Control: public, max-age=3600" cp ./*.zip gs://dl.deno.land/release/$(echo release-rc-latest.txt)/ + gsutil -h "Cache-Control: no-cache" cp release-rc-latest.txt gs://dl.deno.land/release-rc-latest.txt diff --git a/cli/tools/upgrade.rs b/cli/tools/upgrade.rs index fb5d7b7ab..583ae265f 100644 --- a/cli/tools/upgrade.rs +++ b/cli/tools/upgrade.rs @@ -35,6 +35,7 @@ use std::time::Duration; const RELEASE_URL: &str = "https://github.com/denoland/deno/releases"; const CANARY_URL: &str = "https://dl.deno.land/canary"; +const RC_URL: &str = "https://dl.deno.land/release"; pub static ARCHIVE_NAME: Lazy<String> = Lazy::new(|| format!("deno-{}.zip", env!("TARGET"))); @@ -474,7 +475,7 @@ pub async fn upgrade( let download_url = get_download_url( &selected_version_to_upgrade.version_or_hash, - requested_version.is_canary(), + requested_version.release_channel(), )?; log::info!("{}", colors::gray(format!("Downloading {}", &download_url))); let Some(archive_data) = download_package(&client, download_url).await? @@ -505,7 +506,7 @@ pub async fn upgrade( if upgrade_flags.dry_run { fs::remove_file(&new_exe_path)?; log::info!("Upgraded successfully (dry run)"); - if !requested_version.is_canary() { + if requested_version.release_channel() == ReleaseChannel::Stable { print_release_notes( version::DENO_VERSION_INFO.deno, &selected_version_to_upgrade.version_or_hash, @@ -529,7 +530,7 @@ pub async fn upgrade( "\nUpgraded successfully to Deno {}\n", colors::green(selected_version_to_upgrade.display()) ); - if !requested_version.is_canary() { + if requested_version.release_channel() == ReleaseChannel::Stable { print_release_notes( version::DENO_VERSION_INFO.deno, &selected_version_to_upgrade.display, @@ -583,14 +584,10 @@ impl RequestedVersion { } /// Channels that use Git hashes as versions are considered canary. - pub fn is_canary(&self) -> bool { + pub fn release_channel(&self) -> ReleaseChannel { match self { - Self::Latest(channel) => { - matches!(channel, ReleaseChannel::Canary | ReleaseChannel::Rc) - } - Self::SpecificVersion(channel, _) => { - matches!(channel, ReleaseChannel::Canary | ReleaseChannel::Rc) - } + Self::Latest(channel) => *channel, + Self::SpecificVersion(channel, _) => *channel, } } } @@ -663,7 +660,7 @@ async fn find_latest_version_to_upgrade( .await?; let (maybe_newer_latest_version, current_version) = match release_channel { - ReleaseChannel::Stable => { + ReleaseChannel::Stable | ReleaseChannel::Rc => { let current_version = version::DENO_VERSION_INFO.deno; let current_is_most_recent = if version::DENO_VERSION_INFO.release_channel != ReleaseChannel::Canary @@ -694,17 +691,6 @@ async fn find_latest_version_to_upgrade( (Some(latest_version_found), current_version) } } - ReleaseChannel::Rc => { - let current_version = version::DENO_VERSION_INFO.git_hash; - let current_is_most_recent = - current_version == latest_version_found.version_or_hash; - - if !force && current_is_most_recent { - (None, current_version) - } else { - (Some(latest_version_found), current_version) - } - } // TODO(bartlomieju) ReleaseChannel::Lts => unreachable!(), }; @@ -789,7 +775,7 @@ fn get_latest_version_url( ReleaseChannel::Canary => { Cow::Owned(format!("canary-{target_tuple}-latest.txt")) } - ReleaseChannel::Rc => Cow::Borrowed("release-rc.txt"), + ReleaseChannel::Rc => Cow::Borrowed("release-rc-latest.txt"), _ => unreachable!(), }; let query_param = match check_kind { @@ -808,11 +794,21 @@ fn base_upgrade_url() -> Cow<'static, str> { } } -fn get_download_url(version: &str, is_canary: bool) -> Result<Url, AnyError> { - let download_url = if is_canary { - format!("{}/{}/{}", CANARY_URL, version, *ARCHIVE_NAME) - } else { - format!("{}/download/v{}/{}", RELEASE_URL, version, *ARCHIVE_NAME) +fn get_download_url( + version: &str, + release_channel: ReleaseChannel, +) -> Result<Url, AnyError> { + let download_url = match release_channel { + ReleaseChannel::Stable => { + format!("{}/download/v{}/{}", RELEASE_URL, version, *ARCHIVE_NAME) + } + ReleaseChannel::Rc => { + format!("{}/v{}/{}", RC_URL, version, *ARCHIVE_NAME) + } + ReleaseChannel::Canary => { + format!("{}/{}/{}", CANARY_URL, version, *ARCHIVE_NAME) + } + ReleaseChannel::Lts => unreachable!(), }; Url::parse(&download_url).with_context(|| { @@ -1373,7 +1369,7 @@ mod test { "x86_64-pc-windows-msvc", UpgradeCheckKind::Lsp ), - "https://dl.deno.land/release-rc.txt?lsp" + "https://dl.deno.land/release-rc-latest.txt?lsp" ); assert_eq!( get_latest_version_url( @@ -1381,7 +1377,7 @@ mod test { "aarch64-apple-darwin", UpgradeCheckKind::Execution ), - "https://dl.deno.land/release-rc.txt" + "https://dl.deno.land/release-rc-latest.txt" ); assert_eq!( get_latest_version_url( @@ -1389,7 +1385,7 @@ mod test { "aarch64-apple-darwin", UpgradeCheckKind::Lsp ), - "https://dl.deno.land/release-rc.txt?lsp" + "https://dl.deno.land/release-rc-latest.txt?lsp" ); assert_eq!( get_latest_version_url( @@ -1397,7 +1393,7 @@ mod test { "x86_64-pc-windows-msvc", UpgradeCheckKind::Execution ), - "https://dl.deno.land/release-rc.txt" + "https://dl.deno.land/release-rc-latest.txt" ); assert_eq!( get_latest_version_url( @@ -1405,7 +1401,7 @@ mod test { "x86_64-pc-windows-msvc", UpgradeCheckKind::Lsp ), - "https://dl.deno.land/release-rc.txt?lsp" + "https://dl.deno.land/release-rc-latest.txt?lsp" ); } diff --git a/tools/deno.lock.json b/tools/deno.lock.json index d4f7c6230..46c09ce24 100644 --- a/tools/deno.lock.json +++ b/tools/deno.lock.json @@ -2,9 +2,77 @@ "version": "3", "packages": { "specifiers": { + "jsr:@david/dax@0.41.0": "jsr:@david/dax@0.41.0", + "jsr:@david/which@^0.4.1": "jsr:@david/which@0.4.1", + "jsr:@deno/patchver@0.1.0": "jsr:@deno/patchver@0.1.0", + "jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.0", + "jsr:@std/bytes@^0.221.0": "jsr:@std/bytes@0.221.0", + "jsr:@std/fmt@1": "jsr:@std/fmt@1.0.0", + "jsr:@std/fmt@^0.221.0": "jsr:@std/fmt@0.221.0", + "jsr:@std/fs@0.221.0": "jsr:@std/fs@0.221.0", + "jsr:@std/io@0.221.0": "jsr:@std/io@0.221.0", + "jsr:@std/io@^0.221.0": "jsr:@std/io@0.221.0", + "jsr:@std/path@0.221.0": "jsr:@std/path@0.221.0", + "jsr:@std/path@^0.221.0": "jsr:@std/path@0.221.0", + "jsr:@std/streams@0.221.0": "jsr:@std/streams@0.221.0", "jsr:@std/yaml@^0.221": "jsr:@std/yaml@0.221.0" }, "jsr": { + "@david/dax@0.41.0": { + "integrity": "9e1ecf66a0415962cc8ad3ba4e3fa93ce0f1a1cc797dd95c36fdfb6977dc7fc8", + "dependencies": [ + "jsr:@david/which@^0.4.1", + "jsr:@std/fmt@^0.221.0", + "jsr:@std/fs@0.221.0", + "jsr:@std/io@0.221.0", + "jsr:@std/path@0.221.0", + "jsr:@std/streams@0.221.0" + ] + }, + "@david/which@0.4.1": { + "integrity": "896a682b111f92ab866cc70c5b4afab2f5899d2f9bde31ed00203b9c250f225e" + }, + "@deno/patchver@0.1.0": { + "integrity": "3102aa1b751a9fb85ef6cf7d4c0a1ec6624c85a77facc140c5748d82126d66a6" + }, + "@std/assert@0.221.0": { + "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a" + }, + "@std/bytes@0.221.0": { + "integrity": "64a047011cf833890a4a2ab7293ac55a1b4f5a050624ebc6a0159c357de91966" + }, + "@std/fmt@0.221.0": { + "integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a" + }, + "@std/fmt@1.0.0": { + "integrity": "8a95c9fdbb61559418ccbc0f536080cf43341655e1444f9d375a66886ceaaa3d" + }, + "@std/fs@0.221.0": { + "integrity": "028044450299de8ed5a716ade4e6d524399f035513b85913794f4e81f07da286", + "dependencies": [ + "jsr:@std/assert@^0.221.0", + "jsr:@std/path@^0.221.0" + ] + }, + "@std/io@0.221.0": { + "integrity": "faf7f8700d46ab527fa05cc6167f4b97701a06c413024431c6b4d207caa010da", + "dependencies": [ + "jsr:@std/assert@^0.221.0", + "jsr:@std/bytes@^0.221.0" + ] + }, + "@std/path@0.221.0": { + "integrity": "0a36f6b17314ef653a3a1649740cc8db51b25a133ecfe838f20b79a56ebe0095", + "dependencies": [ + "jsr:@std/assert@^0.221.0" + ] + }, + "@std/streams@0.221.0": { + "integrity": "47f2f74634b47449277c0ee79fe878da4424b66bd8975c032e3afdca88986e61", + "dependencies": [ + "jsr:@std/io@^0.221.0" + ] + }, "@std/yaml@0.221.0": { "integrity": "bac8913ee4f6fc600d4b92cc020f755070e22687ad242341f31d123ff690ae98" } diff --git a/tools/release/promote_to_rc.ts b/tools/release/promote_to_rc.ts new file mode 100644 index 000000000..6d7375385 --- /dev/null +++ b/tools/release/promote_to_rc.ts @@ -0,0 +1,212 @@ +#!/usr/bin/env -S deno run -A --lock=tools/deno.lock.json +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +import { $ } from "jsr:@david/dax@0.41.0"; +import { gray } from "jsr:@std/fmt@1/colors"; +import { patchver } from "jsr:@deno/patchver@0.1.0"; + +const SUPPORTED_TARGETS = [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", +]; + +const DENO_BINARIES = [ + "deno", + "denort", +]; + +const CHANNEL = "rc"; + +const CANARY_URL = "https://dl.deno.land"; + +function getCanaryBinaryUrl( + version: string, + binary: string, + target: string, +): string { + return `${CANARY_URL}/canary/${version}/${binary}-${target}.zip`; +} + +function getUnzippedFilename(binary: string, target: string) { + if (target.includes("windows")) { + return `${binary}.exe`; + } else { + return binary; + } +} + +function getRcBinaryName(binary: string, target: string): string { + let ext = ""; + if (target.includes("windows")) { + ext = ".exe"; + } + return `${binary}-${target}-rc${ext}`; +} + +function getArchiveName(binary: string, target: string): string { + return `${binary}-${target}.zip`; +} + +interface CanaryVersion { + target: string; + version: string; +} + +async function remove(filePath: string) { + try { + await Deno.remove(filePath); + } catch { + // pass + } +} + +async function fetchLatestCanaryBinary( + version: string, + binary: string, + target: string, +) { + const url = getCanaryBinaryUrl(version, binary, target); + await $.request(url).showProgress().pipeToPath(); +} + +async function fetchLatestCanaryBinaries(canaryVersion: string) { + for (const binary of DENO_BINARIES) { + for (const target of SUPPORTED_TARGETS) { + $.logStep("Download", binary, gray("target:"), target); + await fetchLatestCanaryBinary(canaryVersion, binary, target); + } + } +} + +async function unzipArchive(archiveName: string, unzippedName: string) { + await remove(unzippedName); + const output = await $`unzip ./${archiveName}`; + if (output.code !== 0) { + $.logError(`Failed to unzip ${archiveName} (error code ${output.code})`); + Deno.exit(1); + } +} + +async function createArchive(rcBinaryName: string, archiveName: string) { + const output = await $`zip -r ./${archiveName} ./${rcBinaryName}`; + + if (output.code !== 0) { + $.logError( + `Failed to create archive ${archiveName} (error code ${output.code})`, + ); + Deno.exit(1); + } +} + +async function runPatchver( + binary: string, + target: string, + rcBinaryName: string, +) { + const input = await Deno.readFile(binary); + const output = patchver(input, CHANNEL); + + try { + await Deno.writeFile(rcBinaryName, output); + } catch (e) { + $.logError( + `Failed to promote to RC ${binary} (${target}), error:`, + e, + ); + Deno.exit(1); + } +} + +async function promoteBinaryToRc(binary: string, target: string) { + const unzippedName = getUnzippedFilename(binary, target); + const rcBinaryName = getRcBinaryName(binary, target); + const archiveName = getArchiveName(binary, target); + await remove(unzippedName); + await remove(rcBinaryName); + $.logStep( + "Unzip", + archiveName, + gray("binary"), + binary, + gray("rcBinaryName"), + rcBinaryName, + ); + + await unzipArchive(archiveName, unzippedName); + await remove(archiveName); + + $.logStep( + "Patchver", + unzippedName, + `(${target})`, + gray("output to"), + rcBinaryName, + ); + await runPatchver(unzippedName, target, rcBinaryName); + // Remove the unpatched binary and rename patched one. + await remove(unzippedName); + await Deno.rename(rcBinaryName, unzippedName); + // Set executable permission + if (!target.includes("windows")) { + Deno.chmod(unzippedName, 0o777); + } + + await createArchive(unzippedName, archiveName); + await remove(unzippedName); +} + +async function promoteBinariesToRc() { + const totalCanaries = SUPPORTED_TARGETS.length * DENO_BINARIES.length; + + for (let targetIdx = 0; targetIdx < SUPPORTED_TARGETS.length; targetIdx++) { + const target = SUPPORTED_TARGETS[targetIdx]; + for (let binaryIdx = 0; binaryIdx < DENO_BINARIES.length; binaryIdx++) { + const binaryName = DENO_BINARIES[binaryIdx]; + const currentIdx = (targetIdx * 2) + binaryIdx + 1; + $.logLight( + `[${currentIdx}/${totalCanaries}]`, + "Promote", + binaryName, + target, + "to RC...", + ); + await promoteBinaryToRc(binaryName, target); + $.logLight( + `[${currentIdx}/${totalCanaries}]`, + "Promoted", + binaryName, + target, + "to RC!", + ); + } + } +} + +async function dumpRcVersion() { + $.logStep("Compute version"); + await unzipArchive(getArchiveName("deno", Deno.build.target), "deno"); + const output = await $`./deno -V`.stdout("piped"); + const denoVersion = output.stdout.slice(5).split("+")[0]; + $.logStep("Computed version", denoVersion); + await Deno.writeTextFile("./release-rc-latest.txt", denoVersion); +} + +async function main() { + const commitHash = Deno.args[0]; + if (!commitHash) { + throw new Error("Commit hash needs to be provided as an argument"); + } + $.logStep("Download canary binaries..."); + await fetchLatestCanaryBinaries(commitHash); + console.log("All canary binaries ready"); + $.logStep("Promote canary binaries to RC..."); + await promoteBinariesToRc(); + + // Finally dump the version name to a `release.txt` file for uploading to GCP + await dumpRcVersion(); +} + +await main(); |