diff --git a/.github/workflows/release-packages.yml b/.github/workflows/release-packages.yml index 173a6e372d..6715424e92 100644 --- a/.github/workflows/release-packages.yml +++ b/.github/workflows/release-packages.yml @@ -428,6 +428,12 @@ jobs: # needs the prebuilt `libperry_ext_*.a` sitting next to # libperry_runtime.a so `perry compile` can link them without a # workspace checkout (see optimized_libs.rs::resolve_prebuilt_ext_libs). + # #7358: keep the compiler and both shipped wrapper archives in each + # Cargo invocation. Building an ext staticlib alone gives its bundled + # runtime/shared dependencies a different feature union from stdlib; + # the link deduper then cannot remove the whole second copy, and the + # HTTP accept loop reads a different async reactor from the one the JS + # event loop drives. # Best-effort per crate: a wrapper that can't build on this host must # not fail the whole release — the packaging glob below ships # whatever was produced. @@ -437,7 +443,8 @@ jobs: [ -d "$d" ] || continue name=$(basename "$d") echo "::group::build $name" - cargo build --profile dist --target ${{ matrix.target }} -p "$name" \ + cargo build --profile dist --target ${{ matrix.target }} \ + -p perry -p perry-runtime-static -p perry-stdlib-static -p "$name" \ || echo " (skipped $name — failed to build on this host)" echo "::endgroup::" done diff --git a/changelog.d/7952-ext-feature-union.md b/changelog.d/7952-ext-feature-union.md new file mode 100644 index 0000000000..f1fa8f4970 --- /dev/null +++ b/changelog.d/7952-ext-feature-union.md @@ -0,0 +1,8 @@ +### Fixed + +- **Native extension archives in release packages now share the shipped + runtime and stdlib feature set (#7358).** Building each `perry-ext-*` crate + alone could leave a partial second runtime in standalone binaries, attaching + HTTP work to an async reactor the JavaScript event loop did not drive. Release + packaging now selects the compiler, both static wrapper crates, and each + extension in one Cargo invocation. diff --git a/crates/perry/src/commands/compile/well_known.rs b/crates/perry/src/commands/compile/well_known.rs index 8227b62aa5..7f64697861 100644 --- a/crates/perry/src/commands/compile/well_known.rs +++ b/crates/perry/src/commands/compile/well_known.rs @@ -669,6 +669,50 @@ mod tests { ); } + /// #7358 — the manifest features above are necessary but not sufficient: + /// Cargo resolves features over the package set selected by one command. + /// A release build of an ext staticlib alone can therefore bundle a + /// runtime/shared-dependency set that differs from the separately-built + /// stdlib archive. The link deduper cannot drop that whole second copy, + /// leaving HTTP attached to an async reactor the JS event loop never + /// drives. Keep the shipping workflow's package set paired with the + /// compiler and both static wrapper crates. + #[test] + fn release_ext_builds_share_the_shipped_runtime_feature_set() { + let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let workspace_root = manifest_dir + .parent() + .and_then(|p| p.parent()) + .expect("workspace root reachable from CARGO_MANIFEST_DIR"); + let workflow = + std::fs::read_to_string(workspace_root.join(".github/workflows/release-packages.yml")) + .expect("read release-packages workflow"); + let step = workflow + .split("- name: Build native ext libraries (Unix)") + .nth(1) + .and_then(|tail| tail.split("- name: Build UI library (macOS)").next()) + .expect("native ext release step remains present"); + let command: String = step + .lines() + .skip_while(|line| !line.trim_start().starts_with("cargo build ")) + .take_while(|line| !line.contains("|| echo")) + .collect::>() + .join(" "); + let tokens: Vec<&str> = command.split_whitespace().collect(); + + for package in ["perry", "perry-runtime-static", "perry-stdlib-static"] { + assert!( + tokens.windows(2).any(|pair| pair == ["-p", package]), + "#7358: release ext build must select `{package}` in the same Cargo \ + invocation so its bundled runtime matches the shipped archives; got:\n{command}" + ); + } + assert!( + tokens.windows(2).any(|pair| pair == ["-p", "\"$name\""]), + "native ext release command stopped selecting the loop's crate: {command}" + ); + } + #[test] fn upstream_pin_parses() { let raw = r#"