Ship FSharp.Core with a net10.0 target framework - #20224
Draft
T-Gro wants to merge 12 commits into
Draft
Conversation
…b (net10.0) + lag guard The shipped FSharp.Core net TFM is a pinned-stable knob that lags the in-dev product TFM (FSharpNetCoreProductTargetFramework=net11.0) and tracks the latest stable released .NET. A build/pack-time guard scoped to the FSharp.Core project fails if the pin is not strictly less than the product TFM. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add $(FSharpCoreShippedNetTargetFramework) (net10.0) to the non-Proto TFMs and
make FSharp.Core actually compile against the net10 BCL:
- prim-types.fs: move the #endif of the '#if !NET5_0_OR_GREATER' block so the
structural 'namespace Microsoft.FSharp.Core' + opens are unconditional (they
were trapped inside the pre-.NET5 polyfill guard and vanished on any net TFM,
causing ~100 'System type not defined' errors). ns2.x output is unchanged.
- prim-types.fsi: guard the System.Diagnostics.CodeAnalysis polyfill with the
matching '#if !NET5_0_OR_GREATER' so the signature drops it on net (the impl
already did), fixing the sig/impl mismatch (FS0193).
- prim-types.{fs,fsi}: floor the .NET8+ CollectionBuilder/ScopedRef polyfills
with '#if !NET8_0_OR_GREATER' (net BCL provides them).
- Widen collection-expression / IAsyncDisposable guards to '|| NET'
(set.{fs,fsi}, tasks.{fs,fsi}, prim-types.{fs,fsi} List builders).
- local.fs / Query.fs: disambiguate net10 BCL overloads
(String.Format ReadOnlySpan<obj>, ElementAt/Take Index/Range) by pinning the
original obj[]/int overloads. No public-surface change.
Builds clean on netstandard2.0, netstandard2.1 and net10.0 (no warnings).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- FSharp.Core.nuspec: add a net10.0 dependency group and lib/net10.0 file
entries (dll, xml, satellite resources), all driven by a
$FSharpCoreShippedNetTargetFramework$ token so the package layout tracks the
single pinned knob rather than hardcoding the TFM in the manifest.
- FSharp.Core.fsproj: surface the knob to the nuspec via
<NuspecProperty Include="FSharpCoreShippedNetTargetFramework=..."> (non-Proto).
- Microsoft.FSharp.Compiler.fsproj: include the pinned TFM in the shipping-path
pack override so the shipped FSharp.Core is packed for net10.0 too, not just
netstandard2.0/2.1.
Verified via Arcade pack: nupkg contains lib/{netstandard2.0,netstandard2.1,net10.0}
with dll+xml+satellites and a <group targetFramework="net10.0" /> dependency group.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Modern .NET hosts (net11+) now bind lib/net10.0, so the SurfaceArea test
must verify against a net baseline (references System.Runtime, not
netstandard). Reorder platform detection to '#if NET' first, add
FSharp.Core.SurfaceArea.net.{debug,release}.bsl, and document that the
netstandard2.1 baselines are intentionally retained but no longer
exercised (ns2.1 coverage moves to the ApiCompat identity gate).
The net member surface is byte-identical to ns2.1 (only assembly-
reference lines differ), confirming the net10.0 == ns2.1 invariant.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Add a dedicated FSharp.Core.ApiCompat project that runs the SDK's Microsoft.DotNet.ApiCompat.Task.ValidateAssembliesTask (registered by Sdk.targets, not Arcade, not package validation) comparing the freshly built netstandard2.1 (contract) and net10.0 implementation assemblies in strict/bidirectional mode with an empty suppression set. This enforces the net10.0 == netstandard2.1 public-surface invariant. Wire it as a required, blocking FSharpCore_ApiCompat job (no continueOnError) in azure-pipelines-PR.yml. Verified locally: passes clean on identically-built DLLs; strict mode correctly FAILS on injected additions (ns2.0-vs-net10), non-strict silently passes them (why strict is mandated). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Allow-list audit of every TFM-discriminating #if/#elif guard in
src/FSharp.Core/**/*.{fs,fsi}: each must be a reviewed <file>:<expr>
pair, so a bare '#if NETSTANDARD2_1' (excludes the net TFM) or bare
'#if !NET' (drops a BCL polyfill on every net TFM) or any un-reviewed
new TFM guard fails.
Implemented as a portable F# fsx (git ls-files + .NET regex, not
'git grep -P' which needs PCRE) and wired as a blocking step in the
required CheckCodeFormatting job, matching the repo's FSCompCheck.fsx /
TestSplit.fsx source-hygiene idiom.
Verified: self-test OK; passes on the shipped tree (10 allow-listed
guards); FAILS on an injected bare '#if NETSTANDARD2_1'.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
…rifier
Portable fsx that discovers the shipped net TFM lib folder from the produced
FSharp.Core nupkg (never a hard-coded literal) and asserts lib/{netstandard2.0,
netstandard2.1,<pin>} DLL+XML present and non-degenerate, satellites present,
a <group targetFramework> dependency group in the nuspec, and a UNIFORM
AssemblyVersion across all three lib assemblies.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Isolated net-TFM consumer (targets $(FSharpCoreShippedNetTargetFramework)) that source-maps FSharp.Core to the locally built package and STRUCTURALLY witnesses, from obj/project.assets.json, that both compile and runtime bind to lib/<pin>/FSharp.Core.dll and NOT to any netstandard asset. Driver pins the exact built version, purges only the cached fsharp.core/<ver>, and runs a widened IAsyncDisposable task member as the runtime smoke (roll-forward for the net run). Verified locally: net10.0 -> lib/net10.0 (compile+runtime) + smoke green; net8.0 control -> lib/netstandard2.1 (witness bites). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Multi-target the AheadOfTime Trimming (x3) and NativeAOT consumers on net9.0;$(FSharpCoreShippedNetTargetFramework) so the shipped net-TFM lib is exercised under PublishTrimmed/PublishAot. The gate is publish SUCCESS under TreatWarningsAsErrors=true (IL2xxx/IL3050 => errors); trimmed sizes for the net pin are report-only (-expected_len -1) since byte counts churn per preview. Both check.ps1 scripts derive the pin from the knob (no literal net10.0 to drift). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
…nto PR pipeline - e2e-1 runs in EndToEndBuildTests right after the -pack step. - e2e-2 runs in Build_And_Test_AOT_Windows after the AheadOfTime checks, with the consumer's project.assets.json published as always()/continueOnError failure evidence. e2e-5 (trim/AOT net-TFM legs) needs no new wiring: the existing AheadOfTime check.ps1 chain already runs the multi-targeted projects. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dd0ca93f-cf73-450a-8a5a-f9e59841802e
Contributor
❗ Release notes requiredYou can open this PR in browser to add release notes: open in github.dev
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ships
FSharp.Corewith an additionalnet10.0target framework alongsidenetstandard2.0andnetstandard2.1. Consumers on modern .NET now bind anet-TFM assembly directly instead of thenetstandard2.1fallback.The
netversion is a pinned knob (FSharpCoreShippedNetTargetFramework, defaultnet10.0) that deliberately lags the in-development product TFM. It does not move automatically when the product advances: F# built on net11 still shipslib/net10.0, and the pin bumps only as an explicit, reviewed step. A build-time guard fails if the pin is ever>=the product TFM.The
net10.0public surface is identical to thenetstandard2.1one — the only difference is the BCL reference set. That invariant is enforced by a blocking, strict/bidirectional assembly-level ApiCompat gate with an empty suppression set, plus a dedicatednetSurfaceArea baseline and a TFM#if-guard audit.Proto stays a single
netstandard2.0bootstrap; the extra target is added to non-Proto builds only.Not done here (cross-repo, must follow this PR): the shipped-in-SDK path flows dotnet/fsharp → dotnet/dotnet (VMR) → dotnet/sdk, and full source-build validation needs a real VMR leg (
DotNetBuildFromVMR). Those cannot be exercised from this repo.Verified locally on macOS: the 3-TFM
FSharp.Corebuild, pack producinglib/net10.0, the ApiCompat identity gate (passes with empty suppressions, fails on an injected member diff in either direction), the#ifaudit self-test, and the e2e pack +net10.0-consumer resolution witness (compile and runtime both bindlib/net10.0; anet8.0control falls back tolib/netstandard2.1). The Windows-only trim/AOT legs and the full CI matrix run in the pipeline.Draft: opening for CI signal and review of the pinned-TFM policy before finalizing.