/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
.github/workflows/ci.ts
2 260 строк
86 KB
Divy Srivastava
ci: only relink startup-function order on main and release tags (#36453)
07 авг 2026, 13:18
Не верифицирован
07 авг 2026, 13:18
a9c091e
Код
Авторство
О чём код?
#!/usr/bin/env -S deno run --check --allow-write=. --allow-read=. --lock=./tools/deno.lock.json // Copyright 2018-2026 the Deno authors. MIT license. import { parse as parseToml } from "jsr:@std/toml@1"; import { Condition, conditions, type ConfigValue, createWorkflow, defineArtifact, defineExprObj, defineMatrix, type ExpressionValue, job, literal, step, } from "jsr:@david/gagen@0.3.1"; // Bump this number when you want to purge the cache. // Note: the tools/release/01_bump_crate_versions.ts script will update this version // automatically via regex, so ensure that this line maintains this format. const cacheVersion = 123; const ubuntuX86Runner = "ubuntu-24.04"; const ubuntuARMRunner = "ubuntu-24.04-arm"; const ubuntuARMXlRunner = "ubuntu-24.04-arm64-xl"; const windowsX86Runner = "windows-2022"; const windowsX86XlRunner = "windows-2022-xl"; const windowsArmRunner = "windows-11-arm"; const macosX86Runner = "macos-15-intel"; const macosArmRunner = "macos-14"; // shared conditions const isDenoland = conditions.isRepository("denoland/deno"); const isMainBranch = conditions.isBranch("main"); const isTag = conditions.isTag(); const isNotTag = isTag.not(); const isMainOrTag = isMainBranch.or(isTag); const isPr = conditions.isPr(); const hasCiFullLabel = conditions.hasPrLabel("ci-full"); const hasCiBenchLabel = conditions.hasPrLabel("ci-bench"); const Runners = { linuxX86: { os: "linux", arch: "x86_64", runner: ubuntuX86Runner, }, linuxX86Xl: { os: "linux", arch: "x86_64", runner: ubuntuX86Runner, }, linuxArm: { os: "linux", arch: "aarch64", runner: ubuntuARMRunner, }, linuxArmXl: { os: "linux", arch: "aarch64", runner: isDenoland.and(isMainOrTag.or(hasCiFullLabel)).then( ubuntuARMXlRunner, ).else(ubuntuARMRunner), testRunner: ubuntuARMRunner, }, macosX86: { os: "macos", arch: "x86_64", runner: macosX86Runner, }, macosArm: { os: "macos", arch: "aarch64", runner: macosArmRunner, }, macosArmSelfHosted: { os: "macos", arch: "aarch64", runner: macosArmRunner, }, windowsX86: { os: "windows", arch: "x86_64", runner: windowsX86Runner, }, windowsX86Xl: { os: "windows", arch: "x86_64", runner: isDenoland.then(windowsX86XlRunner).else(windowsX86Runner), testRunner: windowsX86Runner, }, windowsArm: { os: "windows", arch: "aarch64", runner: windowsArmRunner, }, } as const; const denoCorePackageDirs = [ "libs/core_testing", "libs/core", "libs/core/examples/snapshot", "libs/dcore", "libs/ops", "libs/ops/compile_test_runner", "libs/serde_v8", ]; // discover test crates first so we know which workspace members are test packages const { testCrates, testPackageMembers } = resolveTestCrateTests(); // discover workspace members for the libs test job, split by type const { binCrates, libCrates } = resolveWorkspaceCrates( testPackageMembers, ); // Note that you may need to add more version to the `apt-get remove` line below if you change this const llvmVersion = 22; const installPkgsCommand = `sudo apt-get install -y --no-install-recommends clang-${llvmVersion} lld-${llvmVersion} clang-tools-${llvmVersion} clang-format-${llvmVersion} clang-tidy-${llvmVersion}`; const sysRootConfig = { name: "Set up incremental LTO and sysroot build", run: `# Setting up sysroot export DEBIAN_FRONTEND=noninteractive # Avoid running man-db triggers, which sometimes takes several minutes # to complete. sudo apt-get -qq remove --purge -y man-db > /dev/null 2> /dev/null # Remove older clang before we install sudo apt-get -qq remove \ 'clang-12*' 'clang-13*' 'clang-14*' 'clang-15*' 'clang-16*' 'clang-17*' 'clang-18*' 'clang-19*' 'clang-20*' 'clang-21*' 'llvm-12*' 'llvm-13*' 'llvm-14*' 'llvm-15*' 'llvm-16*' 'llvm-17*' 'llvm-18*' 'llvm-19*' 'llvm-20*' 'llvm-21*' 'lld-12*' 'lld-13*' 'lld-14*' 'lld-15*' 'lld-16*' 'lld-17*' 'lld-18*' 'lld-19*' 'lld-20*' 'lld-21*' > /dev/null 2> /dev/null # Install clang-XXX, lld-XXX, and debootstrap. echo "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-${llvmVersion} main" | sudo dd of=/etc/apt/sources.list.d/llvm-toolchain-noble-${llvmVersion}.list curl https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor | sudo dd of=/etc/apt/trusted.gpg.d/llvm-snapshot.gpg sudo apt-get update # this was unreliable sometimes, so try again if it fails ${installPkgsCommand} || (echo 'Failed. Trying again.' && sudo apt-get clean && sudo apt-get update && ${installPkgsCommand}) # Fix alternatives (yes '' | sudo update-alternatives --force --all) > /dev/null 2> /dev/null || true clang-${llvmVersion} -c -o /tmp/memfd_create_shim.o tools/memfd_create_shim.c -fPIC clang-${llvmVersion} -c -o /tmp/glibc_math_shim.o tools/glibc_math_shim.c -fPIC echo "Decompressing sysroot..." wget -q https://github.com/denoland/deno_sysroot_build/releases/download/sysroot-20250207/sysroot-\`uname -m\`.tar.xz -O /tmp/sysroot.tar.xz cd / xzcat /tmp/sysroot.tar.xz | sudo tar -x sudo mount --rbind /dev /sysroot/dev sudo mount --rbind /sys /sysroot/sys sudo mount --rbind /home /sysroot/home sudo mount -t proc /proc /sysroot/proc cd echo "Done." # Configure the build environment. Both Rust and Clang will produce # llvm bitcode only, so we can use lld's incremental LTO support. # Load the sysroot's env vars echo "sysroot env:" cat /sysroot/.env . /sysroot/.env # Important notes: # 1. -ldl seems to be required to avoid a failure in FFI tests. This flag seems # to be in the Rust default flags in the smoketest, so uncertain why we need # to be explicit here. # 2. RUSTFLAGS and RUSTDOCFLAGS must be specified, otherwise the doctests fail # to build because the object formats are not compatible. echo " CARGO_PROFILE_BENCH_INCREMENTAL=false CARGO_PROFILE_RELEASE_INCREMENTAL=false RUSTFLAGS<<__1 -C linker-plugin-lto=true -C linker=clang-${llvmVersion} -C link-arg=-fuse-ld=lld-${llvmVersion} -C link-arg=-Wl,--icf=safe -C link-arg=-ldl -C link-arg=-Wl,--allow-shlib-undefined -C link-arg=-Wl,--thinlto-cache-dir=$(pwd)/target/release/lto-cache -C link-arg=-Wl,--thinlto-cache-policy,cache_size_bytes=700m -C link-arg=/tmp/memfd_create_shim.o -C link-arg=/tmp/glibc_math_shim.o -C link-arg=-Wl,--wrap=expf -C link-arg=-Wl,--wrap=powf -C link-arg=-Wl,--wrap=exp2f -C link-arg=-Wl,--wrap=log2f -C link-arg=-Wl,--wrap=logf --cfg tokio_unstable $RUSTFLAGS __1 RUSTDOCFLAGS<<__1 -C linker-plugin-lto=true -C linker=clang-${llvmVersion} -C link-arg=-fuse-ld=lld-${llvmVersion} -C link-arg=-Wl,--icf=safe -C link-arg=-ldl -C link-arg=-Wl,--allow-shlib-undefined -C link-arg=-Wl,--thinlto-cache-dir=$(pwd)/target/release/lto-cache -C link-arg=-Wl,--thinlto-cache-policy,cache_size_bytes=700m -C link-arg=/tmp/memfd_create_shim.o -C link-arg=/tmp/glibc_math_shim.o -C link-arg=-Wl,--wrap=expf -C link-arg=-Wl,--wrap=powf -C link-arg=-Wl,--wrap=exp2f -C link-arg=-Wl,--wrap=log2f -C link-arg=-Wl,--wrap=logf --cfg tokio_unstable $RUSTFLAGS __1 CC=/usr/bin/clang-${llvmVersion} CFLAGS=$CFLAGS " > $GITHUB_ENV`, }; const S3Envs: Readonly<Record<string, ConfigValue>> = { AWS_ACCESS_KEY_ID: "${{ vars.S3_ACCESS_KEY_ID }}", AWS_SECRET_ACCESS_KEY: "${{ secrets.S3_SECRET_ACCESS_KEY }}", AWS_ENDPOINT_URL_S3: "${{ vars.S3_ENDPOINT }}", AWS_DEFAULT_REGION: "${{ vars.S3_REGION }}", }; function handleBuildItems(items: { skip_pr?: Condition | true; skip?: Condition | boolean; os: "linux" | "macos" | "windows"; arch: "x86_64" | "aarch64"; runner: string | ExpressionValue; profile: string; use_sysroot?: boolean; testRunner?: string | ExpressionValue; wpt?: Condition | boolean; }[]) { return items.map(({ skip_pr, ...rest }) => { const defaultValues = { skip: false, "use_sysroot": false, wpt: false, }; if (skip_pr == null) { return { ...defaultValues, ...rest, save_cache: true, }; } else { // on PRs without the ci-full label, use a free runner and skip the job const shouldSkip = hasCiFullLabel.not().and(isPr).and(skip_pr); return { ...defaultValues, ...rest, testRunner: shouldSkip.then(ubuntuX86Runner).else( rest.testRunner ?? rest.runner, ), runner: shouldSkip.then(ubuntuX86Runner).else(rest.runner), skip: shouldSkip, // do not save the cache on main if it won't be used by prs most of the time save_cache: skip_pr !== true, }; } }); } // shared steps const cloneRepoStep = step({ name: "Configure git", run: [ "git config --global core.symlinks true", "git config --global fetch.parallel 32", ], }, { name: "Clone repository", uses: "actions/checkout@v6", with: { // Use depth > 1, because sometimes we need to rebuild main and if // other commits have landed it will become impossible to rebuild if // the checkout is too shallow. "fetch-depth": 5, submodules: false, }, }); const cloneSubmodule = (path: string) => step({ name: `Clone submodule ${path}`, run: `git submodule update --init --recursive --depth=1 -- ${path}`, }); const cloneStdSubmoduleStep = cloneSubmodule("./tests/util/std"); const installDenoStep = step({ name: "Install Deno", uses: "denoland/setup-deno@v2", with: { "deno-version": "v2.x" }, }); const installNodeStep = step({ name: "Install Node", uses: "actions/setup-node@v6", with: { "node-version": 22, }, }); function createRestoreAndSaveCacheSteps(m: { name: string; cacheKeyPrefix: string; path: string[]; }) { // this must match for save and restore (https://github.com/actions/cache/issues/1444) const path = m.path.join("\n"); const restoreCacheStep = step({ name: `Restore cache ${m.name}`, uses: "actions/cache/restore@v4", with: { path, key: "never_saved", "restore-keys": `${m.cacheKeyPrefix}-`, }, }); const saveCacheStep = step({ name: `Cache ${m.name}`, uses: "actions/cache/save@v4", with: { path, // We force saving a new cache on every main run so that PRs can // always be up to date with the freshest information. We do this // unconditionally because we don't want caches that only need updating // occassionally (like the cargo home cache) to be lost over time as // other caches that need to be updated frequently (like the cargo build // cache) get populated and purge old caches. key: `${m.cacheKeyPrefix}-\${{ github.sha }}`, }, }); return { restoreCacheStep, saveCacheStep }; } function createCargoCacheHomeStep(m: { os: ExpressionValue; arch: ExpressionValue; cachePrefix: string; }) { const steps = createRestoreAndSaveCacheSteps({ name: "cargo home", path: [ "~/.cargo/.crates.toml", "~/.cargo/.crates2.json", "~/.cargo/bin", "~/.cargo/registry/index", "~/.cargo/registry/cache", "~/.cargo/git/db", ], cacheKeyPrefix: `${cacheVersion}-cargo-home-${m.os}-${m.arch}-${m.cachePrefix}`, }); return { restoreCacheStep: steps.restoreCacheStep.if(isNotTag), saveCacheStep: steps.saveCacheStep.if(isMainBranch.and(isNotTag)), }; } // factory for cache steps parameterized by os/arch/profile/job // works with both defineExprObj (inline values) and defineMatrix (matrix expressions) function createCacheSteps(m: { os: ExpressionValue; arch: ExpressionValue; profile: ExpressionValue; cachePrefix: string; }) { const cargoHomeCacheSteps = createCargoCacheHomeStep(m); const buildCacheSteps = createRestoreAndSaveCacheSteps({ name: "build output", path: [ "./target", "!./target/*/gn_out", "!./target/*/gn_root", "!./target/*/*.zip", "!./target/*/*.tar.gz", ], cacheKeyPrefix: `${cacheVersion}-cargo-target-${m.os}-${m.arch}-${m.profile}-${m.cachePrefix}`, }); const mtimeCacheAndRestoreStep = step({ name: "Apply and update mtime cache", uses: "./.github/mtime_cache", with: { "cache-path": "./target", }, }); return { restoreCacheStep: step( cargoHomeCacheSteps.restoreCacheStep, buildCacheSteps.restoreCacheStep.if(isMainBranch.not().and(isNotTag)), // this should always be done when saving OR restoring mtimeCacheAndRestoreStep, ), saveCacheStep: step( cargoHomeCacheSteps.saveCacheStep, buildCacheSteps.saveCacheStep.if(isMainBranch.and(isNotTag)), ), }; } // Pin rustup-init to 1.28.2: sh.rustup.rs currently serves 1.29.0, which has // a broken proxy multi-call dispatch (cargo/rustc identify as rustup-init). // Pre-installing rustup makes `dsherret/rust-toolchain-file@v1`'s internal // `command -v rustup` check short-circuit and skip the broken curl install. const installRustStep = step( step({ name: "Pre-install rustup 1.28.2 (workaround broken 1.29.0)", shell: "bash", run: [ "if command -v rustup >/dev/null 2>&1; then", " if ! rustup --version 2>&1 | grep -q '1\\.29\\.0'; then exit 0; fi", ' echo "Detected broken rustup 1.29.0, replacing with 1.28.2"', "fi", 'case "${RUNNER_OS}-${RUNNER_ARCH}" in', " Linux-X64) target=x86_64-unknown-linux-gnu; ext= ;;", " Linux-ARM64) target=aarch64-unknown-linux-gnu; ext= ;;", " macOS-X64) target=x86_64-apple-darwin; ext= ;;", " macOS-ARM64) target=aarch64-apple-darwin; ext= ;;", " Windows-X64) target=x86_64-pc-windows-msvc; ext=.exe ;;", " Windows-ARM64) target=aarch64-pc-windows-msvc; ext=.exe ;;", ' *) echo "Unsupported: ${RUNNER_OS}-${RUNNER_ARCH}"; exit 1 ;;', "esac", "curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused -fsSL \\", ' "https://static.rust-lang.org/rustup/archive/1.28.2/${target}/rustup-init${ext}" \\', ' -o "rustup-init${ext}"', 'chmod +x "rustup-init${ext}"', '"./rustup-init${ext}" -y --default-toolchain none --no-modify-path', 'rm "rustup-init${ext}"', 'echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> "$GITHUB_PATH"', ].join("\n"), }), step({ uses: "dsherret/rust-toolchain-file@v1", }), ); const installWasmStep = step({ name: "Install wasm target", run: "rustup target add wasm32-unknown-unknown", }); function getOsSpecificSteps({ isWindows, isMacos, isAarch64, }: { isWindows: Condition; isMacos: Condition; isAarch64: Condition; }) { const installPythonStep = step({ name: "Install Python", uses: "actions/setup-python@v6", with: { "python-version": 3.11, }, }, { name: "Remove unused versions of Python", if: isWindows, shell: "pwsh", run: [ '$env:PATH -split ";" |', ' Where-Object { Test-Path "$_\\python.exe" } |', " Select-Object -Skip 1 |", ' ForEach-Object { Move-Item "$_" "$_.disabled" }', ], }); const setupPrebuiltMacStep = step({ if: isMacos, env: { GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}", }, run: "echo $GITHUB_WORKSPACE/third_party/prebuilt/mac >> $GITHUB_PATH", }); const installLldStep = step .dependsOn( cloneStdSubmoduleStep, installDenoStep, setupPrebuiltMacStep, )({ name: "Install macOS aarch64 lld", if: isMacos.and(isAarch64), env: { GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}", }, run: "./tools/install_prebuilt.js ld64.lld", }); return { installPythonStep, setupPrebuiltMacStep, installLldStep, }; } // === pre_build job === // The pre_build step is used to skip running the CI on draft PRs and to not even // start the build job. This can be overridden by adding [ci] to the commit title const preBuildCheckStep = step({ id: "check", if: conditions.hasPrLabel("ci-draft").not(), run: [ "GIT_MESSAGE=$(git log --format=%s -n 1 ${{github.event.after}})", "echo Commit message: $GIT_MESSAGE", "echo $GIT_MESSAGE | grep '\\[ci\\]' || (echo 'Exiting due to draft PR. Commit with [ci] to bypass or add the ci-draft label.' ; echo 'skip_build=true' >> $GITHUB_OUTPUT)", ], outputs: ["skip_build"] as const, }); const denoCoreChangesCheckStep = step({ id: "deno_core_changes", run: [ // Fetch the base SHA so it's available even in shallow clones `git fetch --depth=1 origin \${{ github.event.pull_request.base.sha }}`, `deno run -A tools/check_deno_core_changes.js \${{ github.event.pull_request.base.sha }}`, ], outputs: ["skip_deno_core_test"] as const, }); // Detects PRs that only touch the `doc/` directory. Such PRs run the `lint` // job alone (markdown is still formatted/linted) and skip the build, test, // bench and deno_core jobs. The base SHA is already fetched by the deno_core // changes step above. const docsOnlyChangesCheckStep = step({ id: "docs_only_changes", run: [ `deno run -A tools/check_docs_only_changes.js \${{ github.event.pull_request.base.sha }}`, ], outputs: ["docs_only"] as const, }); const preBuildJob = job("pre_build", { name: "pre-build", runsOn: "ubuntu-latest", steps: step.if(isPr)( cloneRepoStep, installDenoStep, step.if(conditions.isDraftPr())(preBuildCheckStep), denoCoreChangesCheckStep, docsOnlyChangesCheckStep, ), outputs: { skip_build: preBuildCheckStep.outputs.skip_build, skip_deno_core_test: denoCoreChangesCheckStep.outputs.skip_deno_core_test, docs_only: docsOnlyChangesCheckStep.outputs.docs_only, }, }); // Jobs that compile or test code should not run when a PR only edits docs. const notDocsOnly = preBuildJob.outputs.docs_only.notEquals("true"); // === build job === const buildItems = handleBuildItems([{ ...Runners.macosX86, profile: "debug", }, { ...Runners.macosX86, profile: "release", skip_pr: true, }, { ...Runners.macosArm, profile: "debug", }, { ...Runners.macosArmSelfHosted, profile: "release", skip_pr: true, }, { ...Runners.windowsX86, profile: "debug", }, { ...Runners.windowsX86Xl, profile: "release", skip_pr: true, }, { ...Runners.windowsArm, profile: "debug", }, { ...Runners.windowsArm, profile: "release", skip_pr: true, }, { ...Runners.linuxX86Xl, profile: "release", use_sysroot: true, // Because CI is so slow on for OSX and Windows, we // currently run the Web Platform tests only on Linux. wpt: isNotTag, }, { ...Runners.linuxX86, profile: "debug", use_sysroot: true, }, { ...Runners.linuxArm, profile: "debug", }, { ...Runners.linuxArmXl, profile: "release", use_sysroot: true, skip_pr: true, }]); const buildJobs = buildItems.map((rawBuildItem) => { const buildItem = defineExprObj(rawBuildItem); // Linux release artifacts use frame pointers for stack walking, so their // DWARF unwind tables can be removed during packaging. Rebuild std to keep // the frame-pointer chain intact through Rust code. const usesFramePointerPanicTrace = rawBuildItem.profile === "release" && rawBuildItem.os === "linux"; const buildStdArgs = usesFramePointerPanicTrace ? " -Zbuild-std=core,alloc,std,proc_macro,panic_abort" : ""; const panicTraceFeatures = usesFramePointerPanicTrace ? "deno/panic-trace-frame-pointer" : "deno/panic-trace"; const usesStartupOrder = rawBuildItem.profile === "release" && ((rawBuildItem.os === "linux" && (rawBuildItem.arch === "x86_64" || rawBuildItem.arch === "aarch64")) || (rawBuildItem.os === "macos" && rawBuildItem.arch === "aarch64")); const startupOrderTarget = rawBuildItem.os === "macos" ? "aarch64-apple-darwin" : `${rawBuildItem.arch}-unknown-linux-gnu`; const startupOrderPath = `target/release/startup-order-${startupOrderTarget}.order`; // The startup-order two-pass build (trace startup workloads, relink with the // generated order, then verify) adds several minutes to the release build, // most visibly to `release linux-x86_64` which is the only release build that // runs on PRs. The ordered binary is only shipped/benchmarked from main and // release tags, so restrict the extra passes to those; PRs (and anyone // iterating on the ordering tooling) can opt back in with the `ci-full` label. const runStartupOrder = isMainOrTag.or(hasCiFullLabel); const isLinux = buildItem.os.equals("linux"); const isWindows = buildItem.os.equals("windows"); const isMacos = buildItem.os.equals("macos"); const profileName = `${buildItem.profile}-${buildItem.os}-${buildItem.arch}`; const jobIdForJob = (name: string) => `${name}-${profileName}`; const jobNameForJob = (name: string) => `${name} ${buildItem.profile} ${buildItem.os}-${buildItem.arch}`; const createBinaryArtifact = (name: string) => { const directory = `target/${buildItem.profile}`; const exeExt = rawBuildItem.os === "windows" ? ".exe" : ""; const fileName = `${name}${exeExt}`; const artifact = defineArtifact( `${profileName}-${name.replaceAll("_", "-")}`, { retentionDays: 3, }, ); const filePath = `${directory}/${fileName}`; return { upload() { return artifact.upload({ path: filePath, }); }, download() { return step( artifact.download({ dirPath: directory, }), step({ name: `Set ${filePath} permissions`, if: isWindows.not(), run: `chmod +x ${filePath}`, }), ); }, }; }; const denoArtifact = createBinaryArtifact("deno"); const denortArtifact = createBinaryArtifact("denort"); const testServerArtifact = createBinaryArtifact("test_server"); const env = { CARGO_TERM_COLOR: "always", RUST_BACKTRACE: "full", // disable anyhow's library backtrace RUST_LIB_BACKTRACE: 0, }; const defaults = { run: { // GH actions does not fail fast by default on // Windows, so we set bash as the default shell shell: "bash", }, }; const { installPythonStep, setupPrebuiltMacStep, installLldStep, } = getOsSpecificSteps({ isWindows, isMacos, isAarch64: buildItem.arch.equals("aarch64"), }); const isRelease = buildItem.profile.equals("release"); const isDebug = buildItem.profile.equals("debug"); const sysRootStep = step({ if: buildItem.use_sysroot, ...sysRootConfig, }); const buildJob = job( jobIdForJob("build"), { name: jobNameForJob("build"), needs: [preBuildJob], if: preBuildJob.outputs.skip_build.notEquals("true").and(notDocsOnly), runsOn: buildItem.runner, // This is required to successfully authenticate with Azure using OIDC for // code signing. environment: { name: isMainOrTag.then("build").else(""), }, timeoutMinutes: 240, defaults, env, steps: (() => { const { restoreCacheStep, saveCacheStep, } = createCacheSteps({ ...buildItem, cachePrefix: "build-main", }); const tarSourcePublishStep = step({ name: "Create source tarballs (release, linux)", if: buildItem.os.equals("linux") .and(buildItem.arch.equals("x86_64")), run: [ "mkdir -p target/release", 'tar --exclude=".git*" --exclude=target --exclude=third_party/prebuilt \\', " -czvf target/release/deno_src.tar.gz -C .. deno", ], }); const preRelease = step( { name: "Pre-release (linux)", if: isLinux.and(isDenoland), run: [ "cd target/release", `./deno -A ../../tools/release/create_symcache.ts deno-${buildItem.arch}-unknown-linux-gnu.symcache`, "strip --remove-section=.eh_frame --remove-section=.eh_frame_hdr ./deno", "if readelf -SW ./deno | grep -Eq '\\.eh_frame(_hdr)?'; then echo 'unwind sections remain in deno'; exit 1; fi", "if readelf -lW ./deno | grep -q GNU_EH_FRAME; then echo 'PT_GNU_EH_FRAME remains in deno'; exit 1; fi", `shasum -a 256 deno > deno-${buildItem.arch}-unknown-linux-gnu.sha256sum`, `zip -r deno-${buildItem.arch}-unknown-linux-gnu.zip deno`, `shasum -a 256 deno-${buildItem.arch}-unknown-linux-gnu.zip > deno-${buildItem.arch}-unknown-linux-gnu.zip.sha256sum`, // denort is the `deno compile` base binary: libsui rewrites its // ELF to embed user code, and that rewrite drops `.relr.dyn` // relative relocations when the symbol table is gone, producing a // compiled binary whose C++ static-init guards deadlock // (`__cxa_guard_acquire failed to acquire mutex`). Keep .symtab // (strip only debug info) so the relocations survive. "strip --strip-debug --remove-section=.eh_frame --remove-section=.eh_frame_hdr ./denort", "if readelf -SW ./denort | grep -Eq '\\.eh_frame(_hdr)?'; then echo 'unwind sections remain in denort'; exit 1; fi", "if readelf -lW ./denort | grep -q GNU_EH_FRAME; then echo 'PT_GNU_EH_FRAME remains in denort'; exit 1; fi", `zip -r denort-${buildItem.arch}-unknown-linux-gnu.zip denort`, `shasum -a 256 denort-${buildItem.arch}-unknown-linux-gnu.zip > denort-${buildItem.arch}-unknown-linux-gnu.zip.sha256sum`, "strip --remove-section=.eh_frame --remove-section=.eh_frame_hdr ./libdenort.so", "if readelf -SW ./libdenort.so | grep -Eq '\\.eh_frame(_hdr)?'; then echo 'unwind sections remain in libdenort.so'; exit 1; fi", "if readelf -lW ./libdenort.so | grep -q GNU_EH_FRAME; then echo 'PT_GNU_EH_FRAME remains in libdenort.so'; exit 1; fi", `zip -r libdenort-${buildItem.arch}-unknown-linux-gnu.zip libdenort.so`, `shasum -a 256 libdenort-${buildItem.arch}-unknown-linux-gnu.zip > libdenort-${buildItem.arch}-unknown-linux-gnu.zip.sha256sum`, // QuickJS denort/libdenort for `deno compile --engine quickjs`. // Reuse the same target dir (a second one would exceed runner // disk). Back up the v8 binaries and restore them even if the // QuickJS build fails so later release steps never see a partial // or wrong-engine runtime. "restore_v8_runtimes() {", " test ! -e denort.v8 || mv -f denort.v8 denort", " test ! -e libdenort.v8.so || mv -f libdenort.v8.so libdenort.so", "}", "trap restore_v8_runtimes EXIT", "mv denort denort.v8", "mv libdenort.so libdenort.v8.so", `(cd ../.. && cargo build --release --locked -p denort -p denort_desktop --no-default-features --features quickjs)`, "strip --strip-debug ./denort", `zip -r denort-quickjs-${buildItem.arch}-unknown-linux-gnu.zip denort`, `shasum -a 256 denort-quickjs-${buildItem.arch}-unknown-linux-gnu.zip > denort-quickjs-${buildItem.arch}-unknown-linux-gnu.zip.sha256sum`, "strip ./libdenort.so", `zip -r libdenort-quickjs-${buildItem.arch}-unknown-linux-gnu.zip libdenort.so`, `shasum -a 256 libdenort-quickjs-${buildItem.arch}-unknown-linux-gnu.zip > libdenort-quickjs-${buildItem.arch}-unknown-linux-gnu.zip.sha256sum`, "restore_v8_runtimes", "trap - EXIT", "./deno types > lib.deno.d.ts", ], }, step.dependsOn(setupPrebuiltMacStep, installDenoStep)({ name: "Install rust-codesign", if: buildItem.os.equals("macos").and(isDenoland), env: { GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}", }, run: "./tools/install_prebuilt.js rcodesign", }), { name: "Pre-release (mac)", if: isMacos.and(isDenoland), env: { "APPLE_CODESIGN_KEY": "${{ secrets.APPLE_CODESIGN_KEY }}", "APPLE_CODESIGN_PASSWORD": "${{ secrets.APPLE_CODESIGN_PASSWORD }}", }, run: [ `target/release/deno -A tools/release/create_symcache.ts target/release/deno-${buildItem.arch}-apple-darwin.symcache`, "strip -x -S target/release/deno", 'if [[ "$GITHUB_REF" == "refs/heads/main" || "$GITHUB_REF" == refs/tags/* ]]; then', ' echo "Key is $(echo $APPLE_CODESIGN_KEY | base64 -d | wc -c) bytes"', " rcodesign sign target/release/deno " + "--code-signature-flags=runtime " + '--p12-password="$APPLE_CODESIGN_PASSWORD" ' + "--p12-file=<(echo $APPLE_CODESIGN_KEY | base64 -d) " + "--entitlements-xml-file=cli/entitlements.plist", "fi", "cd target/release", `shasum -a 256 deno > deno-${buildItem.arch}-apple-darwin.sha256sum`, `zip -r deno-${buildItem.arch}-apple-darwin.zip deno`, `shasum -a 256 deno-${buildItem.arch}-apple-darwin.zip > deno-${buildItem.arch}-apple-darwin.zip.sha256sum`, "strip -x -S ./denort", `zip -r denort-${buildItem.arch}-apple-darwin.zip denort`, `shasum -a 256 denort-${buildItem.arch}-apple-darwin.zip > denort-${buildItem.arch}-apple-darwin.zip.sha256sum`, "strip -x -S ./libdenort.dylib", `zip -r libdenort-${buildItem.arch}-apple-darwin.zip libdenort.dylib`, `shasum -a 256 libdenort-${buildItem.arch}-apple-darwin.zip > libdenort-${buildItem.arch}-apple-darwin.zip.sha256sum`, // QuickJS denort/libdenort for `deno compile --engine quickjs` // (see the linux note). "restore_v8_runtimes() {", " test ! -e denort.v8 || mv -f denort.v8 denort", " test ! -e libdenort.v8.dylib || mv -f libdenort.v8.dylib libdenort.dylib", "}", "trap restore_v8_runtimes EXIT", "mv denort denort.v8", "mv libdenort.dylib libdenort.v8.dylib", `(cd ../.. && cargo build --release --locked -p denort -p denort_desktop --no-default-features --features quickjs)`, "strip -x -S ./denort", `zip -r denort-quickjs-${buildItem.arch}-apple-darwin.zip denort`, `shasum -a 256 denort-quickjs-${buildItem.arch}-apple-darwin.zip > denort-quickjs-${buildItem.arch}-apple-darwin.zip.sha256sum`, "strip -x -S ./libdenort.dylib", `zip -r libdenort-quickjs-${buildItem.arch}-apple-darwin.zip libdenort.dylib`, `shasum -a 256 libdenort-quickjs-${buildItem.arch}-apple-darwin.zip > libdenort-quickjs-${buildItem.arch}-apple-darwin.zip.sha256sum`, "restore_v8_runtimes", "trap - EXIT", ], }, { // Note: Azure OIDC credentials are only valid for 5 minutes, so // authentication must be done right before signing. name: "Authenticate with Azure (windows)", if: isWindows.and(isDenoland).and(isMainOrTag), uses: "azure/login@v2", with: { "client-id": "${{ secrets.AZURE_CLIENT_ID }}", "tenant-id": "${{ secrets.AZURE_TENANT_ID }}", "subscription-id": "${{ secrets.AZURE_SUBSCRIPTION_ID }}", "enable-AzPSSession": true, }, }, { name: "Code sign deno.exe (windows)", if: isWindows.and(isDenoland).and(isMainOrTag), uses: "Azure/artifact-signing-action@v0", with: { "endpoint": "https://eus.codesigning.azure.net/", "trusted-signing-account-name": "deno-cli-code-signing", "certificate-profile-name": "deno-cli-code-signing-cert", "files-folder": "target/release", "files-folder-filter": "deno.exe", "file-digest": "SHA256", "timestamp-rfc3161": "http://timestamp.acs.microsoft.com", "timestamp-digest": "SHA256", "exclude-environment-credential": true, "exclude-workload-identity-credential": true, "exclude-managed-identity-credential": true, "exclude-shared-token-cache-credential": true, "exclude-visual-studio-credential": true, "exclude-visual-studio-code-credential": true, "exclude-azure-cli-credential": false, }, }, { name: "Verify signature (windows)", if: isWindows.and(isDenoland).and(isMainOrTag), shell: "pwsh", run: [ '$SignTool = Get-ChildItem -Path "C:\\Program Files*\\Windows Kits\\*\\bin\\*\\x64\\signtool.exe" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1', "$SignToolPath = $SignTool.FullName", "& $SignToolPath verify /pa /v target\\release\\deno.exe", ], }, { name: "Pre-release (windows)", if: isWindows.and(isDenoland), shell: "pwsh", run: [ `Get-FileHash target/release/deno.exe -Algorithm SHA256 | Format-List > target/release/deno-${buildItem.arch}-pc-windows-msvc.sha256sum`, `Compress-Archive -CompressionLevel Optimal -Force -Path target/release/deno.exe -DestinationPath target/release/deno-${buildItem.arch}-pc-windows-msvc.zip`, `Get-FileHash target/release/deno-${buildItem.arch}-pc-windows-msvc.zip -Algorithm SHA256 | Format-List > target/release/deno-${buildItem.arch}-pc-windows-msvc.zip.sha256sum`, `Compress-Archive -CompressionLevel Optimal -Force -Path target/release/denort.exe -DestinationPath target/release/denort-${buildItem.arch}-pc-windows-msvc.zip`, `Get-FileHash target/release/denort-${buildItem.arch}-pc-windows-msvc.zip -Algorithm SHA256 | Format-List > target/release/denort-${buildItem.arch}-pc-windows-msvc.zip.sha256sum`, `Compress-Archive -CompressionLevel Optimal -Force -Path target/release/denort.dll -DestinationPath target/release/libdenort-${buildItem.arch}-pc-windows-msvc.zip`, `Get-FileHash target/release/libdenort-${buildItem.arch}-pc-windows-msvc.zip -Algorithm SHA256 | Format-List > target/release/libdenort-${buildItem.arch}-pc-windows-msvc.zip.sha256sum`, // QuickJS denort/libdenort for `deno compile --engine quickjs` // (see the linux note). "try {", " Move-Item target/release/denort.exe target/release/denort.v8.exe", " Move-Item target/release/denort.dll target/release/denort.v8.dll", // Both packages produce denort.pdb on Windows. Building them in // one Cargo invocation lets their linkers race to write it. ` cargo build --release --locked -p denort --no-default-features --features quickjs`, ' if ($LASTEXITCODE -ne 0) { throw "QuickJS denort build failed" }', ` cargo build --release --locked -p denort_desktop --no-default-features --features quickjs`, ' if ($LASTEXITCODE -ne 0) { throw "QuickJS denort_desktop build failed" }', ` Compress-Archive -CompressionLevel Optimal -Force -Path target/release/denort.exe -DestinationPath target/release/denort-quickjs-${buildItem.arch}-pc-windows-msvc.zip`, ` Get-FileHash target/release/denort-quickjs-${buildItem.arch}-pc-windows-msvc.zip -Algorithm SHA256 | Format-List > target/release/denort-quickjs-${buildItem.arch}-pc-windows-msvc.zip.sha256sum`, ` Compress-Archive -CompressionLevel Optimal -Force -Path target/release/denort.dll -DestinationPath target/release/libdenort-quickjs-${buildItem.arch}-pc-windows-msvc.zip`, ` Get-FileHash target/release/libdenort-quickjs-${buildItem.arch}-pc-windows-msvc.zip -Algorithm SHA256 | Format-List > target/release/libdenort-quickjs-${buildItem.arch}-pc-windows-msvc.zip.sha256sum`, "} finally {", " if (Test-Path target/release/denort.v8.exe) { Move-Item -Force target/release/denort.v8.exe target/release/denort.exe }", " if (Test-Path target/release/denort.v8.dll) { Move-Item -Force target/release/denort.v8.dll target/release/denort.dll }", "}", `target/release/deno.exe -A tools/release/create_symcache.ts target/release/deno-${buildItem.arch}-pc-windows-msvc.symcache`, ], }, { name: "Build bsdiff helper", if: isDenoland.and(isTag), run: [ "cargo build --release -p bsdiff_helper", ], }, { name: "Generate delta patch (linux)", if: isLinux.and(isDenoland).and(isTag), run: [ `TARGET="${buildItem.arch}-unknown-linux-gnu"`, 'PREV_VERSION=$(curl -sf https://dl.deno.land/release-latest.txt | tr -d "v\\n") || true', 'if [ -z "$PREV_VERSION" ]; then echo "No previous version found, skipping delta"; exit 0; fi', 'echo "Generating delta from $PREV_VERSION for $TARGET"', 'curl -fSL -o prev.zip "https://github.com/denoland/deno/releases/download/v${PREV_VERSION}/deno-${TARGET}.zip" || { echo "Previous release not found, skipping delta"; exit 0; }', "unzip -o prev.zip -d prev/", './target/release/bsdiff_helper prev/deno target/release/deno "target/release/deno-${TARGET}.from-${PREV_VERSION}.bsdiff"', 'cd target/release && shasum -a 256 "deno-${TARGET}.from-${PREV_VERSION}.bsdiff" > "deno-${TARGET}.from-${PREV_VERSION}.bsdiff.sha256sum"', ], }, { name: "Generate delta patch (mac)", if: isMacos.and(isDenoland).and(isTag), run: [ `TARGET="${buildItem.arch}-apple-darwin"`, 'PREV_VERSION=$(curl -sf https://dl.deno.land/release-latest.txt | tr -d "v\\n") || true', 'if [ -z "$PREV_VERSION" ]; then echo "No previous version found, skipping delta"; exit 0; fi', 'echo "Generating delta from $PREV_VERSION for $TARGET"', 'curl -fSL -o prev.zip "https://github.com/denoland/deno/releases/download/v${PREV_VERSION}/deno-${TARGET}.zip" || { echo "Previous release not found, skipping delta"; exit 0; }', "unzip -o prev.zip -d prev/", './target/release/bsdiff_helper prev/deno target/release/deno "target/release/deno-${TARGET}.from-${PREV_VERSION}.bsdiff"', 'cd target/release && shasum -a 256 "deno-${TARGET}.from-${PREV_VERSION}.bsdiff" > "deno-${TARGET}.from-${PREV_VERSION}.bsdiff.sha256sum"', ], }, { name: "Generate delta patch (windows)", if: isWindows.and(isDenoland).and(isTag), shell: "pwsh", run: [ `$Target = "${buildItem.arch}-pc-windows-msvc"`, '$PrevVersion = (Invoke-RestMethod https://dl.deno.land/release-latest.txt).Trim() -replace "^v", ""', 'if (-not $PrevVersion) { Write-Host "No previous version found, skipping delta"; exit 0 }', 'Write-Host "Generating delta from $PrevVersion for $Target"', 'try { Invoke-WebRequest -Uri "https://github.com/denoland/deno/releases/download/v$PrevVersion/deno-$Target.zip" -OutFile prev.zip } catch { Write-Host "Previous release not found, skipping delta"; exit 0 }', "Remove-Item -Recurse -Force prev -ErrorAction SilentlyContinue; New-Item -ItemType Directory -Path prev | Out-Null", "Expand-Archive -Force -Path prev.zip -DestinationPath prev", '& .\\target\\release\\bsdiff_helper.exe "prev\\deno.exe" "target\\release\\deno.exe" "target\\release\\deno-$Target.from-$PrevVersion.bsdiff"', 'Get-FileHash "target\\release\\deno-$Target.from-$PrevVersion.bsdiff" -Algorithm SHA256 | Format-List > "target\\release\\deno-$Target.from-$PrevVersion.bsdiff.sha256sum"', ], }, step({ name: "Upload canary to dl.deno.land", if: isDenoland.and(isMainBranch), env: S3Envs, run: [ 'aws s3 sync ./target/release/ s3://dl-deno-land/canary/$(git rev-parse HEAD)/ --exclude "*" --include "*.zip"', 'aws s3 sync ./target/release/ s3://dl-deno-land/canary/$(git rev-parse HEAD)/ --exclude "*" --include "*.sha256sum"', 'aws s3 sync ./target/release/ s3://dl-deno-land/canary/$(git rev-parse HEAD)/ --exclude "*" --include "*.symcache"', "echo ${{ github.sha }} > canary-latest.txt", 'aws s3 cp canary-latest.txt s3://dl-deno-land/canary-$(rustc -vV | sed -n "s|host: ||p")-latest.txt', "rm canary-latest.txt", ], }), ); const packagesToBuild = ["deno", "denort", "test_server"] .map((name) => `-p ${name}`).join(" "); const binsToBuild = ["deno", "denort", "test_server"] .map((name) => `--bin ${name}`).join(" "); const cargoBuildReleaseCommand = `cargo build${buildStdArgs} --release --locked ${packagesToBuild} ${binsToBuild} --features=${panicTraceFeatures}`; const cargoBuildReleaseStep = step .if( isRelease.and(isDenoland.or(buildItem.use_sysroot)), ) .dependsOn( installLldStep, installDenoStep, restoreCacheStep, installRustStep, sysRootStep, )( { // do this on PRs as well as main so that PRs can use the cargo build cache from main name: "Configure canary build", if: isNotTag, run: 'echo "DENO_CANARY=true" >> $GITHUB_ENV', }, ...(usesFramePointerPanicTrace ? [{ name: "Configure frame-pointer panic traces", run: [ // `build-std` is unstable, but keeping Deno's pinned stable // compiler plus this narrow opt-in is preferable to moving // release builds to a separate nightly toolchain. 'echo "RUSTC_BOOTSTRAP=1" >> "$GITHUB_ENV"', // `RUSTFLAGS` is a multi-line value, but cargo splits it on // spaces only (trimming each piece), so a newline is not a // separator. Append on the same line as the tail of the // existing value, otherwise the added flags fuse into one // bogus token. "{", ' echo "RUSTFLAGS<<__DENO_RUSTFLAGS"', ' echo "$RUSTFLAGS -C force-frame-pointers=yes -C force-unwind-tables=no -C link-arg=-Wl,--no-eh-frame-hdr"', ' echo "__DENO_RUSTFLAGS"', '} >> "$GITHUB_ENV"', ], }] : []), { name: "Build release", env: { DENO_SNAPSHOT_MINIFY_SOURCES: "1", }, run: [ // On macOS aarch64, link through lzld so system frameworks // (CoreFoundation/Foundation/Security/CoreServices/Metal/...) are // dlopen'd on first use instead of loaded at launch, cutting dyld // startup cost. lzld needs an absolute -fuse-ld path (Apple clang // rejects relative ones), so patch it in here rather than in the // committed .cargo/config.toml. See tools/lzld. 'if [ "$(uname -s)" = Darwin ] && [ "$(uname -m)" = arm64 ]; then', " git submodule update --init tools/lzld", " make -C tools/lzld", " sed -i '' \"s#-fuse-ld=lld #-fuse-ld=$GITHUB_WORKSPACE/tools/lzld/lzld -L$GITHUB_WORKSPACE/tools/lzld -llzld_arm64 #\" .cargo/config.toml", " grep -n fuse-ld .cargo/config.toml", "fi", // output fs space before and after building "df -h", cargoBuildReleaseCommand, // Build the desktop runtime shared library (libdenort cdylib) for // laufey-based desktop apps. It is a separate invocation, but // still needs the frame-pointer standard library before its // unwind sections can be removed during packaging. `cargo build${buildStdArgs} --release --locked -p denort_desktop`, "df -h", ], }, ...(usesStartupOrder ? [{ name: "Trace startup order", if: runStartupOrder, run: rawBuildItem.os === "macos" ? [ "cp -p target/release/deno target/release/deno-before-startup-order", "target/release/deno run -A tools/startup_order/generate_macos_function_orderfile.ts \\", " --binary $GITHUB_WORKSPACE/target/release/deno-before-startup-order \\", ` --output $GITHUB_WORKSPACE/${startupOrderPath} \\`, " --repeats 3 \\", " --workload-profile run-first", ] : [ "cp -p target/release/deno target/release/deno-before-startup-order", "target/release/deno run -A tools/startup_order/generate_linux_function_orderfile.ts \\", " --binary $GITHUB_WORKSPACE/target/release/deno-before-startup-order \\", ` --output $GITHUB_WORKSPACE/${startupOrderPath} \\`, " --repeats 3 \\", " --workload-profile run-first", ], env: { NO_COLOR: 1 }, }, { name: "Relink release deno with startup order", if: runStartupOrder, run: cargoBuildReleaseCommand, env: { DENO_SNAPSHOT_MINIFY_SOURCES: "1", DENO_USE_STARTUP_ORDER: "1", DENO_STARTUP_ORDER_FILE: `\${{ github.workspace }}/${startupOrderPath}`, }, }, { name: "Verify startup order", if: runStartupOrder, run: [ "target/release/deno run -A tools/startup_order/verify_orderfile.ts \\", " --baseline-binary target/release/deno-before-startup-order \\", " --binary target/release/deno \\", ` --order ${startupOrderPath} \\`, ` --output ${startupOrderPath}.verify.json`, ], env: { NO_COLOR: 1 }, }, { name: "Upload startup order", uses: "actions/upload-artifact@v6", if: runStartupOrder.and(conditions.status.always()), with: { name: `startup-order-${profileName}`, path: [ startupOrderPath, `${startupOrderPath}.json`, ...(rawBuildItem.os === "linux" ? [`${startupOrderPath}.starts.json`] : []), `${startupOrderPath}.verify.json`, ].join("\n"), "retention-days": 7, "if-no-files-found": "warn", }, }] : []), { name: "Check release snapshot flags", if: isLinux, run: [ "if strings target/release/deno | grep -F -- '--no-lazy --no-lazy-eval --no-lazy-streaming'; then", ' echo "release deno binary contains eager snapshot flags"', " exit 1", "fi", ], }, { // Eager bootstrap modules must lazy-load node:/heavy closures via // core.createLazyLoader, never a static `import`. A static import // pulls the module's whole transitive closure into the startup // snapshot (e.g. `import "node:buffer"` dragged ~22 node internal // modules / ~700 SFIs in). Keep them out. name: "Check eager bootstrap does not static-import node:", if: isLinux, run: [ "if grep -rEn '^import .* from \"node:' runtime/js/; then", ' echo "eager bootstrap statically imports a node: module — use core.createLazyLoader so it stays out of the startup snapshot"', " exit 1", "fi", ], }, { name: "Generate symcache", run: [ "target/release/deno -A tools/release/create_symcache.ts ./deno.symcache", "du -h deno.symcache", "du -h target/release/deno", ], env: { NO_COLOR: 1 }, }, preRelease, { name: "Build product size info", if: isMainOrTag, run: [ `du -hd1 "./target/${buildItem.profile}"`, `du -ha "./target/${buildItem.profile}/deno"`, `du -ha "./target/${buildItem.profile}/denort"`, ], }, ); const cargoBuildStep = step .dependsOn( installLldStep, restoreCacheStep, installRustStep, sysRootStep, ) .comesAfter(tarSourcePublishStep)( { name: "Build debug", if: isDebug, run: `cargo build --locked ${packagesToBuild} ${binsToBuild} --features=deno/panic-trace`, env: { CARGO_PROFILE_DEV_DEBUG: 0 }, }, { // The rest of CI only exercises the default v8 backend. Make sure the // experimental QuickJS backend (the deno_v8 facade over the v8x crate) // keeps compiling for the deno + denort binaries so `deno compile` // and the desktop runtime don't silently regress (notably the // mutually-exclusive v8/quickjs feature guard). A `check` is enough to // catch feature/build-script breakage and does not clobber the v8 // binary this job produces. name: "Check QuickJS backend (deno + denort)", if: isDebug.and( isLinux.and(buildItem.arch.equals("x86_64")).or( isMacos.and(buildItem.arch.equals("aarch64")), ), ), run: `cargo check --locked -p deno -p denort --no-default-features --features quickjs`, env: { CARGO_PROFILE_DEV_DEBUG: 0 }, }, cargoBuildReleaseStep, { // Run a minimal check to ensure that binary is not corrupted, regardless // of our build mode name: "Check deno binary", run: `target/${buildItem.profile}/deno eval "console.log(1+2)" | grep 3`, env: { NO_COLOR: 1 }, }, { // Verify that the binary actually works in the Ubuntu-16.04 sysroot. name: "Check deno binary (in sysroot)", if: buildItem.use_sysroot, run: `sudo chroot /sysroot "$(pwd)/target/${buildItem.profile}/deno" --version`, }, denoArtifact.upload(), denortArtifact.upload(), testServerArtifact.upload(), ); const shouldPublishCondition = isRelease.and(isDenoland) .and(isTag); const publishStep = step.if(shouldPublishCondition)( step({ name: "Upload release to dl.deno.land (unix)", if: isWindows.not(), env: S3Envs, run: [ 'aws s3 sync ./target/release/ s3://dl-deno-land/release/${GITHUB_REF#refs/*/}/ --exclude "*" --include "*.zip"', 'aws s3 sync ./target/release/ s3://dl-deno-land/release/${GITHUB_REF#refs/*/}/ --exclude "*" --include "*.sha256sum"', 'aws s3 sync ./target/release/ s3://dl-deno-land/release/${GITHUB_REF#refs/*/}/ --exclude "*" --include "*.symcache"', 'aws s3 sync ./target/release/ s3://dl-deno-land/release/${GITHUB_REF#refs/*/}/ --exclude "*" --include "*.bsdiff"', ], }, { name: "Upload release to dl.deno.land (windows)", if: isWindows, env: { ...S3Envs, CLOUDSDK_PYTHON: "${{env.pythonLocation}}\\python.exe", }, run: [ 'aws s3 sync ./target/release/ s3://dl-deno-land/release/${GITHUB_REF#refs/*/}/ --exclude "*" --include "*.zip"', 'aws s3 sync ./target/release/ s3://dl-deno-land/release/${GITHUB_REF#refs/*/}/ --exclude "*" --include "*.sha256sum"', 'aws s3 sync ./target/release/ s3://dl-deno-land/release/${GITHUB_REF#refs/*/}/ --exclude "*" --include "*.symcache"', 'aws s3 sync ./target/release/ s3://dl-deno-land/release/${GITHUB_REF#refs/*/}/ --exclude "*" --include "*.bsdiff"', ], }), { name: "Create release notes", run: [ "export PATH=$PATH:$(pwd)/target/release", "./tools/release/05_create_release_notes.ts", ], }, { name: "Upload release to GitHub", uses: "softprops/action-gh-release@v2", env: { GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}", }, with: { files: [ "target/release/deno-x86_64-pc-windows-msvc.zip", "target/release/deno-x86_64-pc-windows-msvc.zip.sha256sum", "target/release/deno-x86_64-pc-windows-msvc.sha256sum", "target/release/denort-x86_64-pc-windows-msvc.zip", "target/release/denort-x86_64-pc-windows-msvc.zip.sha256sum", "target/release/libdenort-x86_64-pc-windows-msvc.zip", "target/release/libdenort-x86_64-pc-windows-msvc.zip.sha256sum", "target/release/deno-aarch64-pc-windows-msvc.zip", "target/release/deno-aarch64-pc-windows-msvc.zip.sha256sum", "target/release/deno-aarch64-pc-windows-msvc.sha256sum", "target/release/denort-aarch64-pc-windows-msvc.zip", "target/release/denort-aarch64-pc-windows-msvc.zip.sha256sum", "target/release/libdenort-aarch64-pc-windows-msvc.zip", "target/release/libdenort-aarch64-pc-windows-msvc.zip.sha256sum", "target/release/deno-x86_64-unknown-linux-gnu.zip", "target/release/deno-x86_64-unknown-linux-gnu.zip.sha256sum", "target/release/deno-x86_64-unknown-linux-gnu.sha256sum", "target/release/denort-x86_64-unknown-linux-gnu.zip", "target/release/denort-x86_64-unknown-linux-gnu.zip.sha256sum", "target/release/libdenort-x86_64-unknown-linux-gnu.zip", "target/release/libdenort-x86_64-unknown-linux-gnu.zip.sha256sum", "target/release/deno-x86_64-apple-darwin.zip", "target/release/deno-x86_64-apple-darwin.zip.sha256sum", "target/release/deno-x86_64-apple-darwin.sha256sum", "target/release/denort-x86_64-apple-darwin.zip", "target/release/denort-x86_64-apple-darwin.zip.sha256sum", "target/release/libdenort-x86_64-apple-darwin.zip", "target/release/libdenort-x86_64-apple-darwin.zip.sha256sum", "target/release/deno-aarch64-unknown-linux-gnu.zip", "target/release/deno-aarch64-unknown-linux-gnu.zip.sha256sum", "target/release/deno-aarch64-unknown-linux-gnu.sha256sum", "target/release/denort-aarch64-unknown-linux-gnu.zip", "target/release/denort-aarch64-unknown-linux-gnu.zip.sha256sum", "target/release/libdenort-aarch64-unknown-linux-gnu.zip", "target/release/libdenort-aarch64-unknown-linux-gnu.zip.sha256sum", "target/release/deno-aarch64-apple-darwin.zip", "target/release/deno-aarch64-apple-darwin.zip.sha256sum", "target/release/deno-aarch64-apple-darwin.sha256sum", "target/release/denort-aarch64-apple-darwin.zip", "target/release/denort-aarch64-apple-darwin.zip.sha256sum", "target/release/libdenort-aarch64-apple-darwin.zip", "target/release/libdenort-aarch64-apple-darwin.zip.sha256sum", "target/release/deno_src.tar.gz", "target/release/lib.deno.d.ts", "target/release/deno-*.bsdiff", "target/release/deno-*.bsdiff.sha256sum", ].join("\n"), body_path: "target/release/release-notes.md", draft: true, // Flag pre-release tags (e.g. -alpha./-beta./-rc.) as pre-releases // so they are not marked "Latest" when the draft is published. prerelease: "${{ contains(github.ref_name, '-') }}", }, }, ); return step.if(buildItem.skip.not())( cloneRepoStep, cloneStdSubmoduleStep, // ensure this happens right after cloning tarSourcePublishStep.if(shouldPublishCondition), { name: "Remove macOS cURL --ipv4 flag", run: [ // cURL's --ipv4 flag is busted for now "curl --version", "which curl", "cat /etc/hosts", "rm ~/.curlrc || true", ], if: buildItem.os.equals("macos"), }, step({ name: "Log versions", run: [ "echo '*** Python'", "command -v python && python --version || echo 'No python found or bad executable'", "echo '*** Rust'", "command -v rustc && rustc --version || echo 'No rustc found or bad executable'", "echo '*** Cargo'", "command -v cargo && cargo --version || echo 'No cargo found or bad executable'", "echo '*** Deno'", "command -v deno && deno --version || echo 'No deno found or bad executable'", "echo '*** Node'", "command -v node && node --version || echo 'No node found or bad executable'", "echo '*** Installed packages'", "command -v dpkg && dpkg -l || echo 'No dpkg found or bad executable'", ], }).comesAfter( installDenoStep, installNodeStep, installPythonStep, installRustStep, ), cargoBuildStep, publishStep, saveCacheStep.if(buildItem.save_cache), ); })(), }, ); const additionalJobs = []; { const shardedCrates = new Map([ ["specs", 2], ["integration", 2], ["node_compat", 3], ]); const testMatrix = defineMatrix({ include: testCrates.flatMap((tc) => { const total = shardedCrates.get(tc.name) ?? 1; return Array.from({ length: total }, (_, i) => ({ test_crate: tc.name, test_package: tc.package, // make these strings so index isn't falsy when 0 shard_index: i.toString(), shard_total: total.toString(), shard_label: total > 1 ? `(${i + 1}/${total}) ` : "", })); }), }); const testCrateNameExpr = testMatrix.test_crate; const { restoreCacheStep, saveCacheStep, } = createCacheSteps({ ...buildItem, cachePrefix: `test-${testCrateNameExpr}`, }); // shard_index > 0 jobs only run on PRs (main runs unsharded) const isShardZero = testMatrix.shard_index.equals(0); const shouldRunShard = isShardZero.or(isPr); // Some test shards can finish close to the default 30m job timeout // and get cancelled during harness shutdown. const timeoutMinutes = ((rawBuildItem.profile === "debug" && ((rawBuildItem.os === "windows" && rawBuildItem.arch === "aarch64") || (rawBuildItem.os === "macos" && rawBuildItem.arch === "x86_64"))) || (rawBuildItem.os === "linux" && rawBuildItem.arch === "x86_64")) ? 60 : 30; additionalJobs.push(job( jobIdForJob("test"), { name: `test ${testMatrix.test_crate} ${testMatrix.shard_label}${buildItem.profile} ${buildItem.os}-${buildItem.arch}`, needs: [buildJob], runsOn: buildItem.testRunner ?? buildItem.runner, timeoutMinutes, defaults, env, strategy: { matrix: testMatrix, failFast: false, }, steps: step.if(isNotTag.and(buildItem.skip.not()).and(shouldRunShard))( cloneRepoStep, cloneSubmodule("./tests/node_compat/runner/suite") .if(testCrateNameExpr.equals("node_compat")), cloneStdSubmoduleStep, restoreCacheStep, installNodeStep, installRustStep, installLldStep, sysRootStep, denoArtifact.download(), denortArtifact.download().if( testCrateNameExpr.equals("integration") .or(testCrateNameExpr.equals("specs")), ), testServerArtifact.download().if( testCrateNameExpr.equals("integration") .or(testCrateNameExpr.equals("specs")) .or(testCrateNameExpr.equals("unit")) .or(testCrateNameExpr.equals("unit_node")), ), { name: "Set up playwright cache", uses: "actions/cache@v5", with: { path: "./.ms-playwright", key: "playwright-${{ runner.os }}-${{ runner.arch }}", }, }, { name: "Set up native tsc cache", if: testCrateNameExpr.equals("integration").or( testCrateNameExpr.equals("specs"), ), uses: "actions/cache@v5", with: { // Keyed on native.rs so a pinned-version bump re-downloads. path: "./target/.native_tsc", key: "tsc-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('cli/tsc/native.rs') }}", }, }, { // Warm the cache with the compiler `deno check` uses (into the // default target/.native_tsc/deno_dir) so the test step doesn't // re-download it for every test's fresh DENO_DIR. The harness // resolves that path and injects DENO_TSC_BIN per-test itself (see // test_util::native_tsc_bin_path); no env export needed. Run it with // the built deno binary (the test job has no system `deno` on PATH). name: "Pre-download native tsc", if: testCrateNameExpr.equals("integration").or( testCrateNameExpr.equals("specs"), ), run: [ 'DENO_BIN=""', "for c in ./target/release/deno ./target/release/deno.exe ./target/debug/deno ./target/debug/deno.exe; do", ' [ -f "$c" ] && DENO_BIN="$c" && break', "done", '"$DENO_BIN" run -A ./tools/download_tsc.ts', ].join("\n"), }, { if: buildItem.os.equals("linux").and( buildItem.arch.equals("aarch64"), ), name: "Load 'vsock_loopback; kernel module", run: "sudo modprobe vsock_loopback", }, { name: "Build ffi (debug)", if: isDebug.and(testCrateNameExpr.equals("specs")), run: "cargo build -p test_ffi", }, { name: "Build ffi (release)", if: isRelease.and(testCrateNameExpr.equals("specs")), run: "cargo build --release -p test_ffi", }, { name: "Test (debug)", if: isDebug, run: `cargo test -p ${testMatrix.test_package} --test ${testMatrix.test_crate}`, env: { CARGO_PROFILE_DEV_DEBUG: 0, CI_SHARD_INDEX: isPr.then(testMatrix.shard_index).else(""), CI_SHARD_TOTAL: isPr.then(testMatrix.shard_total).else(""), }, }, { name: "Test (release)", if: isRelease.and( isDenoland.or(buildItem.use_sysroot), ), run: `cargo test -p ${testMatrix.test_package} --test ${testMatrix.test_crate} --release`, env: { CI_SHARD_INDEX: isPr.then(testMatrix.shard_index).else(""), CI_SHARD_TOTAL: isPr.then(testMatrix.shard_total).else(""), }, }, { name: "Ensure no git changes", if: isPr, run: [ 'if [[ -n "$(git status --porcelain)" ]]; then', 'echo "❌ Git working directory is dirty. Ensure `cargo test` is not modifying git tracked files."', 'echo ""', 'echo "📋 Status:"', "git status", 'echo ""', "exit 1", "fi", ], }, { name: "Upload test results", uses: "actions/upload-artifact@v6", if: conditions.status.always().and(isNotTag), with: { name: `test-results-${buildItem.os}-${buildItem.arch}-${buildItem.profile}-${testMatrix.test_crate}${ testMatrix.shard_total.greaterThan(1).then( literal("-shard-").concat(testMatrix.shard_index), ).else("") }.json`, path: `target/test_results_${testMatrix.test_crate}.json`, }, }, saveCacheStep.if(buildItem.save_cache), ), }, )); } const libsCondition = isDebug.and( // aarc64 runner seems faster than x86 isLinux.and(buildItem.arch.equals("aarch64")) .or(isMacos.and(buildItem.arch.equals("aarch64"))) .or(isWindows.and(buildItem.arch.equals("x86_64"))), ); if (libsCondition.isPossiblyTrue()) { const { restoreCacheStep, saveCacheStep, } = createCacheSteps({ ...buildItem, cachePrefix: "test-libs", }); additionalJobs.push(job(jobIdForJob("test-libs"), { name: jobNameForJob("test libs"), needs: [buildJob], runsOn: buildItem.testRunner ?? buildItem.runner, timeoutMinutes: 30, steps: step.if(isNotTag.and(buildItem.skip.not()))( cloneRepoStep, restoreCacheStep, installNodeStep, installRustStep, installLldStep, sysRootStep, denoArtifact.download(), testServerArtifact.download(), { name: "Test libs", run: `cargo test --locked --lib ${ [...binCrates, ...libCrates].map((p) => `-p ${p}`).join(" ") }`, env: { CARGO_PROFILE_DEV_DEBUG: 0 }, }, saveCacheStep, ), })); } if ( isDebug.and(isLinux).and(buildItem.arch.equals("x86_64")).isPossiblyTrue() ) { const { restoreCacheStep, saveCacheStep, } = createCacheSteps({ ...buildItem, cachePrefix: "build-libs", }); additionalJobs.push(job(jobIdForJob("build-libs"), { name: jobNameForJob("build libs"), needs: [preBuildJob], if: preBuildJob.outputs.skip_build.notEquals("true").and(notDocsOnly), runsOn: buildItem.runner, timeoutMinutes: 30, steps: step.if(isNotTag.and(buildItem.skip.not()))( cloneRepoStep, installRustStep, restoreCacheStep, installWasmStep, // we want these crates to be Wasm compatible { name: "Cargo check (deno_resolver)", run: "cargo check --target wasm32-unknown-unknown -p deno_resolver && cargo check --target wasm32-unknown-unknown -p deno_resolver --features graph && cargo check --target wasm32-unknown-unknown -p deno_resolver --features graph --features deno_ast", }, { name: "Cargo check (deno_npm_installer)", run: "cargo check --target wasm32-unknown-unknown -p deno_npm_installer", }, { name: "Cargo check (deno_config)", run: [ "cargo check --no-default-features -p deno_config", "cargo check --no-default-features --features workspace -p deno_config", "cargo check --no-default-features --features package_json -p deno_config", "cargo check --no-default-features --features workspace --features sync -p deno_config", "cargo check --target wasm32-unknown-unknown --all-features -p deno_config", "cargo check -p deno --features=lsp-tracing", ], }, saveCacheStep, ), })); } if (buildItem.wpt.isPossiblyTrue()) { const buildCacheSteps = createRestoreAndSaveCacheSteps({ name: "wpt and autobahn test run hashes", path: [ "./target/wpt_input_hash", "./target/autobahn_input_hash", ], cacheKeyPrefix: `${cacheVersion}-wpt-target-${buildItem.os}-${buildItem.arch}-${buildItem.profile}`, }); additionalJobs.push(job( jobIdForJob("wpt"), { name: jobNameForJob("wpt"), needs: [buildJob], runsOn: buildItem.testRunner ?? buildItem.runner, timeoutMinutes: 30, defaults, env, steps: step.if(isNotTag.and(buildItem.skip.not()))( cloneRepoStep, cloneStdSubmoduleStep, cloneSubmodule("./tests/wpt/suite"), buildCacheSteps.restoreCacheStep, installDenoStep, installPythonStep, denoArtifact.download(), { name: "Configure hosts file for WPT", run: "./wpt make-hosts-file | sudo tee -a /etc/hosts", workingDirectory: "tests/wpt/suite/", }, { name: "Run web platform tests (debug)", if: isDebug, env: { DENO_BIN: "./target/debug/deno" }, run: [ "deno run -RWNE --allow-run --lock=tools/deno.lock.json --config tests/config/deno.json \\", " ./tests/wpt/wpt.ts setup", "deno run -RWNE --allow-run --lock=tools/deno.lock.json --config tests/config/deno.json --unsafely-ignore-certificate-errors \\", ' ./tests/wpt/wpt.ts run --all --quiet --binary="$DENO_BIN"', ], }, { name: "Run web platform tests (release)", if: isRelease, env: { DENO_BIN: "./target/release/deno", }, run: [ "deno run -RWNE --allow-run --lock=tools/deno.lock.json --config tests/config/deno.json \\", " ./tests/wpt/wpt.ts setup", "deno run -RWNE --allow-run --lock=tools/deno.lock.json --config tests/config/deno.json --unsafely-ignore-certificate-errors \\", ' ./tests/wpt/wpt.ts run --all --quiet --release --binary="$DENO_BIN" --json=wpt.json --wptreport=wptreport.json', ], }, { name: "Autobahn testsuite", if: isRelease, run: "target/release/deno run -A --config tests/config/deno.json ext/websocket/autobahn/fuzzingclient.js", }, step({ name: "Upload wpt results to dl.deno.land", continueOnError: true, if: isRelease.and(isLinux).and(isDenoland).and(isMainBranch).and( isNotTag, ), env: S3Envs, run: [ "gzip ./wptreport.json", "aws s3 cp ./wpt.json s3://dl-deno-land/wpt/$(git rev-parse HEAD).json", "aws s3 cp ./wptreport.json.gz s3://dl-deno-land/wpt/$(git rev-parse HEAD)-wptreport.json.gz", "echo $(git rev-parse HEAD) > wpt-latest.txt", "aws s3 cp wpt-latest.txt s3://dl-deno-land/wpt-latest.txt", ], }), { name: "Upload wpt results to wpt.fyi", continueOnError: true, if: isRelease.and(isLinux).and(isDenoland).and(isMainBranch).and( isNotTag, ), env: { WPT_FYI_USER: "deno", WPT_FYI_PW: "${{ secrets.WPT_FYI_PW }}", GITHUB_TOKEN: "${{ secrets.DENOBOT_PAT }}", }, run: [ "./target/release/deno run --allow-all --lock=tools/deno.lock.json \\", " ./tools/upload_wptfyi.js $(git rev-parse HEAD) --ghstatus", ], }, buildCacheSteps.saveCacheStep.if(isMainBranch.and(isNotTag)), ), }, )); } return { buildJob, additionalJobs, }; }); // === bench job === const benchProfile = defineExprObj(Runners.linuxX86Xl); const benchCacheSteps = createCargoCacheHomeStep({ ...benchProfile, cachePrefix: "bench", }); const benchJob = job( "bench", { name: `bench release ${benchProfile.os}-${benchProfile.arch}`, needs: [preBuildJob], if: preBuildJob.outputs.skip_build.notEquals("true").and(notDocsOnly), runsOn: benchProfile.runner, timeoutMinutes: 240, defaults: { run: { // GH actions does not fail fast by default on // Windows, so we set bash as the default shell shell: "bash", }, }, steps: step .if( (hasCiBenchLabel.or(isMainBranch)).and(isNotTag), )( cloneRepoStep, benchCacheSteps.restoreCacheStep, installNodeStep, installRustStep, cloneSubmodule("./tests/bench/testdata/lsp_benchdata"), cloneStdSubmoduleStep, step(sysRootConfig), installDenoStep, { name: "Install benchmark tools", env: { GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" }, run: "./tools/install_prebuilt.js wrk hyperfine", }, // We currently do a full deno build instead of getting this from the build // job because the benchmarks inspect the target folder to see the sizes of // libraries like v8 and swc as well as the snapshot sizes. Maybe in the future // we could optimize this to not need this. { name: "Build deno", env: { DENO_SNAPSHOT_MINIFY_SOURCES: "1", }, run: "cargo build --release -p deno", }, { name: "Run benchmarks", run: "cargo bench -p bench_tests --bench deno_bench --locked", }, { name: "Post benchmarks", if: isDenoland.and(isMainBranch), env: { DENOBOT_PAT: "${{ secrets.DENOBOT_PAT }}", }, run: [ "git clone --depth 1 --branch gh-pages \\", " https://${DENOBOT_PAT}@github.com/denoland/benchmark_data.git \\", " gh-pages", "./target/release/deno run --allow-all ./tools/build_benchmark_jsons.js --release", "cd gh-pages", 'git config user.email "propelml@gmail.com"', 'git config user.name "denobot"', "git add .", 'git commit --message "Update benchmarks"', "git push origin gh-pages", ], }, { name: "Worker info", run: ["cat /proc/cpuinfo", "cat /proc/meminfo"], }, benchCacheSteps.saveCacheStep, ), }, ); // === lint job === const lintMatrix = defineMatrix({ include: [{ ...Runners.linuxX86, profile: "debug", job: "lint", }, { ...Runners.macosX86, profile: "debug", job: "lint", }, { ...Runners.windowsX86, profile: "debug", job: "lint", }], }); const lintJob = job("lint", { name: `lint ${lintMatrix.profile} ${lintMatrix.os}-${lintMatrix.arch}`, needs: [preBuildJob], if: preBuildJob.outputs.skip_build.notEquals("true"), runsOn: lintMatrix.runner, timeoutMinutes: 30, defaults: { run: { shell: "bash", }, }, strategy: { matrix: lintMatrix, }, steps: (() => { const { restoreCacheStep, saveCacheStep, } = createCacheSteps({ ...lintMatrix, cachePrefix: "lint", }); return step( cloneRepoStep, cloneStdSubmoduleStep, restoreCacheStep, installRustStep, installDenoStep, step.if(lintMatrix.os.equals("linux"))( { name: "test_format.js", run: "deno run --allow-write --allow-read --allow-run --allow-net ./tools/format.js --check", }, { name: "jsdoc_checker.js", run: "deno run --allow-read --allow-env --allow-sys ./tools/jsdoc_checker.js", }, ), { name: "lint.js", env: { GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" }, run: "deno run --allow-write --allow-read --allow-run --allow-net --allow-env ./tools/lint.js", }, saveCacheStep, ); })(), }); // === publish-canary job === const publishCanaryJob = job("publish-canary", { name: "publish canary", runsOn: ubuntuX86Runner, needs: [...buildJobs.map((b) => b.buildJob)], if: isDenoland.and(isMainBranch), steps: (() => { return step( { name: "Upload canary version file to dl.deno.land", env: S3Envs, run: [ "echo ${{ github.sha }} > canary-latest.txt", "aws s3 cp canary-latest.txt s3://dl-deno-land/canary-latest.txt", ], }, ); })(), }); // === deno_core test job === // Ported from denoland/deno_core .github/workflows/ci-test/action.yml // Tests the merged deno_core crates (libs/*) using cargo nextest. // Cargo package names for the libs/* workspace members (merged from deno_core). const denoCorePackageNames = [ "deno_core", "deno_v8", "build-your-own-js-snapshot", "dcore", "deno_ops", "deno_ops_compile_test_runner", "serde_v8", "deno_core_testing", ]; const denoCoreTestProfile = defineExprObj({ ...Runners.linuxX86Xl, profile: "release", }); const denoCoreTestCacheSteps = createCacheSteps({ ...denoCoreTestProfile, cachePrefix: "deno-core-test", }); const denoCoreTestJob = job("deno-core-test", { name: `deno_core test linux-x86_64`, needs: [preBuildJob], if: preBuildJob.outputs.skip_build.notEquals("true") .and(preBuildJob.outputs.skip_deno_core_test.notEquals("true")) .and(notDocsOnly), runsOn: denoCoreTestProfile.runner, timeoutMinutes: 60, defaults: { run: { shell: "bash", }, }, env: { CARGO_TERM_COLOR: "always", RUST_BACKTRACE: "full", RUST_LIB_BACKTRACE: 0, }, steps: step.if(isNotTag)( { // Frees several GB of preinstalled toolchains this job never uses // (.NET, Android SDK, GHC, Boost), fixing recurring "No space left // on device" failures while compiling deno_core's doctests. name: "Free disk space", run: [ 'sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /usr/local/share/boost "$AGENT_TOOLSDIRECTORY" || true', "df -h", ], }, cloneRepoStep, denoCoreTestCacheSteps.restoreCacheStep, installRustStep, installDenoStep, step(sysRootConfig), { name: "Install cargo-binstall", uses: "cargo-bins/cargo-binstall@main", }, { name: "Install nextest", run: "cargo binstall cargo-nextest --secure --locked", }, { name: "Cargo nextest (release)", run: [ `cargo nextest run --release`, ` --features "deno_core/default deno_core/unsafe_use_unprotected_platform deno_core/v8"`, ` --tests --examples`, ` ${denoCorePackageNames.map((p) => `-p ${p}`).join(" ")}`, ].join(" \\\n "), }, { // Ported from denoland/deno_core .github/workflows/ci-test-ops/action.yml name: "Cargo nextest ops compile test runner (release)", run: "cargo nextest run --release -p deno_ops_compile_test_runner -p deno_v8", }, { name: "Cargo doc test", run: `cargo test --doc --release ${ denoCorePackageNames.filter((p) => p !== "deno_ops_compile_test_runner" && p !== "dcore" ).map((p) => `-p ${p}`).join(" ") }`, }, { // Regression test for https://github.com/denoland/deno/pull/19615. name: "Run examples (regression tests)", run: [ "cargo run -p deno_core --example op2 --features v8", ], }, denoCoreTestCacheSteps.saveCacheStep, ), }); // === deno_core miri test job === // Ported from denoland/deno_core .github/workflows/ci-test-miri/action.yml // Runs miri tests for deno_core using a nightly Rust toolchain. const miriNightlyToolchain = "nightly-2025-11-12"; const denoCoreMiriJob = job("deno-core-miri", { name: "deno_core miri linux-x86_64", needs: [preBuildJob], if: preBuildJob.outputs.skip_build.notEquals("true").and(notDocsOnly), runsOn: Runners.linuxX86Xl.runner, timeoutMinutes: 60, defaults: { run: { shell: "bash", }, }, env: { CARGO_TERM_COLOR: "always", RUST_BACKTRACE: "full", RUST_LIB_BACKTRACE: 0, }, steps: step.if(isNotTag)( cloneRepoStep, { name: "Install Rust (nightly)", uses: "dtolnay/rust-toolchain@master", with: { toolchain: miriNightlyToolchain, }, }, { name: "Cargo test (miri)", run: [ "cargo clean", `rustup component add --toolchain ${miriNightlyToolchain} miri`, "# This somehow prints errors in CI that don't show up locally", `RUSTFLAGS=-Awarnings cargo +${miriNightlyToolchain} miri test -p deno_core --features v8`, ], }, ), }); // === ci status job (status check gate) === const ciStatusJob = job("ci-status", { name: "ci status", // We use this job in the main branch rule status checks for PRs. // All jobs that are required to pass on a PR should be listed here. needs: [ benchJob, ...buildJobs.map((j) => [j.buildJob, ...j.additionalJobs]).flat(), lintJob, denoCoreTestJob, denoCoreMiriJob, ], if: preBuildJob.outputs.skip_build.notEquals("true") .and(conditions.status.always()), runsOn: "ubuntu-latest", steps: step({ name: "Ensure CI success", run: [ "if [[ \"${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}\" == \"true\" ]]; then", " echo 'CI failed'", " exit 1", "fi", ], }), }); // === generate workflow === const workflow = createWorkflow({ name: "ci", permissions: { contents: "write", "id-token": "write", // Required for GitHub OIDC with Azure for code signing }, on: { push: { branches: ["main"], tags: ["*"], }, pull_request: { types: [ "opened", "reopened", "synchronize", // need to re-run the action when converting from draft because // draft PRs will not necessarily run all the steps "ready_for_review", ], }, }, concurrency: { group: "${{ github.workflow }}-${{ !contains(github.event.pull_request.labels.*.name, 'ci-test-flaky') && github.head_ref || github.run_id }}", cancelInProgress: true, }, jobs: [ preBuildJob, benchJob, ...buildJobs.map((j) => [j.buildJob, ...j.additionalJobs]).flat(), lintJob, denoCoreTestJob, denoCoreMiriJob, ciStatusJob, publishCanaryJob, ], }); export function generate() { return workflow.toYamlString({ header: "# GENERATED BY ./ci.ts -- DO NOT DIRECTLY EDIT", }); } export const CI_YML_URL = new URL("./ci.generated.yml", import.meta.url); if (import.meta.main) { workflow.writeOrLint({ filePath: CI_YML_URL, header: "# GENERATED BY ./ci.ts -- DO NOT DIRECTLY EDIT", }); } function resolveTestCrateTests() { const rootCargoToml = parseToml( Deno.readTextFileSync(new URL("../../Cargo.toml", import.meta.url)), ) as { workspace: { members: string[] } }; const testCrates: { name: string; package: string }[] = []; const testPackageMembers = new Set<string>(); for (const member of rootCargoToml.workspace.members) { if (!member.startsWith("tests")) continue; const cargoToml = parseToml( Deno.readTextFileSync( new URL(`../../${member}/Cargo.toml`, import.meta.url), ), ) as { package: { name: string; autotests?: boolean }; test?: { name: string; path: string }[]; }; // only include crates that explicitly disable auto-test discovery, // indicating they are intentional test packages (not helper libraries // like tests/ffi or tests/util/server) if (cargoToml.package.autotests !== false) continue; const tests = cargoToml.test ?? []; if (tests.length > 0) { testPackageMembers.add(member); for (const test of tests) { testCrates.push({ name: test.name, package: cargoToml.package.name }); } } } return { testCrates, testPackageMembers }; } function resolveWorkspaceCrates(testPackageMembers: Set<string>) { // discover workspace members for the libs test job, split by type const rootCargoToml = parseToml( Deno.readTextFileSync(new URL("../../Cargo.toml", import.meta.url)), ) as { workspace: { members: string[] } }; const libCrates: string[] = []; const binCrates: string[] = []; for (const member of rootCargoToml.workspace.members) { const cargoToml = parseToml( Deno.readTextFileSync( new URL(`../../${member}/Cargo.toml`, import.meta.url), ), ) as { package: { name: string }; bin?: unknown[]; test?: { path?: string }[]; }; if (member.startsWith("tests")) { if (!testPackageMembers.has(member)) { ensureNoIntegrationTests(member, cargoToml); } } else if (denoCorePackageDirs.includes(member)) { // libs/* crates (merged from deno_core) have their own dedicated // deno-core-test CI job, so skip them here. continue; } else if (cargoToml.bin) { ensureNoIntegrationTests(member, cargoToml); binCrates.push(cargoToml.package.name); } else { libCrates.push(cargoToml.package.name); } } return { libCrates, binCrates }; } function ensureNoIntegrationTests( member: string, cargoToml: { package: { name: string }; test?: { path?: string }[]; }, ) { const errors: string[] = []; if (existsSync(new URL(`../../${member}/tests/`, import.meta.url))) { errors.push("has a tests/ folder"); } const hasNonRunnerTests = cargoToml.test?.some( // this path is allowed because it's only used by deno and denort // to cause the deno and denort binaries to be built when running // tests, but it doesn't actually run any tests itself (t) => t.path !== "integration_tests_runner.rs", ); if (hasNonRunnerTests) { errors.push("has a [[test]] section in Cargo.toml"); } if (errors.length > 0) { throw new Error( `crate "${cargoToml.package.name}" (${member}) ${ errors.join(" and ") }. ` + `Integration tests in these crates won't run on CI because we build ` + `binaries on one runner then test on another. ` + `Move them to spec tests, the test crates in tests/, or use #[cfg(test)] lib tests instead.`, ); } } function existsSync(path: string | URL) { try { Deno.statSync(path); return true; } catch (e) { if (!(e instanceof Deno.errors.NotFound)) throw e; return false; } }