Skip to content

Extract logic and hardcoded values about compiler's TFM into a single source of truth#19251

Open
T-Gro wants to merge 46 commits intomainfrom
extract-tfm-logic
Open

Extract logic and hardcoded values about compiler's TFM into a single source of truth#19251
T-Gro wants to merge 46 commits intomainfrom
extract-tfm-logic

Conversation

@T-Gro
Copy link
Member

@T-Gro T-Gro commented Feb 2, 2026

Fixes #19204

Architecture: Single Source of Truth

┌─────────────────────────────────────────────────────────────────────────────┐
│                     eng/TargetFrameworks.props                               │
│              FSharpNetCoreProductDefaultTargetFramework                      │
│                   (SINGLE SOURCE OF TRUTH)                                  │
│                                                                             │
│  Default: net10.0                                                           │
│  Source-build (DotNetBuildSourceOnly=true): $(NetCurrent)                    │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
        ┌───────────────────────────┼───────────────────────────┐
        ▼                           ▼                           ▼
┌───────────────────┐    ┌─────────────────────┐    ┌─────────────────────┐
│     SCRIPTS       │    │      MSBUILD        │    │    COMPILED CODE    │
│  (--getProperty)  │    │   (Import props)    │    │    (generated)      │
└───────────────────┘    └─────────────────────┘    └─────────────────────┘
        │                           │                           │
        ▼                           ▼                           ▼
┌───────────────────┐    ┌─────────────────────┐    ┌─────────────────────┐
│ dotnet msbuild    │    │ Directory.Build.props│   │ buildproperties.fs  │
│ TargetFrameworks  │    │ imports props →     │    │ (auto-generated)    │
│ .props            │    │ sets:               │    │                     │
│ --getProperty:    │    │ • $(FSharp...TFM)   │    │ fsProductTfm =      │
│ FSharpNetCore...  │    │ • $(FSharp...Major) │    │   "net10.0"         │
│                   │    │                     │    │ fsProductTfmMajor = │
│ build.sh forwards │    │         ▼           │    │   10                │
│ /p:DotNetBuild    │    │ All .fsproj, .props │    │                     │
│ SourceOnly for    │    │ .targets reference  │    │         ▼           │
│ source-build      │    │ $(FSharp...TFM)     │    │ ┌─────────────────┐ │
│                   │    │                     │    │ │  PRODUCT CODE   │ │
│ Used by:          │    │ Used by:            │    │ │ FxResolver.fs   │ │
│ • Build.ps1       │    │ • TargetFramework   │    │ │ CompilerLoc.fs  │ │
│ • build.sh        │    │ • Output paths      │    │ │ uses FSharp.    │ │
│ • build-utils.ps1 │    │ • UseLocalCompiler  │    │ │ BuildProperties │ │
│ • test-determ.ps1 │    │ • checkpackages     │    │ └─────────────────┘ │
│ • ilverify.ps1    │    │ • EndToEnd tests    │    │                     │
│ • regression.yml  │    │                     │    │ ┌─────────────────┐ │
│ • CMD scripts     │    │                     │    │ │   TEST CODE     │ │
│                   │    │                     │    │ │ TestFramework.fs│ │
│                   │    │                     │    │ │ uses FSharp.    │ │
│                   │    │                     │    │ │ BuildProperties │ │
│                   │    │                     │    │ └─────────────────┘ │
└───────────────────┘    └─────────────────────┘    └─────────────────────┘

What changed

  • Created eng/TargetFrameworks.props as the single source of truth for the product TFM, including source-build override (DotNetBuildSourceOnly=true$(NetCurrent))
  • All MSBuild files import this props file instead of hardcoding TFM values
  • All scripts use dotnet msbuild --getProperty:FSharpNetCoreProductDefaultTargetFramework instead of hardcoded values
  • build.sh forwards /p:DotNetBuildSourceOnly to ensure correct TFM resolution in source-build/VMR scenarios
  • Directory.Build.props simplified — the <Choose> block for source-build override is now handled by TargetFrameworks.props
  • Test code uses the auto-generated FSharp.BuildProperties module instead of reading text files
  • Removed eng/productTfm.txt (superseded by props file)

T-Gro added 21 commits January 29, 2026 12:52
- Create eng/productTfm.txt as single source of truth for product TFM (net10.0)
- Update Directory.Build.props to read TFM from file and derive major version
- Update FSharpBuild.Directory.Build.targets to emit fsProductTfm and
  fsProductTfmMajorVersion constants in generated buildproperties.fs
…mework) in MSBuild files

- Update UseLocalCompiler.Directory.Build.props to use TFM property
- Add TFM property reading to buildtools/checkpackages/Directory.Build.props
- Update checkpackages fsproj files to use the property

Part of TFM centralization effort - Sprint 2
- eng/Build.ps1: Read TFM from productTfm.txt for coreclrTargetFramework,
  bootstrapTfm, and fsharpNetCoreProductTfm variables
- eng/build.sh: Read TFM using cat and tr -d to strip whitespace
- eng/build-utils.ps1: Read TFM from productTfm.txt for fsharpNetCoreProductTfm

This removes hardcoded 'net10.0' from these script files, enabling
centralized TFM management through eng/productTfm.txt.
- eng/Build.ps1: Read TFM from productTfm.txt for coreclrTargetFramework,
  bootstrapTfm, and fsharpNetCoreProductTfm variables
- eng/build.sh: Read TFM using cat and tr -d to strip whitespace
- eng/build-utils.ps1: Read TFM from productTfm.txt for fsharpNetCoreProductTfm

This removes hardcoded 'net10.0' from these script files, enabling
centralized TFM management through eng/productTfm.txt.
Replace hardcoded TFM list in toolingCompatibleVersions with dynamically
computed list based on FSharp.BuildProperties.fsProductTfmMajorVersion.

- Generate net{N}.0 from major version down to 5
- Append legacy netcoreapp/netstandard versions
- List automatically updates when eng/productTfm.txt changes
Replace hardcoded TFM list in toolingCompatibleVersions with dynamically
computed list based on FSharp.BuildProperties.fsProductTfmMajorVersion.

- Generate net{N}.0 from major version down to 5
- Append legacy netcoreapp/netstandard versions
- List automatically updates when eng/productTfm.txt changes
- TestFramework.fs: Add productTfm that reads from eng/productTfm.txt
- TestFramework.fs: Update dotnetArchitecture to use productTfm
- CompilerAssert.fs: Use TestFramework.productTfm in runtimeconfig generation
- ProjectGeneration.fs: Use TestFramework.productTfm in fsproj template
- Utilities.fs: Use TestFramework.productTfm for target framework
- TestFramework.fs: Add productTfm that reads from eng/productTfm.txt
- TestFramework.fs: Update dotnetArchitecture to use productTfm
- CompilerAssert.fs: Use TestFramework.productTfm in runtimeconfig generation
- ProjectGeneration.fs: Use TestFramework.productTfm in fsproj template
- Utilities.fs: Use TestFramework.productTfm for target framework
- Update tests/fsharp/single-test.fs to use productTfm (3 occurrences)
- Update DependencyManagerInteractiveTests.fs to use TestFramework.productTfm (18 occurrences)
- Both test projects build successfully with 0 errors
- Remaining net10.0 in tests/ are only in utility scripts, MSBuild config, and comments
- Update tests/fsharp/single-test.fs to use productTfm (3 occurrences)
- Update DependencyManagerInteractiveTests.fs to use TestFramework.productTfm (18 occurrences)
- Both test projects build successfully with 0 errors
- Remaining net10.0 in tests/ are only in utility scripts, MSBuild config, and comments
- tests/ILVerify/ilverify.ps1: Use $default_tfm instead of hardcoded net10.0
- EndToEndBuildTests fsproj files: Use $(FSharpNetCoreProductDefaultTargetFramework)
  - BasicProvider/*.fsproj
  - ComboProvider/*.fsproj
  - DesignTimeProviderPackaging/*.fsproj
- tests/ILVerify/ilverify.ps1: Use $default_tfm instead of hardcoded net10.0
- EndToEndBuildTests fsproj files: Use $(FSharpNetCoreProductDefaultTargetFramework)
  - BasicProvider/*.fsproj
  - ComboProvider/*.fsproj
  - DesignTimeProviderPackaging/*.fsproj
- TestBasicProvider.cmd: Read TFM from productTfm.txt
- TestComboProvider.cmd: Read TFM from productTfm.txt
- Equality.fsproj: Use $(FSharpNetCoreProductDefaultTargetFramework)
- FSharpMetadataResource_Trimming_Test.fsproj: Use MSBuild property
- SelfContained_Trimming_Test.fsproj: Use MSBuild property
- StaticLinkedFSharpCore_Trimming_Test.fsproj: Use MSBuild property
- FSharpCoreVersionTest.props: Read TFM from productTfm.txt
- identifierAnalysisByType.fsx: Document as dev utility (F# scripts require hardcoded path)

All functional test code now uses centralized TFM source.
- TestBasicProvider.cmd: Read TFM from productTfm.txt
- TestComboProvider.cmd: Read TFM from productTfm.txt
- Equality.fsproj: Use $(FSharpNetCoreProductDefaultTargetFramework)
- FSharpMetadataResource_Trimming_Test.fsproj: Use MSBuild property
- SelfContained_Trimming_Test.fsproj: Use MSBuild property
- StaticLinkedFSharpCore_Trimming_Test.fsproj: Use MSBuild property
- FSharpCoreVersionTest.props: Read TFM from productTfm.txt
- identifierAnalysisByType.fsx: Document as dev utility (F# scripts require hardcoded path)

All functional test code now uses centralized TFM source.
- Add eng/productTfm.txt as single source of truth for product TFM
- Update Directory.Build.props to read TFM from file
- Update FSharpBuild.Directory.Build.targets to generate F# constants
- Update UseLocalCompiler.Directory.Build.props to use TFM property
- Update buildtools/checkpackages to use TFM property
- Update eng/Build.ps1, eng/build.sh, eng/build-utils.ps1, eng/test-determinism.ps1
- Update eng/templates/regression-test-jobs.yml
- Update CompilerLocation.fs to use generated constants
- Update TestFramework.fs with productTfm constant
- Update test files to use centralized TFM
- Add release notes
- Add eng/productTfm.txt as single source of truth for product TFM
- Update Directory.Build.props to read TFM from file
- Update FSharpBuild.Directory.Build.targets to generate F# constants
- Update UseLocalCompiler.Directory.Build.props to use TFM property
- Update buildtools/checkpackages to use TFM property
- Update eng/Build.ps1, eng/build.sh, eng/build-utils.ps1, eng/test-determinism.ps1
- Update eng/templates/regression-test-jobs.yml
- Update CompilerLocation.fs to use generated constants
- Update TestFramework.fs with productTfm constant
- Update test files to use centralized TFM
- Add release notes
- Add eng/productTfm.txt as single source of truth for product TFM (net10.0)
- Expose via MSBuild: $(FSharpNetCoreProductDefaultTargetFramework), $(FSharpNetCoreProductMajorVersion)
- Expose via F# build properties: fsProductTfm, fsProductTfmMajorVersion
- Expose via test framework: TestFramework.productTfm
- Update build scripts (bash/powershell) to read TFM dynamically
- Update test projects to use centralized TFM value
- Update ILVerify to read product TFM from eng/productTfm.txt
@github-actions
Copy link
Contributor

github-actions bot commented Feb 2, 2026

❗ Release notes required


✅ Found changes and release notes in following paths:

Warning

No PR link found in some release notes, please consider adding it.

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/10.0.300.md No current pull request URL (#19251) found, please consider adding it

T-Gro added 6 commits February 2, 2026 14:06
- Exclude fsProductTfm and fsProductTfmMajorVersion from FSharp.Core's
  buildproperties.fs since the 'int' type isn't defined yet during
  FSharp.Core compilation (bootstrapping issue)
- Fix array yield syntax in CompilerLocation.fs to use yield! for
  combining arrays instead of yield with semicolon-separated values
… artifact

The UseLocalCompiler.Directory.Build.props now reads FSharpNetCoreProductDefaultTargetFramework
from productTfm.txt. For regression tests, this file needs to be co-located with the props file.

Changes:
- UseLocalCompiler.Directory.Build.props: Add logic to read productTfm.txt from either
  co-located path (regression tests) or eng/ subdirectory (local usage)
- azure-pipelines-PR.yml: Stage both UseLocalCompiler.Directory.Build.props and
  eng/productTfm.txt together before publishing as UseLocalCompilerProps artifact

This fixes MSB4057 errors in FSharpPlus regression tests where the target
'_GetRestoreSettingsPerFramework' was not found due to undefined TFM property.
@T-Gro T-Gro marked this pull request as ready for review February 4, 2026 10:32
@T-Gro T-Gro requested a review from a team as a code owner February 4, 2026 10:32
@T-Gro T-Gro requested a review from abonie February 4, 2026 10:33
@github-project-automation github-project-automation bot moved this from New to In Progress in F# Compiler and Tooling Feb 4, 2026
@T-Gro T-Gro requested a review from ViktorHofer February 5, 2026 08:32
T-Gro added 8 commits February 5, 2026 11:38
… TFM

Create eng/TargetFrameworks.props with:
- FSharpNetCoreProductDefaultTargetFramework=net10.0 (defensive, only if not set)
- FSharpNetCoreProductMajorVersion derived via regex

This is the first step in migrating from productTfm.txt to a proper
MSBuild property file. The txt file is kept temporarily for coexistence
during the migration.
Replace File.ReadAllText(productTfm.txt) with FSharp.BuildProperties.fsProductTfm
which is auto-generated during build from MSBuild properties.
Update all 4 PowerShell scripts to use dotnet msbuild --getProperty: via
eng/common/dotnet.ps1 instead of Get-Content productTfm.txt.

Files updated:
- eng/Build.ps1
- eng/build-utils.ps1
- eng/test-determinism.ps1
- tests/ILVerify/ilverify.ps1

This is part of the migration from productTfm.txt to the MSBuild-based
eng/TargetFrameworks.props as the single source of truth for the product TFM.
Replace `cat productTfm.txt` with `dotnet msbuild --getProperty` to read
FSharpNetCoreProductDefaultTargetFramework from eng/TargetFrameworks.props.

This is part of the migration from productTfm.txt to the MSBuild-centric
approach for the single source of truth for the product TFM.
Replace set /p reading from productTfm.txt with dotnet msbuild
--getProperty:FSharpNetCoreProductDefaultTargetFramework in both
TestBasicProvider.cmd and TestComboProvider.cmd.
- azure-pipelines-PR.yml: Copy TargetFrameworks.props instead of productTfm.txt
- regression-test-jobs.yml: Use --getProperty to read TFM from staged props file
- Remove eng/productTfm.txt (replaced by TargetFrameworks.props)
- Update release notes to reference TargetFrameworks.props
- Update identifierAnalysisByType.fsx comment to reference new location
…ding productTfm.txt

Phase 2 of the productTfm.txt migration: Replace File.ReadAllText calls with
Import of eng/TargetFrameworks.props as the single source of truth for TFM.

Files updated:
- Directory.Build.props
- UseLocalCompiler.Directory.Build.props
- buildtools/checkpackages/Directory.Build.props
- tests/AheadOfTime/Directory.Build.props
- tests/fsharp/SDKTests/tests/FSharpCoreVersionTest.props
Copy link
Member

@ViktorHofer ViktorHofer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good. I'm surprised that there are so many places in scripts and even in YML that need the TFM. That's a bit unusual but is fine.

One important feedback: Whenever you call into dotnet msbuild, make sure that you pass global properties in as well. Otherwise conditions on DotNetBuildSourceOnly=true are wrong and the TFM would be wrong.

Co-authored-by: Viktor Hofer <viktor.hofer@microsoft.com>
@ViktorHofer
Copy link
Member

Remove / change this block now?

<Choose>
<!-- Once we move to OOP in VS, and major dependants of FCS will move to netcore (not netstandard),
we should also support $(NetPrevious) for all releases.
This will likely include FCS and FSharp.Core as well as shipped products.
Right now, it only covers products we ship (FSC and FSI), not NuGet packages. -->
<When Condition="'$(DotNetBuildSourceOnly)' == 'true'">
<PropertyGroup>
<FSharpNetCoreProductTargetFramework>$(NetCurrent)</FSharpNetCoreProductTargetFramework>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<FSharpNetCoreProductTargetFramework>$(FSharpNetCoreProductDefaultTargetFramework)</FSharpNetCoreProductTargetFramework>
</PropertyGroup>
</Otherwise>
</Choose>

@T-Gro
Copy link
Member Author

T-Gro commented Feb 6, 2026

@ViktorHofer : Had exactly the same in mind , done :)

@ViktorHofer
Copy link
Member

Is NetCurrent available in that new props file? The value comes from the Arcade SDK.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

Proposal: Centralize Product TFM into a Single Source of Truth

3 participants