diff --git a/crates/socket-patch-cli/tests/e2e_vendor_jvm_build.rs b/crates/socket-patch-cli/tests/e2e_vendor_jvm_build.rs index f6fed853a..23503f7a2 100644 --- a/crates/socket-patch-cli/tests/e2e_vendor_jvm_build.rs +++ b/crates/socket-patch-cli/tests/e2e_vendor_jvm_build.rs @@ -332,13 +332,12 @@ fn maven_tree_rel() -> String { format!(".socket/vendor/maven2/{GROUP_PATH}/{ARTIFACT}/{SV}") } -/// `package` + `build-classpath` into each module's `target/cp.txt`. -fn mvn_classpath(mvn: &Mvn, cwd: &Path, m2: &Path, settings: &Path, offline: bool) -> Output { +/// `package` + `build-classpath` into each module's `target/cp.txt`, after +/// `flags` (`-o`, `-U`). +fn mvn_classpath(mvn: &Mvn, cwd: &Path, m2: &Path, settings: &Path, flags: &[&str]) -> Output { let goal = format!("{CLASSPATH_PLUGIN}:build-classpath"); - let mut args = vec!["package", goal.as_str(), "-Dmdep.outputFile=target/cp.txt"]; - if offline { - args.insert(0, "-o"); - } + let mut args = flags.to_vec(); + args.extend(["package", goal.as_str(), "-Dmdep.outputFile=target/cp.txt"]); mvn.run(cwd, m2, settings, &args) } @@ -389,7 +388,10 @@ fn maven_reactor_vendor_fresh_checkout_offline_build_and_byte_exact_revert() { return; }; write_reactor(&proj); - let out = mvn_classpath(&mvn, &proj, &m2, &settings, false); + // Online: the lifecycle plugins and CLASSPATH_PLUGIN are not warmed. + let out = with_central_fallback(SUITE, "pre-vendor reactor build", &settings, |s, flags| { + mvn_classpath(&mvn, &proj, &m2, s, flags) + }); assert!(ok(&out), "pre-vendor reactor build:\n{}", dump(&out)); let entry = classpath_entry(&proj.join("b")); assert_eq!( @@ -513,7 +515,7 @@ fn maven_reactor_vendor_fresh_checkout_offline_build_and_byte_exact_revert() { let fresh = root.join("fresh"); fresh_checkout_all(&proj, &fresh); purge(&m2); - let out = mvn_classpath(&mvn, &fresh, &m2, &settings, true); + let out = mvn_classpath(&mvn, &fresh, &m2, &settings, &["-o"]); assert!(ok(&out), "fresh offline reactor build:\n{}", dump(&out)); assert_vendored_on_classpath(&fresh.join("a"), &patched, "root build, module a"); assert_vendored_on_classpath( diff --git a/crates/socket-patch-cli/tests/maven_build_common/mod.rs b/crates/socket-patch-cli/tests/maven_build_common/mod.rs index 378e07b8b..4d6e76b62 100644 --- a/crates/socket-patch-cli/tests/maven_build_common/mod.rs +++ b/crates/socket-patch-cli/tests/maven_build_common/mod.rs @@ -235,10 +235,10 @@ impl Mvn { } } -/// How many times the warm-up asks Maven Central before giving up. -const WARM_ATTEMPTS: u32 = 3; +/// How many times an online run asks Maven Central before giving up. +const CENTRAL_ATTEMPTS: u32 = 3; -/// Google's official Maven Central mirror (a separate CDN). Warm-up retries +/// Google's official Maven Central mirror (a separate CDN). Online retries /// go here: Central's own CDN rate-limits shared runner IPs with 429s, /// which Maven reports as an absent artifact, so retrying the same host /// seconds later fails the same way (CI run 37843887676). @@ -294,6 +294,44 @@ pub fn write_settings(path: &Path, mirrors: &[(&str, &str)]) { std::fs::write(path, body).unwrap(); } +/// Run an online Maven step (`run(settings, extra_args)`) that fetches +/// fixed, long-published releases from Maven Central, retrying a resolution +/// failure with `-U` via [`CENTRAL_FALLBACK`]. Such a failure is transient: +/// Central's CDN has served `maven-dependency-plugin` 3.6.1 as absent for a +/// moment (CI run 36899218369) and rate-limits runners with 429s. Maven +/// records a miss in the local repository and refuses to re-ask until the +/// update interval elapses, so a retry must force the check with `-U`. +/// +/// The retries use a settings file that holds only the fallback mirror, so +/// pass a `settings` without mirrors of its own. Every step that may fetch +/// something the warm-up did not goes through here: a runner Central is +/// throttling stays throttled for the next online step too (CI run +/// 37864818879: the warm-up recovered via the mirror, then the reactor's +/// plain pre-vendor build failed on plugins only it needs). +pub fn with_central_fallback( + suite: &str, + what: &str, + settings: &Path, + mut run: impl FnMut(&Path, &[&str]) -> Output, +) -> Output { + let mut out = run(settings, &[]); + let fallback = settings.with_file_name("central-fallback-settings.xml"); + for attempt in 2..=CENTRAL_ATTEMPTS { + if ok(&out) || !is_resolution_failure(&out) { + break; + } + println!( + "{suite}: {what} could not resolve from Maven Central; \ + retrying with -U via {CENTRAL_FALLBACK} ({attempt}/{CENTRAL_ATTEMPTS})" + ); + std::thread::sleep(std::time::Duration::from_secs(5 * u64::from(attempt - 2))); + write_fallback_settings(&fallback); + out = run(&fallback, &["-U"]); + } + let _ = std::fs::remove_file(&fallback); + out +} + pub fn ok(out: &Output) -> bool { out.status.success() } @@ -339,28 +377,11 @@ pub fn warm_fixture( ) -> Option<(Vec, Vec)> { std::fs::create_dir_all(proj).unwrap(); std::fs::write(proj.join("pom.xml"), consumer_pom(VERSION)).unwrap(); - // The warm-up is the one step that fetches from Maven Central, and it - // asks only for fixed, long-published releases, so a resolution failure - // here is transient: Central's CDN has served `maven-dependency-plugin` - // 3.6.1 as absent for a moment (CI run 36899218369) and rate-limits - // runners with 429s. Retries go through Central's Google mirror. Maven - // records a miss in the local repository and refuses to re-ask until the - // update interval elapses, so a retry must force the check with `-U`. - let mut out = mvn.copy_dependencies(proj, m2, settings, "target/warm"); - let fallback = proj.join("warm-fallback-settings.xml"); - for attempt in 2..=WARM_ATTEMPTS { - if ok(&out) || !is_resolution_failure(&out) { - break; - } - println!( - "{suite}: fixture warm-up could not resolve from Maven Central; \ - retrying with -U via {CENTRAL_FALLBACK} ({attempt}/{WARM_ATTEMPTS})" - ); - std::thread::sleep(std::time::Duration::from_secs(5 * u64::from(attempt - 2))); - write_fallback_settings(&fallback); - out = mvn.copy_dependencies_with(proj, m2, &fallback, "target/warm", &["-U"]); - } - let _ = std::fs::remove_file(&fallback); + // The warm-up asks only for fixed, long-published releases, so a + // resolution failure here is transient. + let out = with_central_fallback(suite, "fixture warm-up", settings, |s, extra| { + mvn.copy_dependencies_with(proj, m2, s, "target/warm", extra) + }); if !ok(&out) { skip( suite,