Summary
Currently, the product TFM (e.g., net10.0, 10.0,net10,10) is hardcoded in multiple places across the repository:
- PowerShell scripts (
Build.ps1, build-utils.ps1, test-determinism.ps1)
- Bash scripts (
build.sh)
- MSBuild props (
Directory.Build.props, UseLocalCompiler.Directory.Build.props)
- Pipeline YAML (
regression-test-jobs.yml)
- Test code (
TestFramework.fs, single-test.fs, etc.)
- Product code (
FxResolver.fs, CompilerLocation.fs)
This makes TFM updates error-prone and requires touching many files.
Proposed Architecture: Single Source of Truth
┌─────────────────────────────────────────────────────────────────────────────┐
│ eng/productTfm.txt │
│ "net10.0" │
│ (SINGLE SOURCE OF TRUTH) │
└─────────────────────────────────────────────────────────────────────────────┘
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
┌───────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ SCRIPTS │ │ MSBUILD │ │ COMPILED CODE │
└───────────────────┘ └─────────────────────┘ └─────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ PowerShell: │ │ Directory.Build.props│ │ buildproperties.fs │
│ Get-Content │ │ reads txt file → │ │ (generated) │
│ eng/productTfm.txt│ │ sets: │ │ │
│ │ │ • $(FSharp...TFM) │ │ fsProductTfm = │
│ Bash: │ │ • $(FSharp...Major) │ │ "net10.0" │
│ cat │ │ │ │ fsProductTfmMajor = │
│ eng/productTfm.txt│ │ ▼ │ │ 10 │
│ │ │ All .fsproj, .props │ │ │
│ Used by: │ │ .targets reference │ │ ▼ │
│ • Build.ps1 │ │ $(FSharp...TFM) │ │ ┌─────────────────┐ │
│ • build.sh │ │ │ │ │ PRODUCT CODE │ │
│ • build-utils.ps1 │ │ Used by: │ │ │ FxResolver.fs │ │
│ • test-determ.ps1 │ │ • TargetFramework │ │ │ CompilerLoc.fs │ │
│ • regression.yml │ │ • Output paths │ │ │ uses FSharp. │ │
│ │ │ • UseLocalCompiler │ │ │ BuildProperties │ │
│ │ │ • checkpackages │ │ └─────────────────┘ │
│ │ │ │ │ │
│ │ │ │ │ ┌─────────────────┐ │
│ │ │ │ │ │ TEST CODE │ │
│ │ │ │ │ │ TestFramework.fs│ │
│ │ │ │ │ │ reads txt file │ │
│ │ │ │ │ │ at runtime via │ │
│ │ │ │ │ │ repoRoot path │ │
│ │ │ │ │ └─────────────────┘ │
└───────────────────┘ └─────────────────────┘ └─────────────────────┘
Implementation Details
1. Create the source file
Create eng/productTfm.txt containing just:
2. Update Directory.Build.props
<PropertyGroup>
<!-- Read TFM from single source of truth -->
<FSharpNetCoreProductDefaultTargetFramework>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)eng/productTfm.txt').Trim())</FSharpNetCoreProductDefaultTargetFramework>
<!-- Derive major version: "net10.0" → "10" -->
<FSharpNetCoreProductMajorVersion>$(FSharpNetCoreProductDefaultTargetFramework.Replace('net','').Replace('.0',''))</FSharpNetCoreProductMajorVersion>
</PropertyGroup>
3. Update FSharpBuild.Directory.Build.targets
Add to _GenerateBuildPropertiesFile target:
<_BuildPropertyLines Include="let fsProductTfm = "$(FSharpNetCoreProductDefaultTargetFramework)"" />
<_BuildPropertyLines Include="let fsProductTfmMajorVersion = $(FSharpNetCoreProductMajorVersion)" />
4. Update Scripts
PowerShell (Build.ps1, build-utils.ps1, test-determinism.ps1):
$fsharpNetCoreProductTfm = (Get-Content "$PSScriptRoot/productTfm.txt").Trim()
Bash (build.sh):
tfm=$(cat "$scriptroot/productTfm.txt" | tr -d '[:space:]')
5. Update Product Code
CompilerLocation.fs - Use generated constant to build the TFM probe list:
let toolingCompatibleVersions =
if typeof<obj>.Assembly.GetName().Name = "System.Private.CoreLib" then
[|
for v in FSharp.BuildProperties.fsProductTfmMajorVersion .. -1 .. 5 do
$"net{v}.0"
"netcoreapp3.1"; "netcoreapp3.0"; "netstandard2.1"
"netcoreapp2.2"; "netcoreapp2.1"; "netcoreapp2.0"; "netstandard2.0"
|]
// ... rest unchanged
FxResolver.fs - Use generated constant for fallback:
| _ -> if isRunningOnCoreClr then FSharp.BuildProperties.fsProductTfm else "net472"
6. Update Test Code
TestFramework.fs - Add a shared value:
/// Product TFM read from single source of truth
let productTfm =
File.ReadAllText(repoRoot ++ "eng" ++ "productTfm.txt").Trim()
Then replace all hardcoded "net10.0" references in test files with TestFramework.productTfm.
Files to Update
| Category |
Files |
| New file |
eng/productTfm.txt |
| MSBuild |
Directory.Build.props, FSharpBuild.Directory.Build.targets |
| Scripts |
eng/Build.ps1, eng/build.sh, eng/build-utils.ps1, eng/test-determinism.ps1 |
| Product |
src/Compiler/Driver/FxResolver.fs, src/Compiler/Facilities/CompilerLocation.fs |
| Tests |
tests/FSharp.Test.Utilities/TestFramework.fs + all files referencing hardcoded TFM |
Benefit
To change the product TFM, edit one file: eng/productTfm.txt
Change net10.0 to net11.0. Done.
Summary
Currently, the product TFM (e.g.,
net10.0,10.0,net10,10) is hardcoded in multiple places across the repository:Build.ps1,build-utils.ps1,test-determinism.ps1)build.sh)Directory.Build.props,UseLocalCompiler.Directory.Build.props)regression-test-jobs.yml)TestFramework.fs,single-test.fs, etc.)FxResolver.fs,CompilerLocation.fs)This makes TFM updates error-prone and requires touching many files.
Proposed Architecture: Single Source of Truth
Implementation Details
1. Create the source file
Create
eng/productTfm.txtcontaining just:2. Update Directory.Build.props
3. Update FSharpBuild.Directory.Build.targets
Add to
_GenerateBuildPropertiesFiletarget:4. Update Scripts
PowerShell (Build.ps1, build-utils.ps1, test-determinism.ps1):
Bash (build.sh):
tfm=$(cat "$scriptroot/productTfm.txt" | tr -d '[:space:]')5. Update Product Code
CompilerLocation.fs - Use generated constant to build the TFM probe list:
FxResolver.fs - Use generated constant for fallback:
6. Update Test Code
TestFramework.fs - Add a shared value:
Then replace all hardcoded
"net10.0"references in test files withTestFramework.productTfm.Files to Update
eng/productTfm.txtDirectory.Build.props,FSharpBuild.Directory.Build.targetseng/Build.ps1,eng/build.sh,eng/build-utils.ps1,eng/test-determinism.ps1src/Compiler/Driver/FxResolver.fs,src/Compiler/Facilities/CompilerLocation.fstests/FSharp.Test.Utilities/TestFramework.fs+ all files referencing hardcoded TFMBenefit
To change the product TFM, edit one file:
eng/productTfm.txtChange
net10.0tonet11.0. Done.