Skip to content

fix(core): eliminate all AOT-incompatible patterns for Native AOT support#523

Merged
JusterZhu merged 2 commits into
masterfrom
release/10.5.0-beta.3
Jun 19, 2026
Merged

fix(core): eliminate all AOT-incompatible patterns for Native AOT support#523
JusterZhu merged 2 commits into
masterfrom
release/10.5.0-beta.3

Conversation

@JusterZhu

Copy link
Copy Markdown
Collaborator

Summary

Eliminate all AOT/trimming-incompatible patterns in GeneralUpdate.Core so the library can be published with Native AOT (dotnet publish -aot).

Changes

🔴 Reflection activation eliminated

  • AbstractBootstrap — Replaced Activator.CreateInstance with new T() factory delegates (_extensionFactories) across all 11 extension registration methods. Added parallel _extensionFactories dictionary while preserving _extensions for type metadata queries.
  • GeneralUpdateBootstrap.ConfigureStrategy — Eliminated ConstructorInfo.Invoke + Activator.CreateInstance for IDownloadPipeline; replaced with ResolveExtension<IDownloadPipeline>().

🔴 JSON serialization reflection eliminated

  • New file: JsonContext/UpdateReportJsonContext.cs — Source-generated JSON context for UpdateReport with camelCase policy.
  • HttpUpdateReporter.ReportAsync — Uses UpdateReportJsonContext.Default.UpdateReport instead of JsonSerializerOptions.
  • UpdateRequestBuilder.LoadFromConfigFile — Uses HttpParameterJsonContext.Default.UpdateRequest instead of reflection-based JsonSerializerOptions.

🟡 Reflection fallback paths hardened

  • StorageManager.CreateJson/GetJsonJsonTypeInfo<T> changed from nullable (with reflection fallback) to required parameter.
  • VersionService.SendAsync/PostAsyncJsonTypeInfo<T> changed from nullable to required parameter.

Verification

  • dotnet build0 errors across all targets (netstandard2.0/net8.0/net10.0)
  • 0 IL2026/IL3050 trim/AOT warnings remaining
  • All existing callers already passed JsonTypeInfo — no breaking changes to public API

Files changed

File +/- Change
Configuration/AbstractBootstrap.cs +28/-15 Factory delegate pattern for extensions
Configuration/UpdateRequestBuilder.cs +1/-4 Source-gen JSON deserialization
Download/Reporting/IUpdateReporter.cs +2/-1 Source-gen JSON serialization
FileSystem/StorageManager.cs +14/-17 Required JsonTypeInfo parameter
Network/VersionService.cs +7/-7 Required JsonTypeInfo parameter
JsonContext/UpdateReportJsonContext.cs New UpdateReport source-gen context

🤖 Generated with Claude Code

- Replace Activator.CreateInstance with new T() factory delegates
  in AbstractBootstrap extension point resolution (11 sites)
- Replace ConstructorInfo.Invoke + Activator in
  GeneralUpdateBootstrap configure strategy with ResolveExtension
- Add UpdateReportJsonContext source-gen context for
  HttpUpdateReporter.ReportAsync serialization
- Switch UpdateRequestBuilder config loading to
  HttpParameterJsonContext.Default.UpdateRequest
- Make JsonTypeInfo<T> required (non-nullable) in StorageManager
  and VersionService to eliminate reflection fallback paths

Build verified: 0 errors, 0 IL2026/IL3050 trim warnings across all
target frameworks (netstandard2.0/net8.0/net10.0)

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 19, 2026 17:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR aims to make GeneralUpdate.Core publishable with Native AOT by removing reflection-based activation/serialization paths and switching to source-generated System.Text.Json metadata.

Changes:

  • Replaces extension activation patterns in bootstrap configuration with factory delegates to avoid reflection.
  • Introduces source-generated JSON context for UpdateReport and updates reporter/config parsing to use generated JsonTypeInfo.
  • Makes several JSON helper APIs require JsonTypeInfo to avoid reflection fallbacks.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/c#/GeneralUpdate.Core/Configuration/AbstractBootstrap.cs Adds extension factory delegates and updates resolution logic to avoid reflection.
src/c#/GeneralUpdate.Core/Configuration/UpdateRequestBuilder.cs Switches config deserialization to source-generated JSON metadata.
src/c#/GeneralUpdate.Core/Download/Reporting/IUpdateReporter.cs Switches reporting serialization to source-generated JSON metadata.
src/c#/GeneralUpdate.Core/FileSystem/StorageManager.cs Requires JsonTypeInfo for JSON IO helpers and removes reflection fallbacks.
src/c#/GeneralUpdate.Core/Network/VersionService.cs Requires JsonTypeInfo for response deserialization and removes reflection fallback.
src/c#/GeneralUpdate.Core/JsonContext/UpdateReportJsonContext.cs Adds a source-generated JSON context for UpdateReport.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 363 to 368
protected TExtension? ResolveExtension<TExtension>() where TExtension : class
{
if (_extensions.TryGetValue(typeof(TExtension), out var t))
return Activator.CreateInstance((Type)t) as TExtension;
if (_extensionFactories.TryGetValue(typeof(TExtension), out var factory))
return factory() as TExtension;
if (_instances.TryGetValue(typeof(TExtension), out var instance))
return instance as TExtension;
Comment on lines 148 to 163
/// <param name="typeInfo">JSON type info metadata for source generator serialization support. Required for Native AOT compatibility.</param>
/// <exception cref="ArgumentException">Thrown when <paramref name="targetPath"/> does not contain a valid directory path.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="typeInfo"/> is null.</exception>
/// <remarks>
/// If the directory of the target file does not exist, it will be created automatically.
/// Supports source generator mode via <c>JsonTypeInfo</c>, which avoids runtime reflection in AOT compilation scenarios.
/// </remarks>
public static void CreateJson<T>(string targetPath, T obj, JsonTypeInfo<T>? typeInfo = null) where T : class
public static void CreateJson<T>(string targetPath, T obj, JsonTypeInfo<T> typeInfo) where T : class
{
var folderPath = Path.GetDirectoryName(targetPath) ??
throw new ArgumentException("invalid path", nameof(targetPath));

if (!Directory.Exists(folderPath))
Directory.CreateDirectory(folderPath);

var jsonString = typeInfo != null ? JsonSerializer.Serialize(obj, typeInfo) : JsonSerializer.Serialize(obj);
var jsonString = JsonSerializer.Serialize(obj, typeInfo);
File.WriteAllText(targetPath, jsonString);
Comment on lines 171 to 184
/// <param name="path">The full path of the JSON file.</param>
/// <param name="typeInfo">Optional JSON type info metadata for source generator deserialization support.</param>
/// <param name="typeInfo">JSON type info metadata for source generator deserialization support. Required for Native AOT compatibility.</param>
/// <returns>The deserialized object instance; returns <c>default</c> if the file does not exist.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="typeInfo"/> is null.</exception>
/// <remarks>
/// If the file does not exist, no exception is thrown and <c>null</c> is returned.
/// Supports source generator mode via <c>JsonTypeInfo</c>.
/// </remarks>
public static T? GetJson<T>(string path, JsonTypeInfo<T>? typeInfo = null) where T : class
public static T? GetJson<T>(string path, JsonTypeInfo<T> typeInfo) where T : class
{
if (File.Exists(path))
{
var json = File.ReadAllText(path);
if (typeInfo != null)
{
return JsonSerializer.Deserialize(json, typeInfo);
}
return JsonSerializer.Deserialize<T>(json);
return JsonSerializer.Deserialize(json, typeInfo);
}
Comment on lines 138 to 142
}

var json = File.ReadAllText(configPath);
var config = JsonSerializer.Deserialize<UpdateRequest>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
var config = JsonSerializer.Deserialize(json, JsonContext.HttpParameterJsonContext.Default.UpdateRequest);

Comment on lines 356 to 360
var r = await _sharedClient.SendAsync(req, cts.Token).ConfigureAwait(false);
r.EnsureSuccessStatusCode();
var rj = await r.Content.ReadAsStringAsync().ConfigureAwait(false);
return ti == null ? JsonSerializer.Deserialize<T>(rj) : JsonSerializer.Deserialize(rj, ti);
return JsonSerializer.Deserialize(rj, ti);
}
- Swap ResolveExtension lookup order: _instances wins over _extensionFactories
  so singleton instances (e.g. HttpAuth(provider)) always take precedence
- Keep JsonTypeInfo<T> optional in StorageManager with null-guard at runtime
  for source compatibility while maintaining AOT safety
- Add UpdateRequestConfigJsonContext with case-insensitive matching to
  preserve existing update_config.json behavior under source-gen
- Guard against null JsonSerializer.Deserialize result in VersionService

Co-Authored-By: Claude <noreply@anthropic.com>
@JusterZhu
JusterZhu merged commit 171bde2 into master Jun 19, 2026
3 checks passed
@JusterZhu
JusterZhu deleted the release/10.5.0-beta.3 branch June 19, 2026 17:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants