Skip to content

Commit fa7f887

Browse files
Merge pull request #9 from TimeWarpEngineering/Cramer/2025-10-25/dev
Fix AOT and trim warnings (Issue #7)
2 parents 736628d + ac9d09b commit fa7f887

9 files changed

Lines changed: 146 additions & 7 deletions

File tree

Directory.Build.props

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@
6666
<!-- CA1852: Seal types (not applicable for test base classes) -->
6767
<!-- CA2007: ConfigureAwait (not needed for console applications and tests) -->
6868
<!-- RCS1102: Make class static (test classes with static methods are discovered by Fixie) -->
69-
<!-- IL2026, IL2067, IL2070, IL2075, IL3050, IL2104, IL3053: AOT warnings (not yet implemented) -->
70-
<NoWarn>$(NoWarn);CA1014;CA1031;CA1052;CA1515;CA1707;CA1724;CA1812;CA1848;CA1852;CA2007;RCS1102;IL2026;IL2067;IL2070;IL2075;IL3050;IL2104;IL3053</NoWarn>
69+
<NoWarn>$(NoWarn);CA1014;CA1031;CA1052;CA1515;CA1707;CA1724;CA1812;CA1848;CA1852;CA2007;RCS1102</NoWarn>
7170
</PropertyGroup>
7271

7372
<!-- Code analyzers applied to all projects -->

Directory.Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
55
</PropertyGroup>
66
<ItemGroup Label="Microsoft Extensions">
7+
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="9.0.10" />
78
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.10" />
9+
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.10" />
810
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
911
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.10" />
1012
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.10" />
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Fix AOT and Trim Warnings
2+
3+
## Summary
4+
5+
Package produces IL2104/IL3053 trim and AOT analysis warnings when used in applications published with `PublishAot=true`. The package needs to be made fully AOT-compatible.
6+
7+
**GitHub Issue:** #7
8+
9+
## Todo List
10+
11+
- [x] Identify reflection usage causing trim warnings
12+
- [x] Add `[DynamicallyAccessedMembers]` attributes where needed
13+
- [x] Consider source generators for reflection-based binding
14+
- [x] Add `<IsAotCompatible>true</IsAotCompatible>` to project
15+
- [x] Add `<IsTrimmable>true</IsTrimmable>` to project
16+
- [x] Test with AOT publish to verify zero warnings
17+
- [ ] Update package version
18+
19+
## Notes
20+
21+
**Current warnings:**
22+
```
23+
TimeWarp.OptionsValidation.dll : warning IL2104: Assembly 'TimeWarp.OptionsValidation' produced trim warnings.
24+
TimeWarp.OptionsValidation.dll : warning IL3053: Assembly 'TimeWarp.OptionsValidation' produced AOT analysis warnings.
25+
```
26+
27+
**Reproduction steps:**
28+
1. Create a .NET 10 application using `TimeWarp.OptionsValidation`
29+
2. Use `AddFluentValidatedOptions<TOptions, TValidator>(config)`
30+
3. Publish with AOT: `dotnet publish -p:PublishAot=true`
31+
32+
**Package Version:** 1.0.0-beta.3
33+
34+
**Related:** TimeWarp.Nuru task 018-make-configuration-validation-sample-aot-compatible
35+
36+
## Results
37+
38+
### Changes Made:
39+
40+
**1. `service-collection-extensions.cs`**
41+
- Added `using System.Diagnostics.CodeAnalysis;`
42+
- Added `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]` to `TOptions` on the configuration binding overload (required by `Bind<TOptions>()`)
43+
- Added `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]` to `TValidator` on both overloads
44+
45+
**2. `options-builder-extensions.cs`**
46+
- Added `using System.Diagnostics.CodeAnalysis;`
47+
- Added `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]` to `TValidator` (required by `TryAddSingleton<TValidator>()`)
48+
49+
**3. `timewarp-options-validation.csproj`**
50+
- Added `<IsAotCompatible>true</IsAotCompatible>`
51+
- Added `<IsTrimmable>true</IsTrimmable>`
52+
53+
**4. New AOT test project** (`tests/aot-test/`)
54+
- Created test project that publishes with AOT to verify no trim/AOT warnings
55+
56+
### Test Results:
57+
58+
| Test | Result |
59+
|------|--------|
60+
| `dotnet build -warnaserror` | ✅ 0 warnings, 0 errors |
61+
| `dotnet test` | ✅ Passed: 4, Skipped: 1 |
62+
| `dotnet publish -c Release` (AOT) | ✅ 0 IL2104/IL3053 warnings |
63+
| Run AOT binary | ✅ Output: "Name: Test, Value: 42" |
64+
65+
### Decision:
66+
Source generators were not needed - the `[DynamicallyAccessedMembers]` attributes were sufficient to eliminate all warnings while maintaining full AOT compatibility.

source/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<!-- Default package metadata (can be overridden in individual projects) -->
66
<PropertyGroup Label="Package Metadata">
7-
<PackageVersion>1.0.0-beta.3</PackageVersion>
7+
<PackageVersion>1.0.0-beta.4</PackageVersion>
88
<Authors>Steven T. Cramer</Authors>
99
<Company>TimeWarp Enterprises</Company>
1010
<Product>TimeWarp.OptionsValidation</Product>

source/timewarp-options-validation/extensions/options-builder-extensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace Microsoft.Extensions.Options;
22

3+
using System.Diagnostics.CodeAnalysis;
34
using Microsoft.Extensions.DependencyInjection;
45
using Microsoft.Extensions.DependencyInjection.Extensions;
56
using FluentValidation;
@@ -26,7 +27,7 @@ public static class OptionsBuilderExtensions
2627
/// .ValidateOnStart();
2728
/// </code>
2829
/// </example>
29-
public static OptionsBuilder<TOptions> ValidateFluentValidation<TOptions, TValidator>(
30+
public static OptionsBuilder<TOptions> ValidateFluentValidation<TOptions, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidator>(
3031
this OptionsBuilder<TOptions> optionsBuilder)
3132
where TOptions : class
3233
where TValidator : AbstractValidator<TOptions>

source/timewarp-options-validation/extensions/service-collection-extensions.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
namespace Microsoft.Extensions.DependencyInjection;
1+
namespace Microsoft.Extensions.DependencyInjection;
22

3+
using System.Diagnostics.CodeAnalysis;
34
using Microsoft.Extensions.Options;
45

56
public static class ServiceCollectionExtensions
@@ -34,7 +35,9 @@ public static class ServiceCollectionExtensions
3435
/// .ValidateOnStart();
3536
/// </code>
3637
/// </example>
37-
public static OptionsBuilder<TOptions> AddFluentValidatedOptions<TOptions, TValidator>(
38+
[RequiresUnreferencedCode("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")]
39+
[RequiresDynamicCode("Binding strongly typed objects to configuration values may require generating dynamic code at runtime.")]
40+
public static OptionsBuilder<TOptions> AddFluentValidatedOptions<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidator>(
3841
this IServiceCollection services,
3942
IConfiguration configuration)
4043
where TOptions : class
@@ -71,7 +74,7 @@ public static OptionsBuilder<TOptions> AddFluentValidatedOptions<TOptions, TVali
7174
/// .ValidateOnStart();
7275
/// </code>
7376
/// </example>
74-
public static OptionsBuilder<TOptions> AddFluentValidatedOptions<TOptions, TValidator>(
77+
public static OptionsBuilder<TOptions> AddFluentValidatedOptions<TOptions, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidator>(
7578
this IServiceCollection services,
7679
Action<TOptions> configureOptions)
7780
where TOptions : class

source/timewarp-options-validation/timewarp-options-validation.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
<PropertyGroup>
44
<AssemblyName>TimeWarp.OptionsValidation</AssemblyName>
55
<RootNamespace>TimeWarp.OptionsValidation</RootNamespace>
6+
<IsAotCompatible>true</IsAotCompatible>
7+
<IsTrimmable>true</IsTrimmable>
68
</PropertyGroup>
79

810
<ItemGroup>

tests/aot-test/aot-test.csproj

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net10.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<PublishAot>true</PublishAot>
9+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="Microsoft.Extensions.Configuration" />
14+
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
15+
<ProjectReference Include="../../source/timewarp-options-validation/timewarp-options-validation.csproj" />
16+
</ItemGroup>
17+
18+
</Project>

tests/aot-test/program.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using FluentValidation;
2+
using Microsoft.Extensions.Configuration;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.Extensions.Options;
5+
using TimeWarp.OptionsValidation;
6+
7+
namespace AotTest;
8+
9+
internal static class Program
10+
{
11+
public static void Main()
12+
{
13+
// Create configuration
14+
IConfigurationRoot configuration = new ConfigurationBuilder()
15+
.AddInMemoryCollection(new Dictionary<string, string?>
16+
{
17+
["TestOptions:Name"] = "Test",
18+
["TestOptions:Value"] = "42"
19+
})
20+
.Build();
21+
22+
// Setup DI with options validation
23+
ServiceCollection services = new();
24+
services.AddFluentValidatedOptions<TestOptions, TestOptionsValidator>(configuration)
25+
.ValidateOnStart();
26+
27+
ServiceProvider serviceProvider = services.BuildServiceProvider();
28+
IOptions<TestOptions> options = serviceProvider.GetRequiredService<IOptions<TestOptions>>();
29+
30+
Console.WriteLine($"Name: {options.Value.Name}, Value: {options.Value.Value}");
31+
}
32+
}
33+
34+
[ConfigurationKey("TestOptions")]
35+
internal sealed class TestOptions
36+
{
37+
public string Name { get; set; } = string.Empty;
38+
public int Value { get; set; }
39+
}
40+
41+
internal sealed class TestOptionsValidator : AbstractValidator<TestOptions>
42+
{
43+
public TestOptionsValidator()
44+
{
45+
RuleFor(x => x.Name).NotEmpty();
46+
RuleFor(x => x.Value).GreaterThan(0);
47+
}
48+
}

0 commit comments

Comments
 (0)