Skip to content

Commit 24c9bf7

Browse files
authored
Auto-generate ILLink.Substitutions.xml to Remove F# Metadata Resources (#18592)
1 parent 53929f2 commit 24c9bf7

File tree

6 files changed

+172
-6
lines changed

6 files changed

+172
-6
lines changed

global.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"sdk": {
3-
"version": "10.0.100-preview.7.25322.101",
3+
"version": "10.0.100-preview.5.25277.114",
44
"allowPrerelease": true,
55
"paths": [
66
".dotnet",
@@ -9,7 +9,7 @@
99
"errorMessage": "The .NET SDK could not be found, please run ./eng/common/dotnet.sh."
1010
},
1111
"tools": {
12-
"dotnet": "10.0.100-preview.7.25322.101",
12+
"dotnet": "10.0.100-preview.5.25277.114",
1313
"vs": {
1414
"version": "17.8",
1515
"components": [

src/FSharp.Build/FSharp.Build.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
<Compile Include="CreateFSharpManifestResourceName.fs" />
4747
<Compile Include="SubstituteText.fs" />
4848
<Compile Include="MapSourceRoots.fs" />
49+
<Compile Include="GenerateILLinkSubstitutions.fs" />
4950
<None Include="Microsoft.FSharp.Targets" CopyToOutputDirectory="PreserveNewest" />
5051
<None Include="Microsoft.Portable.FSharp.Targets" CopyToOutputDirectory="PreserveNewest" />
5152
<None Include="Microsoft.FSharp.NetSdk.props" CopyToOutputDirectory="PreserveNewest" />
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
2+
3+
namespace FSharp.Build
4+
5+
open System
6+
open System.IO
7+
open System.Text
8+
open Microsoft.Build.Framework
9+
open Microsoft.Build.Utilities
10+
11+
/// <summary>
12+
/// MSBuild task that generates ILLink.Substitutions.xml file to remove F# metadata resources during IL linking.
13+
/// </summary>
14+
type GenerateILLinkSubstitutions() =
15+
inherit Task()
16+
17+
/// <summary>
18+
/// Assembly name to use when generating resource names to be removed.
19+
/// </summary>
20+
[<Required>]
21+
member val AssemblyName = "" with get, set
22+
23+
/// <summary>
24+
/// Intermediate output path for storing the generated file.
25+
/// </summary>
26+
[<Required>]
27+
member val IntermediateOutputPath = "" with get, set
28+
29+
/// <summary>
30+
/// Generated embedded resource items.
31+
/// </summary>
32+
[<Output>]
33+
member val GeneratedItems = [||]: ITaskItem[] with get, set
34+
35+
override this.Execute() =
36+
try
37+
// Define the resource prefixes that need to be removed
38+
let resourcePrefixes =
39+
[|
40+
// Signature variants
41+
yield!
42+
[|
43+
for dataType in [| "Data"; "DataB" |] do
44+
for compression in [| ""; "Compressed" |] do
45+
yield $"FSharpSignature{compression}{dataType}"
46+
|]
47+
48+
// Optimization variants
49+
yield!
50+
[|
51+
for dataType in [| "Data"; "DataB" |] do
52+
for compression in [| ""; "Compressed" |] do
53+
yield $"FSharpOptimization{compression}{dataType}"
54+
|]
55+
56+
// Info variants
57+
yield "FSharpOptimizationInfo"
58+
yield "FSharpSignatureInfo"
59+
|]
60+
61+
// Generate the XML content
62+
let sb = StringBuilder(4096) // pre-allocate capacity
63+
sb.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>") |> ignore
64+
sb.AppendLine("<linker>") |> ignore
65+
sb.AppendLine($" <assembly fullname=\"{this.AssemblyName}\">") |> ignore
66+
67+
// Add each resource entry with proper closing tag on the same line
68+
for prefix in resourcePrefixes do
69+
sb.AppendLine($" <resource name=\"{prefix}.{this.AssemblyName}\" action=\"remove\"></resource>")
70+
|> ignore
71+
72+
// Close assembly and linker tags
73+
sb.AppendLine(" </assembly>") |> ignore
74+
sb.AppendLine("</linker>") |> ignore
75+
76+
let xmlContent = sb.ToString()
77+
78+
// Create a file in the intermediate output path
79+
let outputFileName =
80+
Path.Combine(this.IntermediateOutputPath, "ILLink.Substitutions.xml")
81+
82+
Directory.CreateDirectory(this.IntermediateOutputPath) |> ignore
83+
File.WriteAllText(outputFileName, xmlContent)
84+
85+
// Create a TaskItem for the generated file
86+
let item = TaskItem(outputFileName) :> ITaskItem
87+
item.SetMetadata("LogicalName", "ILLink.Substitutions.xml")
88+
89+
this.GeneratedItems <- [| item |]
90+
true
91+
with ex ->
92+
this.Log.LogErrorFromException(ex, true)
93+
false

src/FSharp.Build/Microsoft.FSharp.NetSdk.targets

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,18 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and
154154
<SourceRootMappedPathsFeatureSupported>true</SourceRootMappedPathsFeatureSupported>
155155
</PropertyGroup>
156156

157+
<!-- Register the GenerateILLinkSubstitutions task -->
158+
<UsingTask TaskName="GenerateILLinkSubstitutions" AssemblyFile="$(FSharpBuildAssemblyFile)" />
159+
160+
<!-- Generate ILLink.Substitutions.xml to remove F# metadata resources during trimming. F# Core already has the resource defined -->
161+
<Target Name="GenerateFSharpILLinkSubstitutions" BeforeTargets="CoreCompile" Condition="$(DisableILLinkSubstitutions) != 'true' and $(AssemblyName) != 'FSharp.Core'">
162+
<GenerateILLinkSubstitutions
163+
AssemblyName="$(AssemblyName)"
164+
IntermediateOutputPath="$(IntermediateOutputPath)">
165+
<Output TaskParameter="GeneratedItems" ItemName="Embed" />
166+
</GenerateILLinkSubstitutions>
167+
</Target>
168+
157169
<!--
158170
If InitializeSourceControlInformation target isn't supported, we just continue without invoking that synchronization target.
159171
We'll proceed with SourceRoot (and other source control properties) provided by the user (or blank).
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFrameworks>net9.0</TargetFrameworks>
6+
<LangVersion>preview</LangVersion>
7+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
8+
</PropertyGroup>
9+
10+
<PropertyGroup>
11+
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
12+
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
13+
<DisableImplicitLibraryPacksFolder>true</DisableImplicitLibraryPacksFolder>
14+
<PublishTrimmed>true</PublishTrimmed>
15+
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
16+
</PropertyGroup>
17+
18+
<PropertyGroup>
19+
<LocalFSharpBuildBinPath>$(MSBuildThisFileDirectory)../../../../artifacts/bin/fsc/Release/net9.0</LocalFSharpBuildBinPath>
20+
<FSharpBuildAssemblyFile>$(LocalFSharpBuildBinPath)/FSharp.Build.dll</FSharpBuildAssemblyFile>
21+
<DotnetFscCompilerPath>$(LocalFSharpBuildBinPath)/fsc.dll</DotnetFscCompilerPath>
22+
<Fsc_DotNET_DotnetFscCompilerPath>$(LocalFSharpBuildBinPath)/fsc.dll</Fsc_DotNET_DotnetFscCompilerPath>
23+
<FSharpPreferNetFrameworkTools>False</FSharpPreferNetFrameworkTools>
24+
<FSharpPrefer64BitTools>True</FSharpPrefer64BitTools>
25+
</PropertyGroup>
26+
27+
<UsingTask TaskName="GenerateILLinkSubstitutions" AssemblyFile="$(FSharpBuildAssemblyFile)" Override="true" />
28+
29+
<Target Name="GenerateFSharpILLinkSubstitutions" BeforeTargets="AssignTargetPaths">
30+
<GenerateILLinkSubstitutions
31+
AssemblyName="$(AssemblyName)"
32+
IntermediateOutputPath="$(IntermediateOutputPath)">
33+
<Output TaskParameter="GeneratedItems" ItemName="EmbeddedResource" />
34+
</GenerateILLinkSubstitutions>
35+
</Target>
36+
37+
<ItemGroup>
38+
<Compile Include="..\Program.fs" />
39+
</ItemGroup>
40+
41+
<Import Project="$(MSBuildThisFileDirectory)../../../../eng/Versions.props" />
42+
43+
<ItemGroup>
44+
<PackageReference Include="FSharp.Core" Version="$(FSharpCorePreviewPackageVersionValue)"/>
45+
</ItemGroup>
46+
47+
</Project>

tests/AheadOfTime/Trimming/check.ps1

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
function CheckTrim($root, $tfm, $outputfile, $expected_len) {
22
Write-Host "Publish and Execute: ${tfm} - ${root}"
3-
Write-Host "Expecting ${expected_len}"
3+
Write-Host "Expecting ${expected_len} for ${outputfile}"
44

55
$cwd = Get-Location
66
Set-Location (Join-Path $PSScriptRoot "${root}")
@@ -33,17 +33,30 @@ function CheckTrim($root, $tfm, $outputfile, $expected_len) {
3333
# Checking that the trimmed outputfile binary is of expected size (needs adjustments if test is updated).
3434
$file = Get-Item (Join-Path $PSScriptRoot "${root}\bin\release\${tfm}\win-x64\publish\${outputfile}")
3535
$file_len = $file.Length
36-
if (-not ($expected_len -eq -1 -or $file_len -eq $expected_len))
36+
if ($expected_len -eq -1)
37+
{
38+
Write-Host "Actual ${tfm} - trimmed ${outputfile} length: ${file_len} Bytes (expected length is placeholder -1, update test with this actual value)"
39+
}
40+
elseif ($file_len -ne $expected_len)
3741
{
3842
Write-Error "Test failed with unexpected ${tfm} - trimmed ${outputfile} length:`nExpected:`n`t${expected_len} Bytes`nActual:`n`t${file_len} Bytes`nEither codegen or trimming logic have changed. Please investigate and update expected dll size or report an issue." -ErrorAction Stop
3943
}
44+
45+
$fileBeforePublish = Get-Item (Join-Path $PSScriptRoot "${root}\bin\release\${tfm}\win-x64\${outputfile}")
46+
$sizeBeforePublish = $fileBeforePublish.Length
47+
$sizeDiff = $sizeBeforePublish - $file_len
48+
Write-Host "Size of ${tfm} - ${outputfile} before publish: ${sizeBeforePublish} Bytes, which means the diff is ${sizeDiff} Bytes"
4049
}
4150

4251
# NOTE: Trimming now errors out on desktop TFMs, as shown below:
4352
# error NETSDK1124: Trimming assemblies requires .NET Core 3.0 or higher.
4453

45-
# Check net7.0 trimmed assemblies
54+
# Check net9.0 trimmed assemblies
4655
CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outputfile "FSharp.Core.dll" -expected_len 300032
4756

48-
# Check net8.0 trimmed assemblies
57+
# Check net9.0 trimmed assemblies with static linked FSharpCore
4958
CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9154048
59+
60+
# Check net9.0 trimmed assemblies with F# metadata resources removed
61+
CheckTrim -root "FSharpMetadataResource_Trimming_Test" -tfm "net9.0" -outputfile "FSharpMetadataResource_Trimming_Test.dll" -expected_len 7601152
62+

0 commit comments

Comments
 (0)