From 73b7d6116d706e5b04eedb1a7c294931c911e6db Mon Sep 17 00:00:00 2001 From: Anton Dimitrov Date: Mon, 25 Mar 2024 19:40:37 +0200 Subject: [PATCH] use nuget CPM to enable strict package reference on direct dependencies only #2257 In Paket.Restore.targets based on ManagePackageVersionsCentrally property: - add condition on Package reference to include direct only packages - add condition on Version element to be used only when ManagePackageVersionsCentrally not is enabled - add package version items for all the packages in ref file In RestoreProcess.fs - the changes are similiar to those in Paket.Restore.targets --- RELEASE_NOTES.md | 3 + paket.lock | 2 +- src/Paket.Core/Installation/RestoreProcess.fs | 33 +++- src/Paket.Core/embedded/Paket.Restore.targets | 9 +- .../InstallModel/PaketPropsTests.fs | 175 +++++++++++++++++- 5 files changed, 211 insertions(+), 11 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 439a0bbd63..39ed09ac90 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,6 @@ +#### 8.1.0-alpha005 - 2024-03-25 +* Added support for central package managments to fix issue about [references: strict either does not work, or does not work as expected](https://github.com/fsprojects/Paket/issues/2257) + #### 8.1.0-alpha002 - 2024-03-14 * Preview support for .NET 9.0 - https://github.com/fsprojects/Paket/pull/4248 diff --git a/paket.lock b/paket.lock index ee596147be..0237a5abd3 100644 --- a/paket.lock +++ b/paket.lock @@ -118,7 +118,7 @@ NUGET Microsoft.NETCore.DotNetAppHost (>= 3.1.8) Microsoft.NETCore.Platforms (5.0.2) - restriction: || (&& (>= monoandroid) (>= netcoreapp2.1) (< netstandard1.3)) (&& (< monoandroid) (< monotouch) (< net46) (>= netstandard1.3) (< xamarinios) (< xamarinmac) (< xamarintvos) (< xamarinwatchos)) (&& (< monoandroid) (< net45) (< netstandard1.2) (>= netstandard1.3) (< win8)) (&& (< monoandroid) (< net45) (< netstandard1.2) (>= netstandard1.4) (< win8)) (&& (< monoandroid) (< net45) (< netstandard1.3) (>= netstandard1.4) (< win8) (< wpa81)) (&& (< monoandroid) (>= net5.0) (< netstandard2.1) (< xamarintvos) (< xamarinwatchos)) (&& (< monoandroid) (< netstandard1.1) (>= netstandard1.2) (< win8)) (&& (< monoandroid) (< netstandard1.1) (>= netstandard1.3) (< win8)) (&& (< monoandroid) (< netstandard1.1) (>= netstandard1.4) (< win8)) (&& (< monoandroid) (< netstandard1.1) (>= netstandard1.5) (< win8)) (&& (< monoandroid) (< netstandard1.1) (>= netstandard1.6) (< win8)) (&& (>= monotouch) (>= netcoreapp2.1)) (&& (>= net45) (< netstandard1.3)) (&& (< net45) (>= netstandard1.1) (< netstandard1.2) (< win8)) (&& (< net45) (>= netstandard1.2) (< netstandard1.3) (< win8) (< wpa81)) (&& (< net45) (< netstandard1.2) (>= netstandard1.5) (< win8)) (&& (< net45) (< netstandard1.2) (>= netstandard1.6) (< win8)) (&& (< net45) (>= netstandard1.3) (< netstandard1.4) (< win8) (< wpa81)) (&& (< net45) (< netstandard1.3) (>= netstandard1.5) (< win8) (< wpa81)) (&& (< net45) (< netstandard1.3) (>= netstandard1.6) (< win8) (< wpa81)) (&& (< net45) (>= netstandard1.4) (< netstandard1.5) (< win8) (< wpa81)) (&& (< net45) (< netstandard1.4) (>= netstandard1.5) (< win8) (< wpa81)) (&& (< net45) (< netstandard1.4) (>= netstandard1.6) (< win8) (< wpa81)) (&& (< net45) (>= netstandard1.5) (< netstandard1.6) (< win8) (< wpa81)) (&& (< net45) (< netstandard1.5) (>= netstandard1.6) (< win8) (< wpa81)) (&& (< net45) (>= netstandard1.6) (< netstandard2.0) (< win8) (< wpa81)) (&& (< net45) (>= netstandard2.0)) (&& (>= net46) (< netstandard1.4)) (&& (< net46) (>= netstandard2.0) (< xamarinios) (< xamarinmac) (< xamarintvos) (< xamarinwatchos)) (>= net461) (>= netcoreapp2.0) (&& (>= netcoreapp2.1) (< netcoreapp3.0)) (&& (>= netcoreapp3.0) (< xamarinios) (< xamarinmac) (< xamarintvos) (< xamarinwatchos)) (&& (>= netcoreapp3.1) (< xamarinios) (< xamarinmac) (< xamarintvos) (< xamarinwatchos)) (&& (>= netstandard1.0) (< portable-net45+win8+wpa81)) (&& (< netstandard1.0) (>= portable-net45+win8) (< win8)) (&& (< netstandard1.0) (< portable-net45+win8) (>= portable-net45+win8+wpa81)) (&& (< netstandard1.0) (>= portable-net45+win8+wp8+wpa81) (< portable-net45+win8+wpa81)) (&& (< netstandard1.0) (>= win8)) (&& (>= netstandard1.1) (< portable-net45+win8+wpa81)) (&& (< netstandard1.1) (>= uap10.0) (< win8)) (&& (< netstandard1.2) (>= uap10.0) (< win8)) (&& (>= netstandard1.3) (< portable-net45+win8+wpa81)) (&& (< netstandard1.3) (>= uap10.0) (< win8) (< wpa81)) (&& (< netstandard1.3) (< win8) (>= wpa81)) (&& (>= netstandard1.5) (< portable-net45+win8+wpa81)) (&& (< netstandard1.5) (>= uap10.0)) (>= uap10.1) (>= wp8) Microsoft.NETCore.Targets (3.1) - restriction: || (&& (< monoandroid) (< monotouch) (< net46) (>= netstandard1.3) (< xamarinios) (< xamarinmac) (< xamarintvos) (< xamarinwatchos)) (&& (< monoandroid) (< net45) (>= netstandard1.1) (< netstandard1.2) (< win8)) (&& (< monoandroid) (< net45) (>= netstandard1.2) (< netstandard1.3) (< win8) (< wpa81)) (&& (< monoandroid) (< net45) (< netstandard1.2) (>= netstandard1.3) (< win8)) (&& (< monoandroid) (< net45) (< netstandard1.2) (>= netstandard1.4) (< win8)) (&& (< monoandroid) (< net45) (< netstandard1.2) (>= netstandard2.0) (< win8)) (&& (< monoandroid) (< net45) (>= netstandard1.3) (< netstandard1.4) (< win8) (< wpa81)) (&& (< monoandroid) (< net45) (< netstandard1.3) (>= netstandard1.4) (< win8) (< wpa81)) (&& (< monoandroid) (< net45) (< netstandard1.3) (>= netstandard2.0) (< win8) (< wpa81)) (&& (< monoandroid) (< net45) (>= netstandard1.4) (< netstandard1.5) (< win8) (< wpa81)) (&& (< monoandroid) (< net45) (< netstandard1.5) (>= netstandard2.0) (< win8) (< wpa81)) (&& (< monoandroid) (< netstandard1.1) (>= netstandard1.2) (< win8)) (&& (< monoandroid) (< netstandard1.1) (>= netstandard1.3) (< win8)) (&& (< monoandroid) (< netstandard1.1) (>= netstandard1.4) (< win8)) (&& (< monoandroid) (< netstandard1.1) (>= netstandard1.5) (< win8)) (&& (< monoandroid) (< netstandard1.1) (>= netstandard1.6) (< win8)) (&& (< monotouch) (< net45) (>= netstandard1.6) (< netstandard2.0) (< win8) (< wpa81) (< xamarintvos) (< xamarinwatchos)) (&& (< net45) (>= net46) (< netstandard1.4)) (&& (< net45) (< netstandard1.2) (>= netstandard1.5) (< win8)) (&& (< net45) (< netstandard1.2) (>= netstandard1.6) (< win8)) (&& (< net45) (< netstandard1.3) (>= netstandard1.5) (< win8) (< wpa81)) (&& (< net45) (< netstandard1.3) (>= netstandard1.6) (< win8) (< wpa81)) (&& (< net45) (< netstandard1.4) (>= netstandard1.5) (< win8) (< wpa81)) (&& (< net45) (< netstandard1.4) (>= netstandard1.6) (< win8) (< wpa81)) (&& (< net45) (>= netstandard1.5) (< netstandard1.6) (< win8) (< wpa81)) (&& (< net45) (< netstandard1.5) (>= netstandard1.6) (< win8) (< wpa81)) (&& (< net45) (>= netstandard2.0) (< win8) (< wpa81) (< xamarinios) (< xamarinmac) (< xamarintvos) (< xamarinwatchos)) (&& (< net46) (>= netstandard2.0) (< xamarinios) (< xamarinmac) (< xamarintvos) (< xamarinwatchos)) (>= netcoreapp2.2) (&& (>= netstandard1.0) (< portable-net45+win8+wpa81) (< wp8)) (&& (>= netstandard1.1) (< portable-net45+win8+wpa81)) (&& (< netstandard1.1) (>= uap10.0) (< win8)) (&& (< netstandard1.2) (>= uap10.0) (< win8)) (&& (>= netstandard1.3) (< portable-net45+win8+wpa81)) (&& (< netstandard1.3) (>= uap10.0) (< win8) (< wpa81)) (&& (>= netstandard1.5) (< portable-net45+win8+wpa81)) (&& (< netstandard1.5) (>= netstandard1.6) (>= uap10.0)) (&& (< netstandard1.5) (>= uap10.0) (< uap10.1)) (&& (< netstandard1.5) (>= uap10.0) (< win8) (< wpa81)) (&& (< netstandard1.5) (>= uap10.0) (< win81) (< wpa81)) - Microsoft.NETFramework.ReferenceAssemblies.net461 (1.0.2) - copy_local: true + Microsoft.NETFramework.ReferenceAssemblies.net461 (1.0.3) - copy_local: true Microsoft.SourceLink.AzureRepos.Git (1.0) - copy_local: true Microsoft.Build.Tasks.Git (>= 1.0) Microsoft.SourceLink.Common (>= 1.0) diff --git a/src/Paket.Core/Installation/RestoreProcess.fs b/src/Paket.Core/Installation/RestoreProcess.fs index 972eb5bbf3..03a957a750 100644 --- a/src/Paket.Core/Installation/RestoreProcess.fs +++ b/src/Paket.Core/Installation/RestoreProcess.fs @@ -290,7 +290,7 @@ let createAlternativeNuGetConfig (projectFile:FileInfo, objDirectory:DirectoryIn let FSharpCore = PackageName "FSharp.Core" -let createPaketPropsFile (lockFile:LockFile) (cliTools:ResolvedPackage seq) (packages:((GroupName * PackageName) * PackageInstallSettings * _)seq) (fileInfo:FileInfo) = +let createPaketPropsFile (lockFile:LockFile) (cliTools:ResolvedPackage seq) (referencesFile:ReferencesFile) (packages:((GroupName * PackageName) * PackageInstallSettings * _)seq) (fileInfo:FileInfo) = let cliParts = if Seq.isEmpty cliTools then "" @@ -300,6 +300,13 @@ let createPaketPropsFile (lockFile:LockFile) (cliTools:ResolvedPackage seq) (pac |> fun xs -> String.Join(Environment.NewLine,xs) |> fun s -> " " + Environment.NewLine + s + Environment.NewLine + " " + + let allDirectPackages = + referencesFile.Groups.Values + |> Seq.collect (fun g -> g.NugetPackages) + |> Seq.map (fun p -> p.Name) + |> Set.ofSeq + let packagesParts = if Seq.isEmpty packages then "" @@ -328,8 +335,13 @@ let createPaketPropsFile (lockFile:LockFile) (cliTools:ResolvedPackage seq) (pac let packageReferences = packages |> Seq.collect (fun (p,_,packageSettings) -> - [yield sprintf """ """ p.Name - yield sprintf """ %O""" p.Version + let directReferenceCondition = + if not(allDirectPackages.Contains p.Name) then + "Condition=\" '$(ManagePackageVersionsCentrally)' != 'true' \"" + else "" + + [yield sprintf """ """ directReferenceCondition p.Name + yield sprintf """ %O""" p.Version let excludeAssets = [ if combineCopyLocal p.Settings packageSettings = Some false then yield "runtime" if combineOmitContent p.Settings packageSettings = Some ContentCopySettings.Omit then yield "contentFiles" @@ -338,10 +350,23 @@ let createPaketPropsFile (lockFile:LockFile) (cliTools:ResolvedPackage seq) (pac match excludeAssets with | [] -> () | tags -> yield sprintf """ %s""" (tags |> String.concat ";") + + match combineCopyLocal p.Settings packageSettings with + | Some true -> yield sprintf """ All""" + | _ -> () + yield """ """]) + let packageVersions = + packages + |> Seq.collect (fun (p,_,__) -> + [yield sprintf """ """ p.Name + yield sprintf """ %O""" p.Version + yield """ """]) + [yield sprintf " " condition yield! packageReferences + yield! packageVersions yield " "]) |> fun xs -> String.Join(Environment.NewLine,xs) @@ -524,7 +549,7 @@ let createProjectReferencesFiles (lockFile:LockFile) (projectFile:ProjectFile) ( createPaketCLIToolsFile cliTools paketCLIToolsFileName let propsFile = FileInfo(Path.Combine(objDirFullName, projectFileInfo.Name + ".paket.props")) - let written,_ = createPaketPropsFile lockFile cliTools packages propsFile + let written,_ = createPaketPropsFile lockFile cliTools referencesFile packages propsFile if written then try let fi = FileInfo(Path.Combine(objDirFullName,"project.assets.json")) diff --git a/src/Paket.Core/embedded/Paket.Restore.targets b/src/Paket.Core/embedded/Paket.Restore.targets index bbeec153f5..17aeb63502 100644 --- a/src/Paket.Core/embedded/Paket.Restore.targets +++ b/src/Paket.Core/embedded/Paket.Restore.targets @@ -235,14 +235,15 @@ $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length) $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0]) $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[2]) $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4]) $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5]) $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[6]) $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[7]) $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[8]) - - %(PaketReferencesFileLinesInfo.PackageVersion) + + %(PaketReferencesFileLinesInfo.PackageVersion) All runtime $(ExcludeAssets);contentFiles @@ -252,6 +253,10 @@ true + + + %(PaketReferencesFileLinesInfo.PackageVersion) + diff --git a/tests/Paket.Tests/InstallModel/PaketPropsTests.fs b/tests/Paket.Tests/InstallModel/PaketPropsTests.fs index 03675ba766..f65e7042e1 100644 --- a/tests/Paket.Tests/InstallModel/PaketPropsTests.fs +++ b/tests/Paket.Tests/InstallModel/PaketPropsTests.fs @@ -52,6 +52,69 @@ let checkContainsPackageRefs pkgRefs (group: XElement) = | None -> Assert.Fail(sprintf "expected package '%s' with version '%s' not found in '%A' group" pkgName pkgVersion group) +let checkContainsPackageVersions pkgRefs (group: XElement) = + + let isPackageVersion name (x: XElement) = + if x.Name = (xname "PackageVersion") then + match x.Attribute(XName.Get "Include") with + | null -> false + | v -> v.Value = name + else + false + + let hasVersion version (x: XElement) = + x.Elements(xname "Version") + |> Seq.tryHead + |> Option.map (fun x -> x.Value = version) + |> Option.exists id + + let packageVersions = group.Elements(xname "PackageVersion") |> Seq.toList + Assert.AreEqual(pkgRefs |> List.length, packageVersions |> Seq.length, (sprintf "%A" group)) + for pkgName, pkgVersion in pkgRefs do + let pkg = + packageVersions + |> List.filter (isPackageVersion pkgName) + |> List.filter (hasVersion pkgVersion) + |> List.tryHead + match pkg with + | Some p -> () + | None -> + Assert.Fail(sprintf "expected package '%s' with version '%s' not found in '%A' group" pkgName pkgVersion group) + +let checkContainsPackageRefsCondition pkgRefs (group: XElement) = + + let isPackageReference name (x: XElement) = + if x.Name = (xname "PackageReference") then + match x.Attribute(XName.Get "Include") with + | null -> false + | v -> v.Value = name + else + false + + let hasCondition condition (x: XElement) = + let conditionAttr = + x.Attributes(XName.Get "Condition") + |> Seq.tryHead + |> Option.map (fun x -> x.Value) + + match condition, conditionAttr with + | None, None -> true + | Some c, Some a -> c = a + | _ -> false + + let packageRefs = group.Elements(xname "PackageReference") |> Seq.toList + Assert.AreEqual(pkgRefs |> List.length, packageRefs |> Seq.length, (sprintf "%A" group)) + + for pkgName, pkgCondition in pkgRefs do + let pkg = + packageRefs + |> List.filter (isPackageReference pkgName) + |> List.filter (hasCondition pkgCondition) + |> List.tryHead + match pkg with + | Some p -> () + | None -> + Assert.Fail(sprintf "expected package '%s' with condition '%O' not found in '%A' group" pkgName pkgCondition group) [] let ``should create props file for design mode``() = @@ -87,7 +150,55 @@ group Other1 yield! packagesInGroup ] let outPath = System.IO.Path.GetTempFileName() - Paket.RestoreProcess.createPaketPropsFile lockFile Seq.empty packages (FileInfo outPath) + Paket.RestoreProcess.createPaketPropsFile lockFile Seq.empty refFile packages (FileInfo outPath) + + let doc = XDocument.Load(outPath, LoadOptions.PreserveWhitespace) + + let itemGroups = doc.Root.Elements (xname "ItemGroup") |> Seq.toList + + match itemGroups with + | [groupMain] -> + groupMain + |> checkTargetFrameworkNoRestriction + groupMain + |> checkContainsPackageRefs [ "FSharp.Core","3.1.2.5"; "Argu","4.2.1"; "FsCheck","2.8.2" ] + | l -> + Assert.Fail(sprintf "expected one ItemGroup but was '%A'" l) + +[] +let ``should create props file for design mode with conditions on package reference on non direct packages``() = + + let lockFile = """NUGET + remote: https://api.nuget.org/v3/index.json + Argu (4.2.1) + FSharp.Core (>= 3.1.2) + FSharp.Core (3.1.2.5) + +GROUP Other1 +NUGET + remote: https://api.nuget.org/v3/index.json + FsCheck (2.8.2) + FSharp.Core (>= 3.1.2.5) +""" + + let refFileContent = """ +Argu + +group Other1 + FsCheck +""" + + let lockFile = LockFile.Parse("", toLines lockFile) + + let refFile = ReferencesFile.FromLines(toLines refFileContent) + + let packages = + [ for kv in refFile.Groups do + let packagesInGroup,_ = lockFile.GetOrderedPackageHull(kv.Key, refFile) + yield! packagesInGroup ] + + let outPath = System.IO.Path.GetTempFileName() + Paket.RestoreProcess.createPaketPropsFile lockFile Seq.empty refFile packages (FileInfo outPath) let doc = XDocument.Load(outPath, LoadOptions.PreserveWhitespace) @@ -99,6 +210,62 @@ group Other1 |> checkTargetFrameworkNoRestriction groupMain |> checkContainsPackageRefs [ "FSharp.Core","3.1.2.5"; "Argu","4.2.1"; "FsCheck","2.8.2" ] + + groupMain + |> checkContainsPackageRefsCondition [ + "FSharp.Core", Some " '$(ManagePackageVersionsCentrally)' != 'true' "; + "Argu",None; + "FsCheck",None ] + + | l -> + Assert.Fail(sprintf "expected one ItemGroup but was '%A'" l) + + +[] +let ``should create props file for design mode with package version items``() = + + let lockFile = """NUGET + remote: https://api.nuget.org/v3/index.json + Argu (4.2.1) + FSharp.Core (>= 3.1.2) + FSharp.Core (3.1.2.5) + +GROUP Other1 +NUGET + remote: https://api.nuget.org/v3/index.json + FsCheck (2.8.2) + FSharp.Core (>= 3.1.2.5) +""" + + let refFileContent = """ +Argu + +group Other1 + FsCheck +""" + + let lockFile = LockFile.Parse("", toLines lockFile) + + let refFile = ReferencesFile.FromLines(toLines refFileContent) + + let packages = + [ for kv in refFile.Groups do + let packagesInGroup,_ = lockFile.GetOrderedPackageHull(kv.Key, refFile) + yield! packagesInGroup ] + + let outPath = System.IO.Path.GetTempFileName() + Paket.RestoreProcess.createPaketPropsFile lockFile Seq.empty refFile packages (FileInfo outPath) + + let doc = XDocument.Load(outPath, LoadOptions.PreserveWhitespace) + + let itemGroups = doc.Root.Elements (xname "ItemGroup") |> Seq.toList + + match itemGroups with + | [groupMain] -> + groupMain + |> checkTargetFrameworkNoRestriction + groupMain + |> checkContainsPackageVersions [ "FSharp.Core","3.1.2.5"; "Argu","4.2.1"; "FsCheck","2.8.2" ] | l -> Assert.Fail(sprintf "expected one ItemGroup but was '%A'" l) @@ -177,7 +344,7 @@ group Other1 yield! packagesInGroup ] let outPath = System.IO.Path.GetTempFileName() - Paket.RestoreProcess.createPaketPropsFile lockFile Seq.empty packages (FileInfo outPath) + Paket.RestoreProcess.createPaketPropsFile lockFile Seq.empty refFile packages (FileInfo outPath) let doc = XDocument.Load(outPath, LoadOptions.PreserveWhitespace) @@ -234,7 +401,7 @@ group Other2 yield! packagesInGroup ] let outPath = System.IO.Path.GetTempFileName() - Paket.RestoreProcess.createPaketPropsFile lockFile Seq.empty packages (FileInfo outPath) + Paket.RestoreProcess.createPaketPropsFile lockFile Seq.empty refFile packages (FileInfo outPath) let doc = XDocument.Load(outPath, LoadOptions.PreserveWhitespace) @@ -281,7 +448,7 @@ Newtonsoft.Json yield! packagesInGroup ] let outPath = System.IO.Path.GetTempFileName() - Paket.RestoreProcess.createPaketPropsFile lockFile Seq.empty packages (FileInfo outPath) + Paket.RestoreProcess.createPaketPropsFile lockFile Seq.empty refFile packages (FileInfo outPath) let doc = XDocument.Load(outPath, LoadOptions.PreserveWhitespace)