Skip to content
Merged
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
3 changes: 1 addition & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@
<!-- CA1852: Seal types (not applicable for test base classes) -->
<!-- CA2007: ConfigureAwait (not needed for console applications and tests) -->
<!-- RCS1102: Make class static (test classes with static methods are discovered by Fixie) -->
<!-- IL2026, IL2067, IL2070, IL2075, IL3050, IL2104, IL3053: AOT warnings (not yet implemented) -->
<NoWarn>$(NoWarn);CA1014;CA1031;CA1052;CA1515;CA1707;CA1724;CA1812;CA1848;CA1852;CA2007;RCS1102;IL2026;IL2067;IL2070;IL2075;IL3050;IL2104;IL3053</NoWarn>
<NoWarn>$(NoWarn);CA1014;CA1031;CA1052;CA1515;CA1707;CA1724;CA1812;CA1848;CA1852;CA2007;RCS1102</NoWarn>
</PropertyGroup>

<!-- Code analyzers applied to all projects -->
Expand Down
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup Label="Microsoft Extensions">
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.10" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.10" />
Expand Down
66 changes: 66 additions & 0 deletions kanban/done/002-fix-aot-trim-warnings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Fix AOT and Trim Warnings

## Summary

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.

**GitHub Issue:** #7

## Todo List

- [x] Identify reflection usage causing trim warnings
- [x] Add `[DynamicallyAccessedMembers]` attributes where needed
- [x] Consider source generators for reflection-based binding
- [x] Add `<IsAotCompatible>true</IsAotCompatible>` to project
- [x] Add `<IsTrimmable>true</IsTrimmable>` to project
- [x] Test with AOT publish to verify zero warnings
- [ ] Update package version

## Notes

**Current warnings:**
```
TimeWarp.OptionsValidation.dll : warning IL2104: Assembly 'TimeWarp.OptionsValidation' produced trim warnings.
TimeWarp.OptionsValidation.dll : warning IL3053: Assembly 'TimeWarp.OptionsValidation' produced AOT analysis warnings.
```

**Reproduction steps:**
1. Create a .NET 10 application using `TimeWarp.OptionsValidation`
2. Use `AddFluentValidatedOptions<TOptions, TValidator>(config)`
3. Publish with AOT: `dotnet publish -p:PublishAot=true`

**Package Version:** 1.0.0-beta.3

**Related:** TimeWarp.Nuru task 018-make-configuration-validation-sample-aot-compatible

## Results

### Changes Made:

**1. `service-collection-extensions.cs`**
- Added `using System.Diagnostics.CodeAnalysis;`
- Added `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]` to `TOptions` on the configuration binding overload (required by `Bind<TOptions>()`)
- Added `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]` to `TValidator` on both overloads

**2. `options-builder-extensions.cs`**
- Added `using System.Diagnostics.CodeAnalysis;`
- Added `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]` to `TValidator` (required by `TryAddSingleton<TValidator>()`)

**3. `timewarp-options-validation.csproj`**
- Added `<IsAotCompatible>true</IsAotCompatible>`
- Added `<IsTrimmable>true</IsTrimmable>`

**4. New AOT test project** (`tests/aot-test/`)
- Created test project that publishes with AOT to verify no trim/AOT warnings

### Test Results:

| Test | Result |
|------|--------|
| `dotnet build -warnaserror` | ✅ 0 warnings, 0 errors |
| `dotnet test` | ✅ Passed: 4, Skipped: 1 |
| `dotnet publish -c Release` (AOT) | ✅ 0 IL2104/IL3053 warnings |
| Run AOT binary | ✅ Output: "Name: Test, Value: 42" |

### Decision:
Source generators were not needed - the `[DynamicallyAccessedMembers]` attributes were sufficient to eliminate all warnings while maintaining full AOT compatibility.
2 changes: 1 addition & 1 deletion source/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<!-- Default package metadata (can be overridden in individual projects) -->
<PropertyGroup Label="Package Metadata">
<PackageVersion>1.0.0-beta.3</PackageVersion>
<PackageVersion>1.0.0-beta.4</PackageVersion>
<Authors>Steven T. Cramer</Authors>
<Company>TimeWarp Enterprises</Company>
<Product>TimeWarp.OptionsValidation</Product>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Microsoft.Extensions.Options;

using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using FluentValidation;
Expand All @@ -26,7 +27,7 @@ public static class OptionsBuilderExtensions
/// .ValidateOnStart();
/// </code>
/// </example>
public static OptionsBuilder<TOptions> ValidateFluentValidation<TOptions, TValidator>(
public static OptionsBuilder<TOptions> ValidateFluentValidation<TOptions, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidator>(
this OptionsBuilder<TOptions> optionsBuilder)
where TOptions : class
where TValidator : AbstractValidator<TOptions>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Extensions.DependencyInjection;

using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Options;

public static class ServiceCollectionExtensions
Expand Down Expand Up @@ -34,7 +35,9 @@ public static class ServiceCollectionExtensions
/// .ValidateOnStart();
/// </code>
/// </example>
public static OptionsBuilder<TOptions> AddFluentValidatedOptions<TOptions, TValidator>(
[RequiresUnreferencedCode("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")]
[RequiresDynamicCode("Binding strongly typed objects to configuration values may require generating dynamic code at runtime.")]
public static OptionsBuilder<TOptions> AddFluentValidatedOptions<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidator>(
this IServiceCollection services,
IConfiguration configuration)
where TOptions : class
Expand Down Expand Up @@ -71,7 +74,7 @@ public static OptionsBuilder<TOptions> AddFluentValidatedOptions<TOptions, TVali
/// .ValidateOnStart();
/// </code>
/// </example>
public static OptionsBuilder<TOptions> AddFluentValidatedOptions<TOptions, TValidator>(
public static OptionsBuilder<TOptions> AddFluentValidatedOptions<TOptions, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TValidator>(
this IServiceCollection services,
Action<TOptions> configureOptions)
where TOptions : class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<PropertyGroup>
<AssemblyName>TimeWarp.OptionsValidation</AssemblyName>
<RootNamespace>TimeWarp.OptionsValidation</RootNamespace>
<IsAotCompatible>true</IsAotCompatible>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>

<ItemGroup>
Expand Down
18 changes: 18 additions & 0 deletions tests/aot-test/aot-test.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<ProjectReference Include="../../source/timewarp-options-validation/timewarp-options-validation.csproj" />
</ItemGroup>

</Project>
48 changes: 48 additions & 0 deletions tests/aot-test/program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using FluentValidation;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using TimeWarp.OptionsValidation;

namespace AotTest;

internal static class Program
{
public static void Main()
{
// Create configuration
IConfigurationRoot configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
["TestOptions:Name"] = "Test",
["TestOptions:Value"] = "42"
})
.Build();

// Setup DI with options validation
ServiceCollection services = new();
services.AddFluentValidatedOptions<TestOptions, TestOptionsValidator>(configuration)
.ValidateOnStart();

ServiceProvider serviceProvider = services.BuildServiceProvider();
IOptions<TestOptions> options = serviceProvider.GetRequiredService<IOptions<TestOptions>>();

Console.WriteLine($"Name: {options.Value.Name}, Value: {options.Value.Value}");
}
}

[ConfigurationKey("TestOptions")]
internal sealed class TestOptions
{
public string Name { get; set; } = string.Empty;
public int Value { get; set; }
}

internal sealed class TestOptionsValidator : AbstractValidator<TestOptions>
{
public TestOptionsValidator()
{
RuleFor(x => x.Name).NotEmpty();
RuleFor(x => x.Value).GreaterThan(0);
}
}