Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/socket-patch-cli/src/commands/scan/hosted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1358,6 +1358,10 @@ async fn run_redirect_selected_untracked(
// parent directory's and `$CARGO_HOME`'s): a directory source or local
// registry in any of them refuses the cargo rewrite like a project one.
let cargo_outer = || CargoCrawler::crates_io_source_for(&common.cwd);
// The other members of the go.work in effect: the workspace build
// selects the highest version any of them requires.
let go_workspace =
|| socket_patch_core::vendor::go_build_list::other_workspace_members(&common.cwd);
// The scan runs in the install's environment: a pnpm setting there
// (`pnpm_config_trust_lockfile`) overrides the project's files.
let pnpm_env = |var: &str| std::env::var(var).ok();
Expand All @@ -1375,6 +1379,7 @@ async fn run_redirect_selected_untracked(
npm_outer: &npm_outer,
yarn_classic_outer: &yarn_classic_outer,
cargo_outer: &cargo_outer,
go_workspace: &go_workspace,
pnpm_env: &pnpm_env,
blocking: true,
takeover_uuids: takeover_uuids.clone(),
Expand Down
74 changes: 73 additions & 1 deletion crates/socket-patch-cli/tests/e2e_golang_hosted_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,16 @@ fn get_hosted_with(
"fake",
];
args.extend_from_slice(extra);
// The go.work in effect is the one above the project (#1753), whatever
// GOWORK the machine running the tests sets.
let (code, stdout, stderr) = run_with_prebuilt(
consumer,
&args,
&[("GOMODCACHE", modcache.to_str().unwrap())],
&[
("GOMODCACHE", modcache.to_str().unwrap()),
("GOWORK", "auto"),
("GOENV", "off"),
],
);
assert_eq!(
code, 0,
Expand Down Expand Up @@ -267,6 +273,72 @@ async fn leftover_go_sum_lines_do_not_confirm_a_refused_rewrite() {
assert!(!ledger.contains(UPURL), "{ledger}");
}

/// #1753: in a go.work workspace the build selects the highest version any
/// `use` member requires. Once another member requires v1.1.0, a hosted
/// pin for this member's v1.0.0 is inert: the scan must refuse to write it
/// and take back the one it wrote before, never report it as redirected.
#[tokio::test(flavor = "multi_thread")]
async fn hosted_pin_another_go_work_member_outranks_is_refused() {
let tmp = tempfile::tempdir().unwrap();
let ws = tmp.path().join("ws");
let consumer = ws.join("consumer");
write_consumer(&consumer, &format!("require {UMOD} {UVER}\n"), UPSTREAM_SUM);
let other = ws.join("other");
let other_mod =
|v: &str| format!("module example.com/other\n\ngo 1.21\n\nrequire {UMOD} {v}\n");
std::fs::create_dir_all(&other).unwrap();
std::fs::write(other.join("go.mod"), other_mod(UVER)).unwrap();
std::fs::write(
ws.join("go.work"),
"go 1.21\n\nuse (\n\t./consumer\n\t./other\n)\n",
)
.unwrap();
let server = MockServer::start().await;
mount_hosted_grant(&server).await;
let modcache = tmp.path().join("modcache");
let mismatch = |env: &serde_json::Value| {
env["warnings"]
.as_array()
.unwrap()
.iter()
.any(|w| w["code"] == "redirect_golang_version_mismatch")
};

// Control: the other member requires the same version, so the pin is
// the one the workspace builds.
let env = get_hosted(&consumer, &server, &modcache);
assert_eq!(hosted_pinned(&env), 1, "envelope: {env}");
let go_mod = std::fs::read_to_string(consumer.join("go.mod")).unwrap();
assert!(
go_mod.contains(&format!("replace {UMOD} {UVER} => {}", smod())),
"{go_mod}"
);

// The other member moves to v1.1.0: re-running takes the inert pin back.
std::fs::write(other.join("go.mod"), other_mod("v1.1.0")).unwrap();
let env = get_hosted(&consumer, &server, &modcache);
assert_eq!(hosted_pinned(&env), 0, "envelope: {env}");
assert!(mismatch(&env), "{env}");
let go_mod = std::fs::read_to_string(consumer.join("go.mod")).unwrap();
assert!(
!go_mod.contains(&smod()),
"the inert pin is removed: {go_mod}"
);
let go_sum = std::fs::read_to_string(consumer.join("go.sum")).unwrap();
assert!(!go_sum.contains(&smod()), "{go_sum}");

// A fresh project in that state never gets the pin.
write_consumer(&consumer, &format!("require {UMOD} {UVER}\n"), UPSTREAM_SUM);
let _ = std::fs::remove_dir_all(consumer.join(".socket"));
let env = get_hosted(&consumer, &server, &modcache);
assert_eq!(hosted_pinned(&env), 0, "envelope: {env}");
assert!(mismatch(&env), "{env}");
assert_eq!(
std::fs::read_to_string(consumer.join("go.mod")).unwrap(),
format!("module example.com/consumer\n\ngo 1.21\n\nrequire {UMOD} {UVER}\n")
);
}

/// vendored → hosted takeover: the hosted run must revert the vendored
/// state (ledger entry, committed copy, marker) before redirecting, so the
/// project is fully hosted — never a hosted go.mod beside a vendor ledger
Expand Down
24 changes: 24 additions & 0 deletions crates/socket-patch-core/src/hosted/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use crate::patch::redirect::{
use crate::utils::pnpm_workspace::governing_workspace_file;
use crate::utils::purl::purl_parts;
use crate::utils::redact::url_host;
use crate::vendor::go_build_list::ModuleEvidence;
use crate::vendor::lock_inventory::{bun_text_lock_drives, MemoryEntry, ProjectView};

use super::guidance::{
Expand Down Expand Up @@ -1546,6 +1547,11 @@ pub struct RewriteOptions<'a> {
/// resolved only when a cargo dep is rewritten (the cargo rewriter's
/// offline-source refusal).
pub cargo_outer: &'a (dyn Fn() -> CratesIoSource + Send + Sync),
/// The other `use` members of the go.work in effect for the project,
/// resolved only when a golang dep is rewritten: the Go rewriter weighs
/// their requirements, since the workspace build selects the highest
/// version any member asks for (#1753).
pub go_workspace: &'a (dyn Fn() -> Vec<ModuleEvidence> + Send + Sync),
/// A variable of the environment pnpm installs the project in, by
/// name: a `pnpm_config_trust_lockfile` there beats the
/// pnpm-workspace.yaml key the trust auto-config writes (#1514).
Expand Down Expand Up @@ -2272,6 +2278,11 @@ async fn rewrite_once(
} else {
CratesIoSource::Registry
};
let go_members = if rewrite_overrides.iter().any(|o| o.ecosystem == "golang") {
(options.go_workspace)()
} else {
Vec::new()
};
let (files, mut rewrite) = if options.blocking {
// Pure CPU over every lock text (the independent rewriter groups
// run concurrently inside), so it runs on the blocking pool rather
Expand All @@ -2288,6 +2299,7 @@ async fn rewrite_once(
&gradle_unreadable,
&yarn_outer,
&cargo_outer,
&go_members,
);
(files, rewrite)
})
Expand All @@ -2307,6 +2319,7 @@ async fn rewrite_once(
&gradle_unreadable,
&yarn_outer,
&cargo_outer,
&go_members,
);
(files, rewrite)
};
Expand Down Expand Up @@ -3800,6 +3813,7 @@ mod tests {
npm_outer: &outer,
yarn_classic_outer: &OuterYarnMirror::default,
cargo_outer: &no_cargo_outer,
go_workspace: &Vec::new,
pnpm_env: &no_env,
blocking: false,
takeover_uuids: Default::default(),
Expand Down Expand Up @@ -3873,6 +3887,7 @@ mod tests {
npm_outer: &outer,
yarn_classic_outer: &OuterYarnMirror::default,
cargo_outer: &no_cargo_outer,
go_workspace: &Vec::new,
pnpm_env: &no_env,
blocking: false,
takeover_uuids: Default::default(),
Expand Down Expand Up @@ -4188,6 +4203,7 @@ mod tests {
npm_outer: &outer,
yarn_classic_outer: &OuterYarnMirror::default,
cargo_outer: &no_cargo_outer,
go_workspace: &Vec::new,
pnpm_env: env,
blocking: false,
takeover_uuids: Default::default(),
Expand Down Expand Up @@ -4624,6 +4640,7 @@ snapshots:
npm_outer: &outer,
yarn_classic_outer: &OuterYarnMirror::default,
cargo_outer: &no_cargo_outer,
go_workspace: &Vec::new,
pnpm_env: &no_env,
blocking: false,
takeover_uuids: Default::default(),
Expand Down Expand Up @@ -4729,6 +4746,7 @@ snapshots:
npm_outer: &outer,
yarn_classic_outer: &OuterYarnMirror::default,
cargo_outer: &no_cargo_outer,
go_workspace: &Vec::new,
pnpm_env: &no_env,
blocking: false,
takeover_uuids: Default::default(),
Expand Down Expand Up @@ -4905,6 +4923,7 @@ snapshots:
npm_outer: &outer,
yarn_classic_outer: &OuterYarnMirror::default,
cargo_outer: &no_cargo_outer,
go_workspace: &Vec::new,
pnpm_env: &no_env,
blocking: false,
takeover_uuids: Default::default(),
Expand Down Expand Up @@ -5246,6 +5265,7 @@ snapshots:
npm_outer: &outer,
yarn_classic_outer: &OuterYarnMirror::default,
cargo_outer: &no_cargo_outer,
go_workspace: &Vec::new,
pnpm_env: &no_env,
blocking: false,
takeover_uuids: Default::default(),
Expand Down Expand Up @@ -5462,6 +5482,7 @@ snapshots:
npm_outer: &outer,
yarn_classic_outer: &OuterYarnMirror::default,
cargo_outer: &no_cargo_outer,
go_workspace: &Vec::new,
pnpm_env: &no_env,
blocking: false,
takeover_uuids: Default::default(),
Expand Down Expand Up @@ -5517,6 +5538,7 @@ snapshots:
npm_outer: &outer,
yarn_classic_outer: &OuterYarnMirror::default,
cargo_outer: &no_cargo_outer,
go_workspace: &Vec::new,
pnpm_env: &no_env,
blocking: false,
takeover_uuids: Default::default(),
Expand Down Expand Up @@ -5715,6 +5737,7 @@ snapshots:
npm_outer: &outer,
yarn_classic_outer: &OuterYarnMirror::default,
cargo_outer: &no_cargo_outer,
go_workspace: &Vec::new,
pnpm_env: &no_env,
blocking: false,
takeover_uuids: Default::default(),
Expand Down Expand Up @@ -6124,6 +6147,7 @@ snapshots:
npm_outer: &outer,
yarn_classic_outer: &OuterYarnMirror::default,
cargo_outer: &no_cargo_outer,
go_workspace: &Vec::new,
pnpm_env: &no_env,
blocking: false,
takeover_uuids: Default::default(),
Expand Down
1 change: 1 addition & 0 deletions crates/socket-patch-core/src/hosted/memory/stages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ pub(crate) async fn rewrite(
yarn_classic_outer: &yarn_classic_outer,
// The in-memory engine has no project on disk to read.
cargo_outer: &|| CratesIoSource::Registry,
go_workspace: &Vec::new,
// Nor the environment the project's installs run in.
pnpm_env: &|_| None,
blocking: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn run(
overrides: &[DepOverride],
) -> RewriteResult {
let mut got = RewriteResult::default();
rewrite_golang(files, overrides, &mut got);
rewrite_golang(files, overrides, &[], &mut got);
g.next(&(files, overrides), &got);
got
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fn serial_oracle(
rewrite_nuget(files, overrides, &mut result);
rewrite_gem(files, overrides, &mut result);
rewrite_maven_pom(files, overrides, &mut result);
rewrite_golang(files, overrides, &mut result);
rewrite_golang(files, overrides, &[], &mut result);
result
}

Expand Down Expand Up @@ -139,6 +139,7 @@ fn assert_same_with_metadata(
&no_gradle_unreadable,
&no_yarn_outer,
&CratesIoSource::Registry,
&[],
);
let merged = merge_group_outputs(&prefix, run_groups_concurrently(&prefix, &groups)).map(
|mut merged| {
Expand Down
Loading
Loading