Skip to content

[WIP] Auto-generate IlLink.Substitutions.xml to Remove F# Metadata Resources #18592

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/FSharp.Build/FSharp.Build.fsproj
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. -->
<Project Sdk="Microsoft.NET.Sdk">
Expand Down Expand Up @@ -46,6 +46,7 @@
<Compile Include="CreateFSharpManifestResourceName.fs" />
<Compile Include="SubstituteText.fs" />
<Compile Include="MapSourceRoots.fs" />
<Compile Include="GenerateILLinkSubstitutions.fs" />
<None Include="Microsoft.FSharp.Targets" CopyToOutputDirectory="PreserveNewest" />
<None Include="Microsoft.Portable.FSharp.Targets" CopyToOutputDirectory="PreserveNewest" />
<None Include="Microsoft.FSharp.NetSdk.props" CopyToOutputDirectory="PreserveNewest" />
Expand Down
84 changes: 84 additions & 0 deletions src/FSharp.Build/GenerateILLinkSubstitutions.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace FSharp.Build

open System
open System.IO
open System.Text
open Microsoft.Build.Framework
open Microsoft.Build.Utilities

/// <summary>
/// MSBuild task that generates ILLink.Substitutions.xml file to remove F# metadata resources during IL linking.
/// </summary>
type GenerateILLinkSubstitutions() =
inherit Task()

/// <summary>
/// Assembly name to use when generating resource names to be removed.
/// </summary>
[<Required>]
member val AssemblyName = "" with get, set

/// <summary>
/// Intermediate output path for storing the generated file.
/// </summary>
[<Required>]
member val IntermediateOutputPath = "" with get, set

/// <summary>
/// Generated embedded resource items.
/// </summary>
[<o>]

Check failure on line 32 in src/FSharp.Build/GenerateILLinkSubstitutions.fs

View workflow job for this annotation

GitHub Actions / copilot

The type 'o' is not defined.

Check failure on line 32 in src/FSharp.Build/GenerateILLinkSubstitutions.fs

View workflow job for this annotation

GitHub Actions / copilot

The type 'o' is not defined.

Check failure on line 32 in src/FSharp.Build/GenerateILLinkSubstitutions.fs

View workflow job for this annotation

GitHub Actions / copilot

The type 'o' is not defined.

Check failure on line 32 in src/FSharp.Build/GenerateILLinkSubstitutions.fs

View workflow job for this annotation

GitHub Actions / copilot

The type 'o' is not defined.

Check failure on line 32 in src/FSharp.Build/GenerateILLinkSubstitutions.fs

View workflow job for this annotation

GitHub Actions / copilot

The type 'o' is not defined.

Check failure on line 32 in src/FSharp.Build/GenerateILLinkSubstitutions.fs

View workflow job for this annotation

GitHub Actions / copilot

The type 'o' is not defined.

Check failure on line 32 in src/FSharp.Build/GenerateILLinkSubstitutions.fs

View workflow job for this annotation

GitHub Actions / copilot

The type 'o' is not defined.

Check failure on line 32 in src/FSharp.Build/GenerateILLinkSubstitutions.fs

View workflow job for this annotation

GitHub Actions / copilot

The type 'o' is not defined.
member val GeneratedItems = [| |] : ITaskItem[] with get, set

Comment on lines +29 to +34
Copy link
Member

Choose a reason for hiding this comment

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

Type o is not defined, this is wrong and causes a build failure.

Copy link
Member

Choose a reason for hiding this comment

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

override this.Execute() =
try
// Define the resource prefixes that need to be removed
let resourcePrefixes =
[|
// Signature variants
yield! [| for dataType in [| "Data"; "DataB" |] do
for compression in [| ""; "Compressed" |] do
yield $"FSharpSignature{compression}{dataType}" |]

// Optimization variants
yield! [| for dataType in [| "Data"; "DataB" |] do
for compression in [| ""; "Compressed" |] do
yield $"FSharpOptimization{compression}{dataType}" |]

// Info variants
yield "FSharpOptimizationInfo"
yield "FSharpSignatureInfo"
|]

// Generate the XML content
let sb = StringBuilder(4096) // pre-allocate capacity
sb.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>") |> ignore
sb.AppendLine("<linker>") |> ignore
sb.AppendLine($" <assembly fullname=\"{this.AssemblyName}\">") |> ignore

// Add each resource entry with proper closing tag on the same line
for prefix in resourcePrefixes do
sb.AppendLine($" <resource name=\"{prefix}.{this.AssemblyName}\" action=\"remove\"></resource>") |> ignore

// Close assembly and linker tags
sb.AppendLine(" </assembly>") |> ignore
sb.AppendLine("</linker>") |> ignore

let xmlContent = sb.ToString()

// Create a file in the intermediate output path
let outputFileName = Path.Combine(this.IntermediateOutputPath, "ILLink.Substitutions.xml")
Directory.CreateDirectory(this.IntermediateOutputPath) |> ignore
File.WriteAllText(outputFileName, xmlContent)

// Create a TaskItem for the generated file
let item = TaskItem(outputFileName) :> ITaskItem
item.SetMetadata("LogicalName", "ILLink.Substitutions.xml")

this.GeneratedItems <- [| item |]
true
with ex ->
this.Log.LogErrorFromException(ex, true)
false
12 changes: 12 additions & 0 deletions src/FSharp.Build/Microsoft.FSharp.NetSdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,18 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and
<SourceRootMappedPathsFeatureSupported>true</SourceRootMappedPathsFeatureSupported>
</PropertyGroup>

<!-- Register the GenerateILLinkSubstitutions task -->
<UsingTask TaskName="GenerateILLinkSubstitutions" AssemblyFile="$(FSharpBuildAssemblyFile)" />

<!-- Generate ILLink.Substitutions.xml to remove F# metadata resources during trimming -->
<Target Name="GenerateFSharpILLinkSubstitutions" BeforeTargets="CoreCompile">
<GenerateILLinkSubstitutions
AssemblyName="$(AssemblyName)"
IntermediateOutputPath="$(IntermediateOutputPath)">
<Output TaskParameter="GeneratedItems" ItemName="EmbeddedResource" />
</GenerateILLinkSubstitutions>
</Target>

<!--
If InitializeSourceControlInformation target isn't supported, we just continue without invoking that synchronization target.
We'll proceed with SourceRoot (and other source control properties) provided by the user (or blank).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net9.0</TargetFrameworks>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<PropertyGroup>
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
<DisableImplicitLibraryPacksFolder>true</DisableImplicitLibraryPacksFolder>
<PublishTrimmed>true</PublishTrimmed>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>

<PropertyGroup>
Copy link
Member

Choose a reason for hiding this comment

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

You also need to make sure that the new targets and freshly build FSharpBuildAssemblyFile is used.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've updated the test project to explicitly set the FSharpBuildAssemblyFile property to point to the freshly built FSharp.Build.dll, and added imports for the custom targets files directly from the repository to ensure it uses the latest version with our new task. These changes are in commit 74bce89.

<DotnetFscCompilerPath>$(MSBuildThisFileDirectory)../../../../artifacts/bin/fsc/Release/net9.0/fsc.dll</DotnetFscCompilerPath>
<Fsc_DotNET_DotnetFscCompilerPath>$(MSBuildThisFileDirectory)../../../../artifacts/bin/fsc/Release/net9.0/fsc.dll</Fsc_DotNET_DotnetFscCompilerPath>
<FSharpBuildAssemblyFile>$(MSBuildThisFileDirectory)../../../../artifacts/bin/FSharp.Build/Release/net9.0/FSharp.Build.dll</FSharpBuildAssemblyFile>
<FSharpPreferNetFrameworkTools>False</FSharpPreferNetFrameworkTools>
<FSharpPrefer64BitTools>True</FSharpPrefer64BitTools>
</PropertyGroup>

<!-- Import the custom targets file directly from the repository to use the latest version -->
<Import Project="$(MSBuildThisFileDirectory)../../../../src/FSharp.Build/Microsoft.FSharp.NetSdk.props" />
<Import Project="$(MSBuildThisFileDirectory)../../../../src/FSharp.Build/Microsoft.FSharp.NetSdk.targets" />

<ItemGroup>
<Compile Include="..\Program.fs" />
</ItemGroup>

<Import Project="$(MSBuildThisFileDirectory)../../../../eng/Versions.props" />

<ItemGroup>
<PackageReference Include="FSharp.Core" Version="$(FSharpCorePreviewPackageVersionValue)"/>
</ItemGroup>

</Project>
7 changes: 5 additions & 2 deletions tests/AheadOfTime/Trimming/check.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,11 @@ function CheckTrim($root, $tfm, $outputfile, $expected_len) {
# NOTE: Trimming now errors out on desktop TFMs, as shown below:
# error NETSDK1124: Trimming assemblies requires .NET Core 3.0 or higher.

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

# Check net8.0 trimmed assemblies
# Check net9.0 trimmed assemblies with static linked FSharpCore
CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9150976

# Check net9.0 trimmed assemblies with F# metadata resources removed
CheckTrim -root "FSharpMetadataResource_Trimming_Test" -tfm "net9.0" -outputfile "FSharpMetadataResource_Trimming_Test.dll" -expected_len -1
Loading