From fc4e29212e0428151174fdcdd58c407383d90d81 Mon Sep 17 00:00:00 2001 From: fpellet Date: Sun, 8 Nov 2026 19:27:41 +0100 Subject: [PATCH 1/3] refactor: expose the target profiles that a project file declares getDeclaredTargetProfiles returns the profiles of the project file without the net40 fallback of getTargetProfiles, so that a caller can tell a project that declares .NET Framework 4.0 from one whose framework Paket can't read, as when it comes from Directory.Build.props. --- .../PaketConfigFiles/ProjectFile.fs | 20 +++++++++++++++---- .../ProjectFile/TargetFrameworkSpecs.fs | 18 +++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/Paket.Core/PaketConfigFiles/ProjectFile.fs b/src/Paket.Core/PaketConfigFiles/ProjectFile.fs index ed2db762cd..5f5200a3e0 100644 --- a/src/Paket.Core/PaketConfigFiles/ProjectFile.fs +++ b/src/Paket.Core/PaketConfigFiles/ProjectFile.fs @@ -1099,7 +1099,10 @@ module ProjectFile = | true, version -> adjustIfWeHaveSDK version | _ -> adjustIfWeHaveSDK 4.0 - let getTargetProfiles (project:ProjectFile) = + /// The target profiles that the project file declares itself. Paket doesn't read the files that + /// it imports, so this is empty when the framework comes from one of them, as from + /// Directory.Build.props, or when the file declares no framework that Paket recognizes. + let getDeclaredTargetProfiles (project:ProjectFile) = let fallback () = let prefix() = match getTargetFrameworkIdentifier project with @@ -1114,9 +1117,9 @@ module ProjectFile = | Some x -> [prefix() + (x.Replace("v",""))] - match frameworks |> List.choose (fun s -> FrameworkDetection.internalExtract s |> Option.map TargetProfile.SinglePlatform) |> List.filter (fun x -> match x with TargetProfile.SinglePlatform(Unsupported _) -> false | _ -> true) with - | [] -> [TargetProfile.SinglePlatform (DotNetFramework FrameworkVersion.V4)] - | xs -> xs + frameworks + |> List.choose (fun s -> FrameworkDetection.internalExtract s |> Option.map TargetProfile.SinglePlatform) + |> List.filter (fun x -> match x with TargetProfile.SinglePlatform(Unsupported _) -> false | _ -> true) match getTargetFrameworkProfile project with @@ -1136,6 +1139,13 @@ module ProjectFile = fallback() | _ -> fallback() + /// The target profiles of the project: the ones that the project file declares, or net40 when + /// it declares none (see getDeclaredTargetProfiles). + let getTargetProfiles (project:ProjectFile) = + match getDeclaredTargetProfiles project with + | [] -> [TargetProfile.SinglePlatform (DotNetFramework FrameworkVersion.V4)] + | profiles -> profiles + let updateReferences (completeModel: Map) (directPackages : Map) @@ -1813,6 +1823,8 @@ type ProjectFile with member this.GetTargetProfiles() = ProjectFile.getTargetProfiles this + member this.GetDeclaredTargetProfiles() = ProjectFile.getDeclaredTargetProfiles this + member this.AddImportForPaketTargets relativeTargetsPath = ProjectFile.addImportForPaketTargets relativeTargetsPath this member this.RemoveImportForPaketTargets() = ProjectFile.removeImportForPaketTargets this diff --git a/tests/Paket.Tests/ProjectFile/TargetFrameworkSpecs.fs b/tests/Paket.Tests/ProjectFile/TargetFrameworkSpecs.fs index 3512dbd9d4..dd111b7c05 100644 --- a/tests/Paket.Tests/ProjectFile/TargetFrameworkSpecs.fs +++ b/tests/Paket.Tests/ProjectFile/TargetFrameworkSpecs.fs @@ -48,3 +48,21 @@ let ``should detect the correct framework on test projects`` projectFile expecte p.GetTargetProfiles() |> shouldEqual [expectedProfile] p.GetTargetProfiles().Head.ToString() |> shouldEqual expectedProfileString + +let private sdkProject properties = + let content = + sprintf """ + %s +""" properties + ProjectFile.LoadFromString("Project.fsproj", content) + +[] +let ``declares no target profile when the framework comes from Directory.Build.props`` () = + let p = sdkProject "" + p.GetDeclaredTargetProfiles() |> shouldEqual [] + p.GetTargetProfiles() |> shouldEqual [TargetProfile.SinglePlatform(DotNetFramework FrameworkVersion.V4)] + +[] +let ``declares the target profile of the framework in the project file`` () = + let p = sdkProject "net48" + p.GetDeclaredTargetProfiles() |> shouldEqual [TargetProfile.SinglePlatform(DotNetFramework FrameworkVersion.V4_8)] From eee404926f7c8a865ef177873111633d28314c34 Mon Sep 17 00:00:00 2001 From: fpellet Date: Sun, 8 Nov 2026 20:06:09 +0100 Subject: [PATCH 2/3] fix: don't take an empty TargetFramework for .NET Framework 1.0 Paket doesn't read Directory.Build.props, so a TargetFramework set to a property defined there, as $(DefaultTargetFramework), expands to an empty string, which FrameworkDetection reads as net10. The project file then declares no framework, and getTargetProfiles falls back to net40 as for a project without TargetFramework. The same goes for an empty entry of TargetFrameworks, as the space after the last separator of `net8.0; `, which survives the split and becomes "" once trimmed. --- src/Paket.Core/PaketConfigFiles/ProjectFile.fs | 3 +++ .../Paket.Tests/ProjectFile/TargetFrameworkSpecs.fs | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/Paket.Core/PaketConfigFiles/ProjectFile.fs b/src/Paket.Core/PaketConfigFiles/ProjectFile.fs index 5f5200a3e0..3af4a4e9f6 100644 --- a/src/Paket.Core/PaketConfigFiles/ProjectFile.fs +++ b/src/Paket.Core/PaketConfigFiles/ProjectFile.fs @@ -1118,6 +1118,9 @@ module ProjectFile = | Some x -> [prefix() + (x.Replace("v",""))] frameworks + // a property defined outside of the project file, as in $(DefaultTargetFramework), expands + // to an empty string, which internalExtract would take for .NET Framework 1.0 + |> List.filter (String.IsNullOrWhiteSpace >> not) |> List.choose (fun s -> FrameworkDetection.internalExtract s |> Option.map TargetProfile.SinglePlatform) |> List.filter (fun x -> match x with TargetProfile.SinglePlatform(Unsupported _) -> false | _ -> true) diff --git a/tests/Paket.Tests/ProjectFile/TargetFrameworkSpecs.fs b/tests/Paket.Tests/ProjectFile/TargetFrameworkSpecs.fs index dd111b7c05..eed47d0b50 100644 --- a/tests/Paket.Tests/ProjectFile/TargetFrameworkSpecs.fs +++ b/tests/Paket.Tests/ProjectFile/TargetFrameworkSpecs.fs @@ -62,6 +62,18 @@ let ``declares no target profile when the framework comes from Directory.Build.p p.GetDeclaredTargetProfiles() |> shouldEqual [] p.GetTargetProfiles() |> shouldEqual [TargetProfile.SinglePlatform(DotNetFramework FrameworkVersion.V4)] +[] +let ``declares no target profile when the framework is a property defined outside of the project file`` () = + let p = sdkProject "$(DefaultTargetFramework)" + p.GetDeclaredTargetProfiles() |> shouldEqual [] + p.GetTargetProfiles() |> shouldEqual [TargetProfile.SinglePlatform(DotNetFramework FrameworkVersion.V4)] + +[] +let ``ignores an empty entry of TargetFrameworks`` () = + // the space after the last separator survives the split, and becomes "" once trimmed + let p = sdkProject "net8.0; " + p.GetDeclaredTargetProfiles() |> shouldEqual [TargetProfile.SinglePlatform(DotNetFramework FrameworkVersion.V8)] + [] let ``declares the target profile of the framework in the project file`` () = let p = sdkProject "net48" From aed3c69986f1f07ea78be3e18ccc17b3fe3ba45b Mon Sep 17 00:00:00 2001 From: fpellet Date: Sun, 8 Nov 2026 19:36:10 +0100 Subject: [PATCH 3/3] fix: don't warn about FSharp.Core redirects when the project inherits its framework Paket doesn't read Directory.Build.props, so a project that takes its framework from there declares none in its file, and the net40 fallback of getTargetProfiles took it for a .NET Framework project. The warning now checks the profiles that the project file declares, and stays silent when there is none. Fixes #4485 --- RELEASE_NOTES.md | 1 + .../Paket.IntegrationTests/BindingRedirect.fs | 17 ++++++++++++++++- .../before/Directory.Build.props | 6 ++++++ .../before/framework/Framework.fsproj | 8 ++++++++ .../before/framework/Lib.fs | 1 + .../before/framework/paket.references | 1 + .../before/inherited/Inherited.fsproj | 5 +++++ .../before/inherited/Lib.fs | 1 + .../before/inherited/paket.references | 1 + .../before/paket.dependencies | 4 ++++ .../before/property/Lib.fs | 1 + .../before/property/Property.fsproj | 8 ++++++++ .../before/property/paket.references | 1 + src/Paket.Core/Installation/InstallProcess.fs | 4 +++- 14 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/Directory.Build.props create mode 100644 integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/framework/Framework.fsproj create mode 100644 integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/framework/Lib.fs create mode 100644 integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/framework/paket.references create mode 100644 integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/inherited/Inherited.fsproj create mode 100644 integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/inherited/Lib.fs create mode 100644 integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/inherited/paket.references create mode 100644 integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/paket.dependencies create mode 100644 integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/property/Lib.fs create mode 100644 integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/property/Property.fsproj create mode 100644 integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/property/paket.references diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 61e8a55393..9d1d787ae7 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,7 @@ * `paket pack --symbols` no longer fails on a project with Compile entries without an `Include` or `Update` attribute, such as `` or a Compile item definition in an `ItemDefinitionGroup`, and leaves out of the symbols package the files that a Remove names without wildcards - https://github.com/fsprojects/Paket/issues/4222 * `paket update --no-install` no longer fails when a group has `generate_load_scripts: true` and the packages of the new paket.lock aren't all restored, as on a fresh checkout or for a package that the update adds: it leaves the load scripts to the next restore, which downloads the packages that they load - https://github.com/fsprojects/Paket/issues/2785 +* `paket install` no longer warns about missing "redirects: force" for FSharp.Core in SDK projects that take their target framework from Directory.Build.props, by inheriting it or through a property such as `$(DefaultTargetFramework)`. Paket, which doesn't read Directory.Build.props, also stops taking such a property for net10 (.NET Framework 1.0): where it needs the framework of the project, as for `framework: auto-detect` or `paket pack`, it falls back to net40, like for a project without framework - https://github.com/fsprojects/Paket/issues/4485 #### 12.0.0-alpha005 - 2026-11-07 diff --git a/integrationtests/Paket.IntegrationTests/BindingRedirect.fs b/integrationtests/Paket.IntegrationTests/BindingRedirect.fs index 3efc76bf67..0571dd183a 100644 --- a/integrationtests/Paket.IntegrationTests/BindingRedirect.fs +++ b/integrationtests/Paket.IntegrationTests/BindingRedirect.fs @@ -222,4 +222,19 @@ let ``#1783 generates binding redirect when assembly with different version of m let config = File.ReadAllText(configPath) |> normalizeLineEndings - config |> shouldContainText ``Newtonsoft.Json`` \ No newline at end of file + config |> shouldContainText ``Newtonsoft.Json`` + +[] +let ``#4485 does not warn about FSharp.Core redirects when the framework comes from Directory.Build.props``() = + let scenario = "i004485-fsharp-core-warning-inherited-framework" + let cleanup, output = paket "install" scenario + use __ = cleanup + let warnsAbout (project: string) = + output.Split('\n') + |> Array.exists (fun line -> line.Contains "without specifying \"redirects: force\"" && line.Contains project) + + // the project that declares net48 itself is warned, so that the checks below can't pass + // because the text of the warning changed + Assert.IsTrue(warnsAbout "Framework.fsproj", output) + Assert.IsFalse(warnsAbout "Inherited.fsproj", output) + Assert.IsFalse(warnsAbout "Property.fsproj", output) diff --git a/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/Directory.Build.props b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/Directory.Build.props new file mode 100644 index 0000000000..51e1a8a574 --- /dev/null +++ b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/Directory.Build.props @@ -0,0 +1,6 @@ + + + net10.0 + $(DefaultTargetFramework) + + diff --git a/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/framework/Framework.fsproj b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/framework/Framework.fsproj new file mode 100644 index 0000000000..d01386b9ac --- /dev/null +++ b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/framework/Framework.fsproj @@ -0,0 +1,8 @@ + + + net48 + + + + + diff --git a/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/framework/Lib.fs b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/framework/Lib.fs new file mode 100644 index 0000000000..1701fa070a --- /dev/null +++ b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/framework/Lib.fs @@ -0,0 +1 @@ +module Lib diff --git a/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/framework/paket.references b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/framework/paket.references new file mode 100644 index 0000000000..6f627f42c5 --- /dev/null +++ b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/framework/paket.references @@ -0,0 +1 @@ +FSharp.Core diff --git a/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/inherited/Inherited.fsproj b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/inherited/Inherited.fsproj new file mode 100644 index 0000000000..2843d11958 --- /dev/null +++ b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/inherited/Inherited.fsproj @@ -0,0 +1,5 @@ + + + + + diff --git a/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/inherited/Lib.fs b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/inherited/Lib.fs new file mode 100644 index 0000000000..1701fa070a --- /dev/null +++ b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/inherited/Lib.fs @@ -0,0 +1 @@ +module Lib diff --git a/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/inherited/paket.references b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/inherited/paket.references new file mode 100644 index 0000000000..6f627f42c5 --- /dev/null +++ b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/inherited/paket.references @@ -0,0 +1 @@ +FSharp.Core diff --git a/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/paket.dependencies b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/paket.dependencies new file mode 100644 index 0000000000..ed074c9d87 --- /dev/null +++ b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/paket.dependencies @@ -0,0 +1,4 @@ +source https://api.nuget.org/v3/index.json +storage: none + +nuget FSharp.Core 9.0.303 diff --git a/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/property/Lib.fs b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/property/Lib.fs new file mode 100644 index 0000000000..1701fa070a --- /dev/null +++ b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/property/Lib.fs @@ -0,0 +1 @@ +module Lib diff --git a/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/property/Property.fsproj b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/property/Property.fsproj new file mode 100644 index 0000000000..000a19225b --- /dev/null +++ b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/property/Property.fsproj @@ -0,0 +1,8 @@ + + + $(DefaultTargetFramework) + + + + + diff --git a/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/property/paket.references b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/property/paket.references new file mode 100644 index 0000000000..6f627f42c5 --- /dev/null +++ b/integrationtests/scenarios/i004485-fsharp-core-warning-inherited-framework/before/property/paket.references @@ -0,0 +1 @@ +FSharp.Core diff --git a/src/Paket.Core/Installation/InstallProcess.fs b/src/Paket.Core/Installation/InstallProcess.fs index d77cce6ad4..f95275e840 100644 --- a/src/Paket.Core/Installation/InstallProcess.fs +++ b/src/Paket.Core/Installation/InstallProcess.fs @@ -343,6 +343,8 @@ let private applyBindingRedirects isFirstGroup createNewBindingFiles cleanBindin /// DotNetFramework V5+ but is not .NET Framework) - .NET Core/.NET 5+ and /// netstandard-only projects use assembly unification via deps.json instead, so the /// warning would be noise there. +/// The profiles are the ones that the project file declares: without any, as when the +/// framework comes from Directory.Build.props, Paket can't tell and doesn't warn. let shouldWarnAboutFSharpCoreRedirects (targetProfiles: TargetProfile list) (createBindingRedirects: BindingRedirectsSettings option) = let targetsDotNetFramework = targetProfiles @@ -519,7 +521,7 @@ let InstallIntoProjects(options : InstallerOptions, forceTouch, dependenciesFile traceWarnfn "F# project %s does not reference FSharp.Core." project.FileName | Some kv -> let _, settings = kv.Value - if shouldWarnAboutFSharpCoreRedirects (project.GetTargetProfiles()) settings.CreateBindingRedirects then + if shouldWarnAboutFSharpCoreRedirects (project.GetDeclaredTargetProfiles()) settings.CreateBindingRedirects then traceWarnfn "F# project %s references FSharp.Core without specifying \"redirects: force\" in paket.dependencies. This can lead to \"Could not load file or assembly 'FSharp.Core'\" errors at runtime. See https://fsprojects.github.io/Paket/dependencies-file.html#Controlling-assembly-binding-redirects for details." project.FileName // if any errors have been found during the installation process thus far, fail and print all errors collected