fsharp icon indicating copy to clipboard operation
fsharp copied to clipboard

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

Open Copilot opened this issue 7 months ago • 8 comments

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.

Copilot avatar May 22 '25 12:05 Copilot

@copilot please do it

T-Gro avatar May 22 '25 14:05 T-Gro

:white_check_mark: No release notes required

github-actions[bot] avatar May 22 '25 14:05 github-actions[bot]

@copilot try now

T-Gro avatar May 22 '25 15:05 T-Gro

@copilot try now

T-Gro avatar May 23 '25 08:05 T-Gro

@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:

Copilot avatar May 23 '25 08:05 Copilot

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:

Copilot avatar May 23 '25 09:05 Copilot

@copilot :

The script check.ps1 reports an error, but it does not output the actual value. We need the actual value so that you can set it as the real expectation (I do realize that the value of -1 was just a placeholder).

Change the script so that it reports the actual values of sizes as well, and so that we can see them in build failures logs.

Right now the logs were just:

Publish and Execute: net9.0 - FSharpMetadataResource_Trimming_Test
Expecting -1
CheckTrim : Build failed with exit code 1
At D:\a\_work\1\s\tests\AheadOfTime\Trimming\check.ps1:52 char:1

T-Gro avatar Jun 03 '25 08:06 T-Gro

@copilot :

The script check.ps1 reports an error, but it does not output the actual value. We need the actual value so that you can set it as the real expectation (I do realize that the value o...

I've updated the check.ps1 script to report the actual file sizes when the expected length is the placeholder value -1. Now when the FSharpMetadataResource_Trimming_Test runs, it will output a message like "Actual net9.0 - trimmed FSharpMetadataResource_Trimming_Test.dll length: [actual_bytes] Bytes (expected length is placeholder -1, update test with this actual value)" so you can see the actual size in the build logs and update the test accordingly. Changes are in commit 28d99ca.

Copilot avatar Jun 03 '25 08:06 Copilot

/run fantomas

T-Gro avatar Jul 11 '25 16:07 T-Gro

🔧 CLI Command Report

  • Command: /run fantomas
  • Outcome: success

✅ Patch applied: - Files changed: 1 - Lines changed: 87

github-actions[bot] avatar Jul 11 '25 16:07 github-actions[bot]

The resource is present, change on app size covered by publishing tests the measure size in bytes.

image

T-Gro avatar Jul 28 '25 15:07 T-Gro