Skip to content

Generate built-in format registration for Native AOT - #77

Merged
Olof-Lagerkvist merged 4 commits into
LTRData.DiscUtils-initialfrom
feature/generated-format-registration
Sep 7, 2026
Merged

Generate built-in format registration for Native AOT#77
Olof-Lagerkvist merged 4 commits into
LTRData.DiscUtils-initialfrom
feature/generated-format-registration

Conversation

@Olof-Lagerkvist

@Olof-Lagerkvist Olof-Lagerkvist commented Sep 7, 2026

Copy link
Copy Markdown
Member

Registration is explicit and local to each format assembly. This refinement removes registration inferred from assembly references: an implementation dependency no longer changes which providers Formats.Register() activates. Existing setup helpers compose the appropriate libraries explicitly, preserving their original provider selection and assembly order.

Applications can register individual libraries:

DiscUtils.Core.Formats.Register(); // Core providers, including the file transport
DiscUtils.Vhd.Formats.Register();
DiscUtils.Fat.Formats.Register();

Or use the existing SetupComplete(), SetupContainers(), SetupFileSystems(), SetupTransports() or ExFAT setup helper. Helpers register Core first. Individual generated entry points contain no dependency calls; no dependency exception was necessary. Core's existing lazy partition/volume defaults and the reflection helper's Core initialization remain.

Generated namespaces are now derived from the assembly name using identifier-safe sanitization of each dot-separated segment. Invalid identifier characters become _; leading digits and reserved C# keywords receive an _ prefix, and empty segments become _. Valid identifier characters and contextual keywords usable as namespace identifiers are preserved. For example, Acme.DiscUtils-Plugin exposes Acme.DiscUtils_Plugin.Formats.Register() and 123.Tools exposes _123.Tools.Formats.Register(). Only the generated C# namespace/type name changes: typeof(Formats).Assembly still passes the actual runtime assembly to the guard. No assembly/package rename, hashing or collision detection is introduced.

The incremental generator keeps the five existing discovery attributes as its source of truth. Generated methods contain the common assembly guard and deterministic direct constructors/delegates for local providers. There is no reflection, Activator, module initializer or separate generated initialization cache. The generator no longer inspects referenced assemblies for registration entry points.

The library-wide opt-in remains for low maintenance. Projects without discovery attributes, including meta-packages, emit no registration code. Meta-packages expose their existing setup helpers. Core ships no registration analyzer or buildTransitive machinery. Applications need no DiscUtils generator, registration-related Roslyn version or C# 9 features; DUAOT002 and consumer/module-initializer generation remain removed.

Both third-party paths remain supported: normal JIT applications can discover attributed plugins with DiscUtils.Setup.SetupHelper.RegisterAssembly(assembly), while external libraries can expose handwritten registration or build their own MyLibrary.Formats.Register(). Applications explicitly call the compiled entry point. Reflection discovery retains RequiresUnreferencedCode annotations and uses the same extensible registries. DiskImageBuilder shares VirtualDiskManager's registry.

The public transport base/attribute/delegate registration and logical-volume factory base/attribute/instance registration are intentional supported extension points. Documentation now says so explicitly, including the public LogicalVolumeInfo constructor for external mappings. Partition-table factory APIs remain internal.

The once-per-assembly guard retains legacy failure semantics: it marks the assembly before invoking the callback. Repeated and recursive calls are ignored. If a callback throws, its exception propagates, partial registrations remain, and subsequent explicit or assembly-level reflection calls do not retry it. Focused tests now cover repeated/concurrent success, callback reentrance, generated-then-reflection, reflection-then-explicit, partial failure and attempts after failure.

Disk types, extensions and transport schemes remain case insensitive; conflict handling and file-system/volume registry ordering are unchanged. Third-party providers can still be added after built-in setup. See the updated registration documentation for initialization, extension and ordering details.

Validation on Ubuntu 24.04 x64, .NET SDK 10.0.100 and GCC 13.3. The full solution build and generator suite were rerun for the final namespace-only refinement; runtime and Native AOT results below are from the preceding registration revision:

  • Full solution build across all existing target frameworks: 0 warnings, 0 errors.
  • Registration tests: 19 passed, including the generated private-library fixture and reflection-only plugin without generator/friend access.
  • Source-generator tests: 21 passed. Ten namespace cases cover normal names, hyphens, leading digits, other invalid characters, reserved/contextual keywords, empty segments and Unicode identifiers. Each emitted assembly compiles in C# 7.3, invokes its generated entry point with the original runtime assembly identity and has deterministic output when input order changes. Assembly-local output and reference-only opt-in behavior remain covered.
  • Existing library tests: 566 passed, 19 skipped; verified with a TRX result file.
  • Direct and SetupContainers() smoke paths passed in separate processes under both JIT and Native AOT. Native publish produced no trimming/AOT warnings.
  • The direct smoke proves that VHD registration activates only VHD before Core is explicitly registered. The helper smoke calls setup twice and verifies that the unrelated optical-disc-sharing scheme remains available for third-party registration.
  • Standalone package smoke used 15 freshly packed libraries outside the repository and a fresh DiscUtils package cache. It exercises VHD create/open, mixed-case extension lookup, FAT detect/open, partition/volume discovery, image builders and a handwritten transport.
  • A private library compiled in C# 7.3 for net46 and netstandard2.0 using the optional generator. Its DLL-only consumer verified explicit, once-only registration under JIT and Native AOT. A C# 7.3/.NET Framework 4.6 consumer compiled with warnings treated as errors. Consumer builds explicitly reject a DiscUtils analyzer.
  • Separate Core-only processes verified lazy partition and volume defaults, RAW extension lookup and file transport without explicit setup.
  • The final namespace-only refinement leaves representative VHD, FAT, LVM, NFS and OpticalDisk generated source byte-for-byte unchanged. Inspected generated Core, VHD, FAT, LVM, NFS, OpticalDisk and private-library source. Inspected the packed dependencies for analyzer/buildTransitive assets and generator/Roslyn dependencies: none. git diff --check passed.

Build/test commands:

dotnet build DiscUtils.slnx -c Debug --no-restore -m:1 -nr:false \
  -p:BuildInParallel=false -p:UseSharedCompilation=false \
  -p:EnableWindowsTargeting=true -p:EmitCompilerGeneratedFiles=true
dotnet test Tests/RegistrationTests/RegistrationTests.csproj -c Debug -f net10.0 --no-build --no-restore -m:1
dotnet test Tests/SourceGeneratorTests/SourceGeneratorTests.csproj -c Debug -f net10.0 --no-restore -m:1 -nr:false \
  -p:BuildInParallel=false -p:UseSharedCompilation=false
dotnet test Tests/LibraryTests/LibraryTests.csproj -c Debug -f net10.0 --no-build --no-restore -m:1 --logger trx

The standalone package smoke was published with -c Release -r linux-x64 -p:UsePackageReferences=true -p:DiscUtilsPackageVersion=1.0.88 -p:CppCompilerAndLinker=gcc, then run both without arguments and with --containers. The existing scratch-only workaround ran SDK ComputeManagedAssemblies and ILLink tasks in-process via UsingTask Override="true"; no SDK or repository workaround changes are committed.

Native execution covers representative VHD/FAT/partition/volume operations and registration of the container providers; it does not exercise every container format or actual LVM/dynamic-disk layouts. .NET Framework targets were compiled, not executed on this Linux host. Unrelated protocol reflection and implementation-specific trimming/native interop remain outside this registration change.

Existing package IDs, versions, target frameworks, CI/release workflows and repository metadata are unchanged. Target remains LTRData.DiscUtils-initial. This updates PR #77 for manual review and further edits; it is not merged.

Compose providers explicitly in the existing setup helpers, preserve and test the assembly guard's failure semantics, and cover direct and setup-helper registration under Native AOT. Document the supported transport and logical-volume extension points.
Derive valid C# namespace segments from assembly names while preserving the runtime assembly identity. Cover identifier characters, keywords, deterministic output and C# 7.3 compilation, and document the mapping.
@Olof-Lagerkvist
Olof-Lagerkvist merged commit 7ff2eaf into LTRData.DiscUtils-initial Sep 7, 2026
9 checks passed
@Olof-Lagerkvist
Olof-Lagerkvist deleted the feature/generated-format-registration branch September 7, 2026 19:54
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.

1 participant