@@ -5760,11 +5760,13 @@ fn nuget_xml_attribute(value: &str) -> String {
57605760 .replace('\r', "
")
57615761}
57625762
5763- /// The synthetic candidate key carrying why the engine could not list the
5764- /// project's NuGet locks (an unreadable directory or project file): the
5765- /// rewriter then skips the nuget redirect rather than leave a lock it never
5766- /// saw on its upstream hash. Never a path (see [`sbt::SYNTHETIC_KEY_PREFIX`]).
5767- pub const NUGET_LOCK_WALK_KEY: &str = "<socket-patch:nuget-lock-walk>";
5763+ /// The synthetic candidate key carrying the locks the engine found every
5764+ /// project under the root restoring into (#353, #514), one per line:
5765+ /// `lock\t<rel>`, `unresolved\t<project>\t<why>` for a `NuGetLockFilePath`
5766+ /// it cannot evaluate, or `error\t<why>` when it could not list the tree.
5767+ /// Absent (the in-memory engine, unit tests), the root `packages.lock.json`
5768+ /// is the one lock. Never a path (see [`sbt::SYNTHETIC_KEY_PREFIX`]).
5769+ pub const NUGET_LOCKS_KEY: &str = "<socket-patch:nuget-locks>";
57685770
57695771fn rewrite_nuget(
57705772 files: &BTreeMap<String, String>,
@@ -5808,41 +5810,45 @@ fn rewrite_nuget(
58085810 // routes them all, so each is pinned with it (#353, #514). The engine
58095811 // reads the project files and their locks; the same pure discovery
58105812 // re-derives which keys are locks here.
5811- if let Some(why) = files.get(NUGET_LOCK_WALK_KEY) {
5812- result.warnings.push(RewriteWarning {
5813- code: "redirect_nuget_lock_unreadable".into(),
5814- detail: format!(
5815- "cannot list the project's NuGet locks ({why}); nuget redirect skipped"
5816- ),
5817- });
5818- return;
5819- }
5820- let projects: Vec<(String, String)> = files
5821- .iter()
5822- .filter(|(rel, _)| {
5823- !sbt::is_synthetic_key(rel)
5824- && crate::formats::nuget::lock::is_project_file(
5825- rel.rsplit('/').next().unwrap_or(rel),
5826- )
5827- })
5828- .map(|(rel, text)| (rel.clone(), text.clone()))
5829- .collect();
5830- let governed =
5831- crate::formats::nuget::lock::governed_locks(&projects, |rel| files.contains_key(rel));
5832- if let Some((project, detail)) = governed.unresolved.first() {
5833- result.warnings.push(RewriteWarning {
5834- code: "redirect_nuget_lock_path_unresolved".into(),
5835- detail: format!(
5836- "{project}: {detail}; the lock it restores into cannot be pinned, so nuget \
5837- redirect is skipped (set a literal NuGetLockFilePath, or remove it)"
5838- ),
5839- });
5840- return;
5841- }
5813+ let lock_rels: Vec<String> = match files.get(NUGET_LOCKS_KEY) {
5814+ None => vec![crate::formats::nuget::lock::PACKAGES_LOCK.to_string()],
5815+ Some(found) => {
5816+ let mut rels = Vec::new();
5817+ for line in found.lines() {
5818+ let mut fields = line.splitn(3, '\t');
5819+ match (fields.next(), fields.next(), fields.next()) {
5820+ (Some("lock"), Some(rel), None) => rels.push(rel.to_string()),
5821+ (Some("unresolved"), Some(project), Some(detail)) => {
5822+ result.warnings.push(RewriteWarning {
5823+ code: "redirect_nuget_lock_path_unresolved".into(),
5824+ detail: format!(
5825+ "{project}: {detail}; the lock it restores into cannot be \
5826+ pinned, so nuget redirect is skipped (set a literal \
5827+ NuGetLockFilePath, or remove it)"
5828+ ),
5829+ });
5830+ return;
5831+ }
5832+ (Some("error"), Some(why), _) => {
5833+ result.warnings.push(RewriteWarning {
5834+ code: "redirect_nuget_lock_unreadable".into(),
5835+ detail: format!(
5836+ "cannot list the project's NuGet locks ({why}); nuget redirect \
5837+ skipped"
5838+ ),
5839+ });
5840+ return;
5841+ }
5842+ _ => {}
5843+ }
5844+ }
5845+ rels
5846+ }
5847+ };
58425848 // Read past a UTF-8 BOM the way dotnet does (#623); the write below
58435849 // keeps it.
58445850 let mut locks: Vec<(String, &String, Value, bool)> = Vec::new();
5845- for rel in governed.locks {
5851+ for rel in lock_rels {
58465852 let Some(text) = files.get(&rel) else {
58475853 continue;
58485854 };
@@ -22644,21 +22650,24 @@ packages:
2264422650 }
2264522651
2264622652 /// #353 / #514: member-project locks and named locks the root config
22647- /// governs are re-pinned under their own paths with the config.
22653+ /// governs (the engine's walk, carried by [`NUGET_LOCKS_KEY`]) are
22654+ /// re-pinned under their own paths with the config.
2264822655 #[test]
2264922656 fn nuget_member_and_named_locks_are_repinned() {
2265022657 let mut files = BTreeMap::new();
2265122658 files.insert("nuget.config".to_string(), default_nuget_config());
22652- files.insert("src/App/App.csproj".to_string(), "<Project />".to_string());
2265322659 files.insert(
2265422660 "src/App/packages.lock.json".to_string(),
2265522661 simple_lock("ORIGINALHASH=="),
2265622662 );
22657- files.insert("src/Lib/Lib.csproj".to_string(), "<Project />".to_string());
2265822663 files.insert(
2265922664 "src/Lib/packages.Lib.lock.json".to_string(),
2266022665 simple_lock("ORIGINALHASH=="),
2266122666 );
22667+ files.insert(
22668+ NUGET_LOCKS_KEY.to_string(),
22669+ "lock\tsrc/App/packages.lock.json\nlock\tsrc/Lib/packages.Lib.lock.json".to_string(),
22670+ );
2266222671 let r = rewrite_registry_redirect(&files, &[nuget_override()]);
2266322672 assert!(r.warnings.is_empty(), "{:?}", r.warnings);
2266422673 for rel in [
@@ -22671,7 +22680,6 @@ packages:
2267122680 "{rel}"
2267222681 );
2267322682 }
22674- assert!(!r.files.contains_key("src/App/App.csproj"));
2267522683 let lock_paths: Vec<&str> = r
2267622684 .edits
2267722685 .iter()
@@ -22685,35 +22693,31 @@ packages:
2268522693 "src/Lib/packages.Lib.lock.json"
2268622694 ]
2268722695 );
22696+ // Without the walk (in memory, unit tests) only the root lock is one:
22697+ // a member lock is never mistaken for one.
22698+ files.remove(NUGET_LOCKS_KEY);
22699+ let r = rewrite_registry_redirect(&files, &[nuget_override()]);
22700+ assert!(!r.files.contains_key("src/App/packages.lock.json"));
2268822701 }
2268922702
2269022703 /// #514: an unresolvable `NuGetLockFilePath`, or a project tree the
2269122704 /// engine could not list, skips the nuget redirect with nothing written.
2269222705 #[test]
2269322706 fn nuget_unknowable_locks_skip_the_redirect() {
22694- let mut files = BTreeMap::new();
22695- files.insert("nuget.config".to_string(), default_nuget_config());
22696- files.insert(
22697- "app.csproj".to_string(),
22698- "<Project><PropertyGroup><NuGetLockFilePath>$(X)/l.json</NuGetLockFilePath></PropertyGroup></Project>"
22699- .to_string(),
22700- );
22701- let r = rewrite_registry_redirect(&files, &[nuget_override()]);
22702- assert!(r.files.is_empty() && r.edits.is_empty(), "{:?}", r.files);
22703- assert_eq!(
22704- warning_codes(&r),
22705- vec!["redirect_nuget_lock_path_unresolved"]
22706- );
22707-
22708- let mut files = BTreeMap::new();
22709- files.insert("nuget.config".to_string(), default_nuget_config());
22710- files.insert(
22711- NUGET_LOCK_WALK_KEY.to_string(),
22712- "unreadable src".to_string(),
22713- );
22714- let r = rewrite_registry_redirect(&files, &[nuget_override()]);
22715- assert!(r.files.is_empty() && r.edits.is_empty(), "{:?}", r.files);
22716- assert_eq!(warning_codes(&r), vec!["redirect_nuget_lock_unreadable"]);
22707+ for (walk, code) in [
22708+ (
22709+ "lock\tpackages.lock.json\nunresolved\tapp.csproj\tNuGetLockFilePath `$(X)/l.json` references MSBuild properties or items",
22710+ "redirect_nuget_lock_path_unresolved",
22711+ ),
22712+ ("error\tunreadable src", "redirect_nuget_lock_unreadable"),
22713+ ] {
22714+ let mut files = BTreeMap::new();
22715+ files.insert("nuget.config".to_string(), default_nuget_config());
22716+ files.insert(NUGET_LOCKS_KEY.to_string(), walk.to_string());
22717+ let r = rewrite_registry_redirect(&files, &[nuget_override()]);
22718+ assert!(r.files.is_empty() && r.edits.is_empty(), "{:?}", r.files);
22719+ assert_eq!(warning_codes(&r), vec![code]);
22720+ }
2271722721 }
2271822722
2271922723 /// #593: a multi-targeting lock resolving the patched id at another
0 commit comments