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

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented May 22, 2025

Thanks for assigning this issue to me. I'm starting to work on it and will keep this PR's description up to date as I form a plan and make progress.

Original issue description:

Background

F# assemblies contain embedded resources for signature and optimization data that can significantly increase assembly size. These resource names are templates that get combined with the actual assembly name. To reduce the size of trimmed F# assemblies, we need to automatically remove these metadata resources during IL linking:
(the asterisk is not there, this is where the project name goes)

The full set of F#-compiler-generated prefixes currently is:
FSharpSignatureData.*
FSharpSignatureDataB.*
FSharpSignatureCompressedData.*
FSharpSignatureCompressedDataB.*

FSharpOptimizationData.*
FSharpOptimizationDataB.*
FSharpOptimizationCompressedData.*
FSharpOptimizationCompressedDataB.*

FSharpOptimizationInfo.*
FSharpSignatureInfo.*

Files to Modify

1. Create new MSBuild task: src/FSharp.Build/GenerateILLinkSubstitutions.fs

Create a new task that follows the pattern of existing embedded resource generators. Study how it creates TaskItem objects for embedded resources.

The task should:

  • Inherit from Microsoft.Build.Utilities.Task
  • Take the assembly name as input
  • Generate XML content with actual resource names (not patterns with asterisks)
  • Create the content in memory and add it as an EmbeddedResource item

Example of what the generated XML should contain (using actual assembly name):

<linker>
  <assembly fullname="MyProject">
    <resource name="FSharpOptimizationData.MyProject" action="remove" />
    <resource name="FSharpSignatureData.MyProject" action="remove" />
    <resource name="FSharpOptimizationCompressedData.MyProject" action="remove" />
    <resource name="FSharpSignatureCompressedData.MyProject" action="remove" />
    <resource name="FSharpOptimizationDataB.MyProject" action="remove" />
    <resource name="FSharpSignatureDataB.MyProject" action="remove" />
    <resource name="FSharpOptimizationCompressedDataB.MyProject" action="remove" />
    <resource name="FSharpSignatureCompressedDataB.MyProject" action="remove" />
    <resource name="FSharpOptimizationInfo.MyProject" action="remove" />
    <resource name="FSharpSignatureInfo.MyProject" action="remove" />
  </assembly>
</linker>

2. Update src/FSharp.Build/FSharp.Build.fsproj

Add the new task file to the compilation list: 3

<Compile Include="GenerateILLinkSubstitutions.fs" />

3. Add target to src/FSharp.Build/Microsoft.FSharp.NetSdk.targets

Create a new target that runs during every compilation. Looking at the existing props file structure, add:

<UsingTask TaskName="GenerateILLinkSubstitutions" AssemblyFile="$(FSharpBuildTasksAssembly)" />

<Target Name="GenerateFSharpILLinkSubstitutions" BeforeTargets="CoreCompile">
  <GenerateILLinkSubstitutions 
    AssemblyName="$(AssemblyName)"
    IntermediateOutputPath="$(IntermediateOutputPath)" />
</Target>

Implementation Steps

  1. Study the existing embedded resource pattern: Look at how it generates embedded resources programmatically.

  2. Understand resource name construction: The patterns are templates. You need to append the actual assembly name to create the full resource names (e.g., "FSharpOptimizationData." + assemblyName).

  3. Create the MSBuild task:

    • Follow the structure of existing tasks in src/FSharp.Build/
    • Generate XML content in memory
    • Create TaskItem with the XML content as an embedded resource
    • Set appropriate metadata like LogicalName="ILLink.Substitutions.xml"
    • Use action="remove" for all resource entries
  4. Wire into the build system:

    • Add the target to run during normal compilation
    • Use BeforeTargets="CoreCompile" to ensure it runs at the right time
    • Pass the assembly name from MSBuild properties
  5. ** Create tests for the behavior.

Look at the folder https://github.com/dotnet/fsharp/tree/main/tests/AheadOfTime/Trimming to see existing trimming tests.
You will need to adjust the test setup to make it use freshly built FSharp.Build.dll as well as the adjusted props and targets.

Notes

This approach generates the substitution file as an embedded resource during compilation, ensuring F# metadata resources are removed during IL linking to reduce the size of trimmed F# assemblies.

Fixes #18591.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@T-Gro
Copy link
Member

T-Gro commented May 22, 2025

@copilot please do it

Copy link
Contributor

✅ No release notes required

@T-Gro
Copy link
Member

T-Gro commented May 22, 2025

@copilot try now

@T-Gro
Copy link
Member

T-Gro commented May 23, 2025

@copilot try now

Copy link
Contributor Author

Copilot AI commented May 23, 2025

@copilot try now

I've implemented the requested task to auto-generate ILLink.Substitutions.xml for removing F# metadata resources during trimming. The implementation includes:

  1. A new GenerateILLinkSubstitutions.fs task that generates the XML with resource name patterns for the current assembly
  2. Updated FSharp.Build.fsproj to include this new file
  3. Added a target in Microsoft.FSharp.NetSdk.targets that runs the task during build

Changes are in commit 3aff8db. Next I'll be working on adding tests in the AheadOfTime/Trimming folder.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • x3yvsblobprodcus370.vsblob.vsassets.io
    • Triggering command: dotnet build src/FSharp.Build/FSharp.Build.fsproj (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

/// Assembly name to use when generating resource names to be removed.
/// </summary>
[<Required>]
member _.AssemblyName
Copy link
Member

Choose a reason for hiding this comment

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

Just a member val to have an F# auto-implemented property.

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 code to use F# auto-implemented properties with member val syntax for all the properties in the task. These changes can be seen in commit 74bce89.

Comment on lines 33 to 35
member _.IntermediateOutputPath
with get () = _intermediateOutputPath
and set value = _intermediateOutputPath <- value
Copy link
Member

Choose a reason for hiding this comment

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

Just a member val to have an F# auto-implemented property.

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 IntermediateOutputPath property to use member val syntax along with the other properties in commit 74bce89.

Comment on lines 41 to 43
member _.GeneratedItems
with get () = _generatedItems
and set value = _generatedItems <- value
Copy link
Member

Choose a reason for hiding this comment

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

Just a member val to have an F# auto-implemented property.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The GeneratedItems property has been updated to use member val syntax along with the other properties in commit 74bce89.

Comment on lines 48 to 59
let resourcePrefixes = [|
"FSharpSignatureData"
"FSharpSignatureDataB"
"FSharpSignatureCompressedData"
"FSharpSignatureCompressedDataB"
"FSharpOptimizationData"
"FSharpOptimizationDataB"
"FSharpOptimizationCompressedData"
"FSharpOptimizationCompressedDataB"
"FSharpOptimizationInfo"
"FSharpSignatureInfo"
|]
Copy link
Member

Choose a reason for hiding this comment

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

Use the F# array builder syntax to make it more apparent that those are combinations of:
Signature x Optimization
Compressed vs "" (blank)
Data vs DataB

and then yield the last two "..Info" special cases.

That way, it will be easier to see that indeed all combinations are covered.

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 refactored the resource prefixes array to use a more structured approach with F# comprehension expressions to clearly show all combinations of prefix types (Signature/Optimization), compression (""/Compressed), and data types (Data/DataB). This makes it clearer that all combinations are covered. The Info variants are now added separately as they follow a different pattern. These changes are in commit 74bce89.

|]

// Generate the XML content
let sb = StringBuilder()
Copy link
Member

Choose a reason for hiding this comment

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

The size can be pre-detrmined upfront, use an overload of StringBuilder that takes initial capacity.

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 code to pre-allocate the StringBuilder capacity by using the appropriate constructor overload with an initial capacity of 4096, which should be more than enough for our XML content. This change is in commit 74bce89.

Comment on lines 71 to 73
// Close all resource tags
for _ in resourcePrefixes do
sb.Append("</resource>") |> ignore
Copy link
Member

Choose a reason for hiding this comment

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

This is just wrong. Each resource should be closed on the same line it is opened. What you did would produce invalid XML.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're absolutely right. I've fixed the XML generation to properly close each resource tag on the same line it's opened, producing valid XML like: <resource name="..." action="remove"></resource>. This change is in commit 74bce89.

<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.

Copy link
Contributor Author

Copilot AI commented May 23, 2025

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • x3yvsblobprodcus370.vsblob.vsassets.io
    • Triggering command: dotnet build src/FSharp.Build/FSharp.Build.fsproj (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Comment on lines +29 to +34
/// <summary>
/// Generated embedded resource items.
/// </summary>
[<o>]
member val GeneratedItems = [| |] : ITaskItem[] with get, set

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: New
Development

Successfully merging this pull request may close these issues.

Auto-generate IlLink.Substitutions.xml to Remove F# Metadata Resources
2 participants