From 10aed2cbbcdc5aaa4948cc4137e3cec2a2fca976 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 21 Oct 2026 13:57:34 +0200 Subject: [PATCH 1/7] Add narrated slides release agent (#20813) * Add narrated slides release agent Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6cc6e5b6-78b0-43d8-9b4b-d705cb3ec54e * Pin narrated slides wrapper source Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6cc6e5b6-78b0-43d8-9b4b-d705cb3ec54e --------- Copilot-Session: 6cc6e5b6-78b0-43d8-9b4b-d705cb3ec54e --- .../release-notes-to-narrated-slides.md | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/agents/release-notes-to-narrated-slides.md diff --git a/.github/agents/release-notes-to-narrated-slides.md b/.github/agents/release-notes-to-narrated-slides.md new file mode 100644 index 00000000000..5cf36d61cdd --- /dev/null +++ b/.github/agents/release-notes-to-narrated-slides.md @@ -0,0 +1,26 @@ +--- +name: Release notes to narrated slides +description: Turn a URL to written release notes into a short slide deck plus the script a presenter speaks over it. Inputs - release-notes URL, talk length in minutes. +--- + +# Release notes to narrated slides + +Wrapper around [OnurGumus/codebase-video at commit 9deddc3](https://github.com/OnurGumus/codebase-video/tree/9deddc3f7b3a414650966850201b5da703367740). Download exactly that commit, not a branch head. Read its `SKILL.md` and engine notes, and use its setup, rendering and checks. Replace Claude-specific calls with your own. Do not copy it here. +It narrates with a synthetic voice. Here the presenter speaks, so you produce slides and a script, not audio. +To find release content, the [F# release announcement agent](fsharp-release-announcement.md) can run first. + +**Inputs:** release-notes URL, minutes. Ask once with the ask tool: presenter name and team, audience, deep-dive topics, topics to skip, a one-line motivation, highlights of the previous release, and a design template (a style description, images or a URL). Never infer a team from a handle. Without answers, state your defaults. + +**Output:** step-through slides (self-contained HTML plus PDF; no .pptx) and a script holding only the words to say, as speaker notes and a separate file. Show a rendered draft before the presenter records. + +## Rules for release notes + +- **Length:** about 30 s per slide, 1.8 spoken words per second, within the limit. Say the estimate is unverified until the presenter records. +- **Slide 1:** thanks; name and team only if known. One line on the preceding preview or RC of the same product with "go read those notes"; verify it, keep the presenter's claim, flag mismatches in the report. End with the agenda in one sentence. +- **Story:** lead with the benefit. One beat per slide, headline claim or number in the title, a one-sentence bridge at the end of each note. Depth on the presenter's topics; everything else on one "also" slide. Housekeeping goes in a footnote. +- **Evidence:** follow linked articles, tables and charts that back a slide. Show tables complete, including unchanged rows. Compile and run every code sample you show on the SDK the notes name, else mark it unverified. Show real compiler errors. Never retype numbers or code. +- **Narration:** say what the team did, then the range, never cherry-picked points. Caveats go on the slide, not in speech. Say preview, prototype, or not production once per topic. Separate what ships from copy-from-repo prototypes. Never promise speed; say "experiment and measure". +- **Voice:** natural first person, motivation before mechanism, the presenter's framing in plain terms. Do not invent technical claims. +- **Template:** follow its rules on type size and colour. It may be confidential: keep derived art, fonts and colours out of the repository. +- **Check:** render and look at every slide. Reject overflow, off-slide content, and text under the template's minimum (24 px at 1920 px wide if none). +- **Finish with a report:** recap verified, samples run (N/N), slides visually checked, claims you could not verify. From d0b662a911582ab78636c2326e21c09f49910898 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 21 Oct 2026 13:58:38 +0200 Subject: [PATCH 2/7] Distinguish struct tuples in quotation output (#20814) * Fix struct tuple quotation printer (#20800) Use the stored tuple type only to select the debug-layout node name. Preserve tuple construction, patterns, types and shape reconstruction. Cover all four renderers with typed/untyped literals, both constructors, mixed nesting, generic lambdas, and literal/constructed arities 7/8/9. Add the VNEXT 11.0.200 FSharp.Core printer-only release note. All commands below used BUILDING_USING_DOTNET=true and FSHARPCORE_USE_PACKAGE=false. MTP options are passed directly: the selected SDK rejects the handoff's extra "--" separator. RED/GREEN command (plus Detailed output and xUnit XML diagnostics): dotnet test --project tests\FSharp.Core.UnitTests\FSharp.Core.UnitTests.fsproj -c Debug -f net11.0 --filter-method "*Issue 20800*" Before product edits: 2 executed, reference passed, struct failed, exit 2. Expected: NewStructTuple (Value (1), Value ("a")) Actual: NewTuple (Value (1), Value ("a")) After the printer-only change: unchanged pair 2/2 passed, exit 0. Expanded matrix: 3/3 passed, exit 0. Both RED and GREEN loaded version 11.0.0.0 from: C:\agents\fsharp-worktrees\issue-2112\artifacts\bin\FSharp.Core.UnitTests\Debug\net11.0\FSharp.Core.dll Each matched the corresponding just-built repository core output: C:\agents\fsharp-worktrees\issue-2112\artifacts\bin\FSharp.Core\Debug\net10.0\FSharp.Core.dll RED SHA256: C1CDC73CE62BAB4DF4478F94CBA53488D69758C896734C4B15D61998DCB28AEF GREEN SHA256: 05129632B3E6821E6E857FDF2EA68A0AEDC3FD4B67EC783707EC7203DFEE3E5D Temporary runtime provenance diagnostics were removed. Validation (all exit 0): .\build.cmd -c Release Initial and final composite builds: 0 errors. dotnet test --project tests\FSharp.Core.UnitTests\FSharp.Core.UnitTests.fsproj -c Debug -f net11.0 --filter-class "FSharp.Core.UnitTests.Quotations.*" --minimum-expected-tests 1 29/29 passed, including unchanged tuple active-pattern facts. dotnet test --project tests\FSharp.Core.UnitTests\FSharp.Core.UnitTests.fsproj -c Debug -f net11.0 Total 6358: 6353 passed, 5 skipped, 0 failed. dotnet test --project tests\FSharp.Compiler.ComponentTests\FSharp.Compiler.ComponentTests.fsproj -c Release -f net11.0 --filter-class "Conformance.Expressions.ExpressionQuotations.*" --minimum-expected-tests 1 123/123 passed, including rendering snapshots. dotnet test --project tests\fsharp\FSharpSuite.Tests.fsproj -c Debug -f net472 -p:BUILDING_USING_DOTNET=false --filter-method "*quotes-*" --output Detailed --minimum-expected-tests 3 3/3 passed: quotes-FSC-FSC_DEBUG, quotes-FSC-BASIC, quotes-FSI-BASIC. These cases are #if !NETCOREAPP; net11.0 cannot discover them. MTP method filtering needs the leading wildcard for qualified names. MSBuild property enables desktop targeting; environment retains the values above and repository core project references remain in use. Both requested file-scoped Fantomas commands ran. The product branch is formatted; tests/ is intentionally excluded by .fantomasignore. No unrelated formatting. git diff --check passed. Bounded read-only expert-reviewer review using GPT-5.4: no findings. Copilot Coding Agent-only final CoreCLR sweep is not applicable to this Copilot CLI execution. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add release notes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/release-notes/.FSharp.Core/11.0.200.md | 2 + src/FSharp.Core/quotations.fs | 8 +- .../FSharpQuotations.fs | 109 ++++++++++++++++++ 3 files changed, 118 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/.FSharp.Core/11.0.200.md b/docs/release-notes/.FSharp.Core/11.0.200.md index 5240f757be4..23a4bea0427 100644 --- a/docs/release-notes/.FSharp.Core/11.0.200.md +++ b/docs/release-notes/.FSharp.Core/11.0.200.md @@ -4,4 +4,6 @@ ### Fixed +* Quotation debug text from `Expr.ToString()`, `Expr.ToString(full)`, and `%A` now prints struct tuple construction as `NewStructTuple`; reference tuples remain `NewTuple`. ([Issue #20800](https://github.com/dotnet/fsharp/issues/20800), [PR #20814](https://github.com/dotnet/fsharp/pull/20814)) + ### Changed diff --git a/src/FSharp.Core/quotations.fs b/src/FSharp.Core/quotations.fs index 4ac472877e2..dfc74644b0c 100644 --- a/src/FSharp.Core/quotations.fs +++ b/src/FSharp.Core/quotations.fs @@ -378,7 +378,13 @@ and [] Expr( | CombTerm(NewRecordOp ty, args) -> combL "NewRecord" (typeL ty :: exprs args) | CombTerm(NewUnionCaseOp unionCase, args) -> combL "NewUnionCase" (ucaseL unionCase :: exprs args) | CombTerm(UnionCaseTestOp unionCase, args) -> combL "UnionCaseTest" (exprs args @ [ ucaseL unionCase ]) - | CombTerm(NewTupleOp _, args) -> combL "NewTuple" (exprs args) + | CombTerm(NewTupleOp ty, args) -> + combL + (if ty.IsValueType then + "NewStructTuple" + else + "NewTuple") + (exprs args) | CombTerm(TupleGetOp(_, i), [ arg ]) -> combL "TupleGet" ([ expr arg ] @ [ objL i ]) | CombTerm(ValueOp(v, _, Some nm), []) -> combL "ValueWithName" [ objL v; wordL (tagLocal nm) ] | CombTerm(ValueOp(v, _, None), []) -> combL "Value" [ objL v ] diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Quotations/FSharpQuotations.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Quotations/FSharpQuotations.fs index 4b2c1caf3d3..d354f69c0ac 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Quotations/FSharpQuotations.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Quotations/FSharpQuotations.fs @@ -94,6 +94,115 @@ type FSharpQuotationsTests() = member x.GetConstructorFiltersOutStaticConstructor() = ignore <@ System.Exception() @> + [] + member x.``Issue 20800 - struct tuple quotation printing`` () = + Assert.Equal( + """NewStructTuple (Value (1), Value ("a"))""", + (<@ struct (1, "a") @>).ToString() + ) + + [] + member x.``Issue 20800 - reference tuple quotation printing`` () = + Assert.Equal( + """NewTuple (Value (1), Value ("a"))""", + (<@ (1, "a") @>).ToString() + ) + + static member private TupleLambdas<'T>() = + [ + (<@ fun (x: 'T) -> struct (x, 1) @> :> Expr), + typeof<'T -> struct ('T * int)>, typeof, "NewStructTuple" + (<@ fun (x: 'T) -> (x, 1) @> :> Expr), + typeof<'T -> ('T * int)>, typeof<'T * int>, "NewTuple" + ] + + [] + member x.``Issue 20800 - tuple rendering preserves types patterns and shapes`` () = + let assertPrinted expected (expr: Expr) = + Assert.Equal(expected, expr.ToString()) + Assert.Equal(expected, expr.ToString(false)) + Assert.Equal(expected, expr.ToString(true)) + Assert.Equal(expected, sprintf "%A" expr) + + let assertTuple (expectedType: Type) expectedArgs expected (expr: Expr) = + match expr with + | ExprShape.ShapeCombination(shape, args) -> + Assert.Equal(expectedArgs, args) + let rebuilt = ExprShape.RebuildShapeCombination(shape, args) + for tuple in [expr; rebuilt] do + Assert.Equal(expectedType, tuple.Type) + Assert.Equal(expectedType.IsValueType, tuple.Type.IsValueType) + match tuple with + | NewTuple args -> Assert.Equal(expectedArgs, args) + | _ -> Assert.Fail("Expected NewTuple to match both tuple kinds") + match tuple with + | NewStructTuple args -> + Assert.True(expectedType.IsValueType) + Assert.Equal(expectedArgs, args) + | _ -> Assert.False(expectedType.IsValueType) + assertPrinted expected tuple + | _ -> Assert.Fail("Expected a tuple combination") + + let pairArgs = [ <@@ 1 @@>; <@@ "a" @@> ] + for expectedType, label, expressions in [ + typeof, "NewTuple", + [ (<@ (1, "a") @> :> Expr); <@@ (1, "a") @@>; Expr.NewTuple pairArgs ] + typeof, "NewStructTuple", + [ (<@ struct (1, "a") @> :> Expr); <@@ struct (1, "a") @@> + Expr.NewStructTuple pairArgs + Expr.NewStructTuple(typeof.Assembly, pairArgs) ] + ] do + for expr in expressions do + assertTuple expectedType pairArgs ($"""{label} (Value (1), Value ("a"))""") expr + + for expr, expectedType, args, expected in [ + (<@ struct ((1, "a"), 2) @> :> Expr), typeof, + [ <@@ (1, "a") @@>; <@@ 2 @@> ], + """NewStructTuple (NewTuple (Value (1), Value ("a")), Value (2))""" + (<@ (struct (1, "a"), 2) @> :> Expr), typeof, + [ <@@ struct (1, "a") @@>; <@@ 2 @@> ], + """NewTuple (NewStructTuple (Value (1), Value ("a")), Value (2))""" + (<@ struct (1, "a", true) @> :> Expr), typeof, + [ yield! pairArgs; <@@ true @@> ], + """NewStructTuple (Value (1), Value ("a"), Value (true))""" + (<@ (1, "a", true) @> :> Expr), typeof, + [ yield! pairArgs; <@@ true @@> ], + """NewTuple (Value (1), Value ("a"), Value (true))""" + ] do + assertTuple expectedType args expected expr + + for expr, functionType, tupleType, label in [ + yield! FSharpQuotationsTests.TupleLambdas() + yield! FSharpQuotationsTests.TupleLambdas() + ] do + Assert.Equal(functionType, expr.Type) + assertPrinted $"Lambda (x, {label} (x, Value (1)))" expr + match expr with + | Lambda(v, body) -> + assertTuple tupleType [ Expr.Var v; <@@ 1 @@> ] $"{label} (x, Value (1))" body + | _ -> Assert.Fail("Expected a generic tuple lambda") + + let firstSix = "Value (1), Value (2), Value (3), Value (4), Value (5), Value (6)" + for arity, tail, referenceType, structType, referenceLiteral, structLiteral in [ + 7, "Value (7)", typeof, + typeof, + (<@ (1,2,3,4,5,6,7) @> :> Expr), (<@ struct (1,2,3,4,5,6,7) @> :> Expr) + 8, "Value (7), Value (8)", typeof, + typeof, + (<@ (1,2,3,4,5,6,7,8) @> :> Expr), (<@ struct (1,2,3,4,5,6,7,8) @> :> Expr) + 9, "Value (7), Value (8), Value (9)", typeof, + typeof, + (<@ (1,2,3,4,5,6,7,8,9) @> :> Expr), (<@ struct (1,2,3,4,5,6,7,8,9) @> :> Expr) + ] do + let args = [ for i in 1..arity -> Expr.Value i ] + for expectedType, label, indent, literal, constructed in [ + referenceType, "NewTuple", " ", referenceLiteral, Expr.NewTuple args + structType, "NewStructTuple", " ", structLiteral, Expr.NewStructTuple args + ] do + let expected = $"{label} ({firstSix},\n{indent}{tail})" + for expr in [literal; constructed] do + assertTuple expectedType args expected expr + [] member x.``NewStructTuple literal should be recognized by NewStructTuple active pattern`` () = match <@ struct(1, "") @> with From add651eca955993f0bede45589a52573850107c0 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 21 Oct 2026 17:51:59 +0200 Subject: [PATCH 3/7] Update service-branch-merge.json with new merge config (#20830) * Update service-branch-merge.json with new merge config Removed old branch flow configurations and added a new merge configuration for release/10.0.4xx. * Add automatic merge flow from main to feature/net12-scouting Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8dc30c74-e8bc-4ab4-b25f-2ebb9ae42ad7 --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8dc30c74-e8bc-4ab4-b25f-2ebb9ae42ad7 --- .config/service-branch-merge.json | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/.config/service-branch-merge.json b/.config/service-branch-merge.json index b52ba67be18..97c573af195 100644 --- a/.config/service-branch-merge.json +++ b/.config/service-branch-merge.json @@ -1,19 +1,6 @@ { "merge-flow-configurations": { - // regular branch flow - "release/dev17.12": { - "MergeToBranch": "release/dev17.13", - "ExtraSwitches": "-QuietComments" - }, - "release/dev17.13": { - "MergeToBranch": "release/dev17.14", - "ExtraSwitches": "-QuietComments" - }, - "release/dev17.14": { - "MergeToBranch": "release/dev18.0", - "ExtraSwitches": "-QuietComments" - }, - "release/dev18.0": { + "release/10.0.4xx": { "MergeToBranch": "main", "ExtraSwitches": "-QuietComments", "ResetToTargetPaths": [ @@ -26,6 +13,18 @@ "azure-pipelines.yml", "azure-pipelines-PR.yml" ] + }, + "main": { + "MergeToBranch": "feature/net12-scouting", + "ExtraSwitches": "-QuietComments", + "ResetToTargetPaths": [ + "global.json", + "eng/Version.Details.xml", + "eng/Version.Details.props", + "eng/Versions.props", + "eng/common/**", + "eng/TargetFrameworks.props" + ] } } } From db3b2a459041136e57f635ab1405d2815b4ae5b5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 15:53:10 +0000 Subject: [PATCH 4/7] Reset files to feature/net12-scouting Reset patterns: - global.json - eng/Version.Details.xml - eng/Version.Details.props - eng/Versions.props - eng/common/** - eng/TargetFrameworks.props --- eng/TargetFrameworks.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/TargetFrameworks.props b/eng/TargetFrameworks.props index a55b1e1a3bb..2bea1ee3384 100644 --- a/eng/TargetFrameworks.props +++ b/eng/TargetFrameworks.props @@ -11,7 +11,7 @@ - net11.0 + net12.0 $([System.Text.RegularExpressions.Regex]::Replace('$(FSharpNetCoreProductTargetFramework)', '^net(\d+)\.0$', '$1')) From 261fbe1993b5536877ed9656f27c989dc1607b65 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Wed, 21 Oct 2026 16:25:22 +0000 Subject: [PATCH 5/7] Update dependencies from https://github.com/dotnet/arcade build 20261009.4 On relative base path root Microsoft.DotNet.Arcade.Sdk From Version 12.0.0-beta.26505.2 -> To Version 12.0.0-beta.26509.4 --- eng/Version.Details.props | 2 +- eng/Version.Details.xml | 4 ++-- eng/common/build.ps1 | 4 ---- eng/common/core-templates/steps/astred-artifacts.yml | 9 ++++++--- eng/common/msbuild.ps1 | 5 ----- eng/common/tools.ps1 | 10 ++++++---- eng/common/tools.sh | 8 ++------ global.json | 6 +++--- 8 files changed, 20 insertions(+), 28 deletions(-) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 8251142a313..dc56a1c1225 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -6,7 +6,7 @@ This file should be imported by eng/Versions.props - 12.0.0-beta.26505.2 + 12.0.0-beta.26509.4 10.0.14 10.0.14 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index fead4e0cd63..ba06132dda3 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -77,9 +77,9 @@ - + https://github.com/dotnet/arcade - 5a63d054ea793bdbdb6aa9f4558558e3a8f41529 + 7dad90156e9d69e991ad499ece9cf805cabcf506 https://dev.azure.com/dnceng/internal/_git/dotnet-optimization diff --git a/eng/common/build.ps1 b/eng/common/build.ps1 index 305132a6d6d..64c5ed10263 100644 --- a/eng/common/build.ps1 +++ b/eng/common/build.ps1 @@ -181,10 +181,6 @@ try { if (-not $PSBoundParameters.ContainsKey('nodeReuse')) { $nodeReuse = $false } - # MSBuild's multi-threaded mode isn't run on CI unless it was explicitly requested via -msbuildMultiThreaded. - if (-not $PSBoundParameters.ContainsKey('msbuildMultiThreaded')) { - $msbuildMultiThreaded = $false - } } if (-not [string]::IsNullOrEmpty($binaryLogName)) { diff --git a/eng/common/core-templates/steps/astred-artifacts.yml b/eng/common/core-templates/steps/astred-artifacts.yml index b27a54e08ef..86420925b44 100644 --- a/eng/common/core-templates/steps/astred-artifacts.yml +++ b/eng/common/core-templates/steps/astred-artifacts.yml @@ -21,7 +21,7 @@ steps: FeedUrl: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet-internal-FoSSE/nuget/v3/index.json' - pwsh: | - $ErrorActionPreference = 'Continue' + $ErrorActionPreference = 'Stop' $apjOut = Join-Path $env:ASTRED_CAPTURE_PATH 'apj' New-Item -ItemType Directory -Force -Path $apjOut | Out-Null @@ -34,10 +34,13 @@ steps: $project = Join-Path $apjOut '.astred.project.json' $binlogPaths = @($binlogs.FullName) + $responseFile = Join-Path $apjOut 'binlogs.rsp' + $binlogPaths | ForEach-Object { '"{0}"' -f $_ } | + Set-Content -LiteralPath $responseFile -Encoding utf8 Write-Host "astproj: processing $($binlogPaths.Count) binlog(s)" - astred astproj -nofolders @binlogPaths "-o:$project" + astred astproj -nofolders "@$responseFile" "-o:$project" if ($LASTEXITCODE -ne 0) { - Write-Host "##vso[task.logissue type=warning]astproj failed (exit $LASTEXITCODE)" + throw "astproj failed (exit $LASTEXITCODE)" } displayName: Generate Astred Project Files workingDirectory: ${{ parameters.sourcesPath }} diff --git a/eng/common/msbuild.ps1 b/eng/common/msbuild.ps1 index 8076945064d..161f8e8077c 100644 --- a/eng/common/msbuild.ps1 +++ b/eng/common/msbuild.ps1 @@ -19,11 +19,6 @@ try { $nodeReuse = $false } - # MSBuild's multi-threaded mode isn't run on CI unless it was explicitly requested via -msbuildMultiThreaded. - if ($ci -and -not $PSBoundParameters.ContainsKey('msbuildMultiThreaded')) { - $msbuildMultiThreaded = $false - } - MSBuild @extraArgs } catch { diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index cea14279b85..9cb84a8a941 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -31,8 +31,9 @@ # Set to true to reuse msbuild nodes. Recommended to not reuse on CI. [bool]$nodeReuse = if (Test-Path variable:nodeReuse) { $nodeReuse } else { !$ci } -# Set to true to build with MSBuild's multi-threaded mode (-mt). Enabled by default for local builds and not run on CI. -[bool]$msbuildMultiThreaded = if (Test-Path variable:msbuildMultiThreaded) { $msbuildMultiThreaded } else { !$ci } +# Set to true to build with MSBuild's multi-threaded mode (-mt). Enabled by default for both local and CI builds. +# Not applied when building with msbuild.exe (VS MSBuild). +[bool]$msbuildMultiThreaded = if (Test-Path variable:msbuildMultiThreaded) { $msbuildMultiThreaded } else { $true } # Configures warning treatment in msbuild. [bool]$warnAsError = if (Test-Path variable:warnAsError) { $warnAsError } else { $true } @@ -812,8 +813,9 @@ function MSBuild() { $cmdArgs = "$($buildTool.Command) /m /nologo /clp:Summary /v:$verbosity /nr:$nodeReuse /p:ContinuousIntegrationBuild=$ci" - # Build with MSBuild's multi-threaded mode. - if ($msbuildMultiThreaded) { + # Build with MSBuild's multi-threaded mode, but not with msbuild.exe (VS MSBuild) for now. + # TODO: Enable for VS MSBuild as well: https://github.com/dotnet/dotnet/issues/9989 + if ($msbuildMultiThreaded -and [System.IO.Path]::GetFileName($buildTool.Path) -ne 'msbuild.exe') { $cmdArgs += ' -mt' } diff --git a/eng/common/tools.sh b/eng/common/tools.sh index fdbc19243f1..3b4a5ac5142 100755 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -60,13 +60,9 @@ else node_reuse=${node_reuse:-true} fi -# Set to true to build with MSBuild's multi-threaded mode (-mt). Enabled by default for local builds and not run on CI. +# Set to true to build with MSBuild's multi-threaded mode (-mt). Enabled by default for both local and CI builds. msbuild_multi_threaded=$(NormalizeBoolArg "${msbuild_multi_threaded:-}") -if [[ "$ci" == true ]]; then - msbuild_multi_threaded=${msbuild_multi_threaded:-false} -else - msbuild_multi_threaded=${msbuild_multi_threaded:-true} -fi +msbuild_multi_threaded=${msbuild_multi_threaded:-true} # Configures warning treatment in msbuild. warn_as_error=$(NormalizeBoolArg "${warn_as_error:-}") diff --git a/global.json b/global.json index c01cb2bb7fc..07c8230ed59 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "11.0.100-rc.1.26420.103", + "version": "12.0.100-alpha.1.26480.102", "allowPrerelease": true, "rollForward": "latestMinor", "paths": [ @@ -13,7 +13,7 @@ "runner": "Microsoft.Testing.Platform" }, "tools": { - "dotnet": "11.0.100-rc.1.26420.103", + "dotnet": "12.0.100-alpha.1.26480.102", "vs": { "version": "18.0", "components": [ @@ -23,7 +23,7 @@ "xcopy-msbuild": "18.0.0" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "12.0.0-beta.26505.2", + "Microsoft.DotNet.Arcade.Sdk": "12.0.0-beta.26509.4", "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.23255.2" } } From 74521b64a832b17157c782aae0bfebc1fcaf4d52 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 21 Oct 2026 19:26:54 +0200 Subject: [PATCH 6/7] Fix .NET 12 scouting build and NuGet resolution with alpha SDK Early .NET 12 alpha SDKs ship with NETCoreAppMaximumVersion=11.0 in Microsoft.NETCoreSdk.BundledVersions.props before the SDK's own TFM bump lands, causing _CheckForUnsupportedNETCoreVersion to fail with NETSDK1045 when targeting net12.0 or running #r "nuget:" on .NET 12. Set NETCoreAppMaximumVersion=12.0 when targeting net12.0. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8dc30c74-e8bc-4ab4-b25f-2ebb9ae42ad7 --- eng/TargetFrameworks.props | 3 +++ .../FSharp.DependencyManager.ProjectFile.fs | 1 + tests/FSharp.Build.UnitTests/ResourceTests.fs | 1 + .../CompilerService/EncMethodDebugInformationTests.fs | 1 + tests/FSharp.Test.Utilities/ProjectGeneration.fs | 1 + tests/FSharp.Test.Utilities/Utilities.fs | 1 + tests/fsharp/single-test.fs | 1 + 7 files changed, 9 insertions(+) diff --git a/eng/TargetFrameworks.props b/eng/TargetFrameworks.props index 2bea1ee3384..bd879d7f3fd 100644 --- a/eng/TargetFrameworks.props +++ b/eng/TargetFrameworks.props @@ -16,6 +16,9 @@ $([System.Text.RegularExpressions.Regex]::Replace('$(FSharpNetCoreProductTargetFramework)', '^net(\d+)\.0$', '$1')) + + 12.0 + net10.0 diff --git a/src/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.ProjectFile.fs b/src/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.ProjectFile.fs index dfe128da71d..c2230089d1e 100644 --- a/src/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.ProjectFile.fs +++ b/src/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.ProjectFile.fs @@ -49,6 +49,7 @@ $(POUND_R) $(TARGETFRAMEWORK) + 12.0 $(RUNTIMEIDENTIFIER) false true diff --git a/tests/FSharp.Build.UnitTests/ResourceTests.fs b/tests/FSharp.Build.UnitTests/ResourceTests.fs index 711e9e2a0ab..c91faabf8d3 100644 --- a/tests/FSharp.Build.UnitTests/ResourceTests.fs +++ b/tests/FSharp.Build.UnitTests/ResourceTests.fs @@ -27,6 +27,7 @@ let private createSdkProjectDirectory () = false {productTfm} + 12.0 {buildDirectory ++ "Microsoft.FSharp.NetSdk.targets"} {initialConfig.FSharpBuild} "{initialConfig.DOTNETFSCCOMPILERPATH}" diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/EncMethodDebugInformationTests.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/EncMethodDebugInformationTests.fs index e7d748c1673..652085644cd 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/EncMethodDebugInformationTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/EncMethodDebugInformationTests.fs @@ -329,6 +329,7 @@ let private buildCSharpScratchPdb () = Library {TestFramework.productTfm} + 12.0 portable false true diff --git a/tests/FSharp.Test.Utilities/ProjectGeneration.fs b/tests/FSharp.Test.Utilities/ProjectGeneration.fs index 1a8a05f38e3..c5e5e44702c 100644 --- a/tests/FSharp.Test.Utilities/ProjectGeneration.fs +++ b/tests/FSharp.Test.Utilities/ProjectGeneration.fs @@ -458,6 +458,7 @@ let private renderFsProj (p: SyntheticProject) = Exe {productTfm} + 12.0 diff --git a/tests/FSharp.Test.Utilities/Utilities.fs b/tests/FSharp.Test.Utilities/Utilities.fs index 1db689ca706..51a98ea387b 100644 --- a/tests/FSharp.Test.Utilities/Utilities.fs +++ b/tests/FSharp.Test.Utilities/Utilities.fs @@ -146,6 +146,7 @@ module Utilities = Exe $TARGETFRAMEWORK + 12.0 true true diff --git a/tests/fsharp/single-test.fs b/tests/fsharp/single-test.fs index eddb0dc6080..79c4b62e4aa 100644 --- a/tests/fsharp/single-test.fs +++ b/tests/fsharp/single-test.fs @@ -145,6 +145,7 @@ let generateProjectArtifacts (pc:ProjectConfiguration) outputType (targetFramewo $(OUTPUTTYPE) $(TARGETFRAMEWORK) + 12.0 true false $(DEBUG) From 005cfb000fc50735eeca7f6a4fd1d8706a0966b2 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 22 Oct 2026 09:08:39 +0200 Subject: [PATCH 7/7] Use FSharpNetCoreProductTargetFramework in MultithreadedTasks harness Read FSharpNetCoreProductTargetFramework from eng/TargetFrameworks.props instead of hardcoding net11.0 in MultithreadedTasks/run.fsx and Fixture.fsproj. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8dc30c74-e8bc-4ab4-b25f-2ebb9ae42ad7 --- .../MultithreadedTasks/Fixture.fsproj | 2 +- .../MultithreadedTasks/run.fsx | 25 +++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/tests/EndToEndBuildTests/MultithreadedTasks/Fixture.fsproj b/tests/EndToEndBuildTests/MultithreadedTasks/Fixture.fsproj index c6386be1144..398bab9cb8b 100644 --- a/tests/EndToEndBuildTests/MultithreadedTasks/Fixture.fsproj +++ b/tests/EndToEndBuildTests/MultithreadedTasks/Fixture.fsproj @@ -1,6 +1,6 @@ - net11.0 + $(FSharpNetCoreProductTargetFramework) Exe false true diff --git a/tests/EndToEndBuildTests/MultithreadedTasks/run.fsx b/tests/EndToEndBuildTests/MultithreadedTasks/run.fsx index 6f8d2a1b6a7..125abe00d8a 100644 --- a/tests/EndToEndBuildTests/MultithreadedTasks/run.fsx +++ b/tests/EndToEndBuildTests/MultithreadedTasks/run.fsx @@ -53,12 +53,17 @@ let succeed label arguments = let _, sdk = succeed "sdk" [ "--version" ] let _, msbuild = succeed "msbuild" [ "msbuild"; "-version"; "-nologo" ] +let tfm = + let props = File.ReadAllText(Path.Combine(repo, "eng", "TargetFrameworks.props")) + let matched = Regex.Match(props, @"]*>(net\d+\.\d+)") + if not matched.Success then failwith "Could not determine FSharpNetCoreProductTargetFramework from eng/TargetFrameworks.props" + matched.Groups[1].Value let sdkPath = Path.Combine(repo, ".dotnet", "sdk", sdk.Trim()) -let product = Path.Combine(repo, "artifacts", "bin", "fsc", configuration, "net11.0") +let product = Path.Combine(repo, "artifacts", "bin", "fsc", configuration, tfm) let taskProduct = Path.Combine(repo, "artifacts", "bin", "FSharp.Build", configuration, "netstandard2.0") let taskAssembly = Path.Combine(taskProduct, "FSharp.Build.dll") let compiler = Path.Combine(product, "fsc.dll") -let localFsi = Path.Combine(repo, "artifacts", "bin", "fsi", configuration, "net11.0", "fsi.dll") +let localFsi = Path.Combine(repo, "artifacts", "bin", "fsi", configuration, tfm, "fsi.dll") for path in [ taskAssembly; compiler; localFsi ] do if not (File.Exists path) then failwithf "Build local product bits first: missing %s" path let taskHash = hash taskAssembly @@ -67,18 +72,18 @@ write (Path.Combine(root, "versions.txt")) $"SDK {sdk}MSBuild {msbuild}Tasks {ta // An ordinary SDK helper project uses only assemblies supplied by the running SDK, without NuGet packages. let helper = Path.Combine(root, "probe") -write (Path.Combine(root, "Directory.Build.props")) "false" +write (Path.Combine(root, "Directory.Build.props")) $"""false""" write (Path.Combine(root, "Directory.Build.targets")) "" write (Path.Combine(helper, "Probe.cs")) (File.ReadAllText(Path.Combine(__SOURCE_DIRECTORY__, "Probe.cs"))) write (Path.Combine(helper, "Probe.csproj")) $""" - net11.0false + {tfm}false {xml sdkPath}/Microsoft.Build.Framework.dllfalse {xml sdkPath}/Microsoft.Build.Utilities.Core.dllfalse """ succeed "probe-build" [ "build"; Path.Combine(helper, "Probe.csproj"); "-nologo"; "-v:minimal"; "-nr:false" ] |> ignore -let probeAssembly = Path.Combine(helper, "bin", "Debug", "net11.0", "Probe.dll") +let probeAssembly = Path.Combine(helper, "bin", "Debug", tfm, "Probe.dll") let graph = Path.Combine(root, "graph") let projects = [| for index in 1 .. projectCount -> sprintf "P%02d" index |] write (Path.Combine(graph, "Directory.Build.props")) $""" @@ -192,10 +197,10 @@ let validateOutputs label = for file in [ "fsi.txt"; "obj/substituted/template.txt" ] do if File.ReadAllText(Path.Combine(directory, file)) <> name then failwithf "%s: wrong %s" name file for file, marker in [ "Strings.fs", "RESX_" + name; "ILLink.Substitutions.xml", $"fullname=\"{name}\"" ] do - if not (File.ReadAllText(Path.Combine(directory, "obj/Release/net11.0", file)).Contains marker) then + if not (File.ReadAllText(Path.Combine(directory, $"obj/Release/{tfm}", file)).Contains marker) then failwithf "%s: generated %s has the wrong marker" name file - succeed (label + "-" + name) [ Path.Combine(directory, "bin", "Release", "net11.0", name + ".dll") ] |> ignore - for child in [ "bin/Release/net11.0"; "obj/Release/net11.0" ] do + succeed (label + "-" + name) [ Path.Combine(directory, "bin", "Release", tfm, name + ".dll") ] |> ignore + for child in [ $"bin/Release/{tfm}"; $"obj/Release/{tfm}" ] do for file in Directory.GetFiles(Path.Combine(directory, child), "*", SearchOption.AllDirectories) do if [ ".dll"; ".pdb"; ".fs"; ".resx"; ".resources"; ".xml" ] |> List.contains (Path.GetExtension file) then yield Path.GetRelativePath(graph, file), hash file ] |> Map.ofList @@ -248,7 +253,7 @@ for mt in [ false; true ] do if failure = "resource" && errors.Length <> 1 then failwithf "%s reported %d errors for one malformed resource" label errors.Length for name in projects |> Array.skip 1 do - let dll = Path.Combine(graph, name, "bin/Release/net11.0", name + ".dll") + let dll = Path.Combine(graph, name, $"bin/Release/{tfm}", name + ".dll") succeed (label + "-" + name) [ dll ] |> ignore if hash dll <> baseline[Path.GetRelativePath(graph, dll)] then failwithf "%s contaminated %s" label name printfn "%s: expected failure; %d unaffected executables match baseline" label (projectCount - 1) @@ -263,7 +268,7 @@ let resourceNames file = [ for handle in metadata.ManifestResources -> metadata.GetString(metadata.GetManifestResource(handle).Name) ] let trimProject = Path.Combine(graph, projects[1]) let metadata names = names |> List.filter (fun (name: string) -> name.StartsWith("FSharpSignature") || name.StartsWith("FSharpOptimization")) |> Set.ofList -let untrimmed = Path.Combine(trimProject, "bin/Release/net11.0", projects[1] + ".dll") +let untrimmed = Path.Combine(trimProject, $"bin/Release/{tfm}", projects[1] + ".dll") let originalMetadata = metadata (resourceNames untrimmed) if originalMetadata.IsEmpty then failwith "Trimming control has no F# metadata" let trimFailures = ResizeArray()