diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml index 206ae6f71..8b4ec41c7 100644 --- a/.github/workflows/publish-nuget.yml +++ b/.github/workflows/publish-nuget.yml @@ -1,9 +1,7 @@ name: NuGet push (tag) -on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' +on: + workflow_dispatch: env: DOTNET_CLI_TELEMETRY_OPTOUT: true @@ -11,22 +9,20 @@ env: jobs: build: - runs-on: windows-2019 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 + - name: Setup .NET Core + uses: actions/setup-dotnet@v5 with: - fetch-depth: 50 - lfs: 'true' - # We do not need to fetch tags, as we're already at a tagged build - it should be available automatically + dotnet-version: 10.0.x - - name: Setup .NET Core 7.0 - uses: actions/setup-dotnet@v1 - with: - dotnet-version: '7.0.x' + - name: Build + run: dotnet build -c Release - name: Pack - run: dotnet pack -c Release -o ${{ github.workspace }}/build + run: dotnet pack -c Release -o ${{ github.workspace }}/build --no-build - name: NuGet push run: dotnet nuget push *.nupkg --skip-duplicate -k ${{secrets.NUGET_KEY}} -s https://api.nuget.org/v3/index.json diff --git a/.gitignore b/.gitignore index b207fc71c..7d87a18bc 100644 --- a/.gitignore +++ b/.gitignore @@ -386,3 +386,19 @@ artifacts-dotnet-releaser/ # Verify *.received.* *# + +# Build outputs in Library folder +Library/net*/ +Library/netstandard*/ +net10.0/ + +# Build outputs in root +netstandard*/ + +# Build outputs in Tests folder +Tests/net*/ +Tests/netstandard*/ + +# Build outputs in Utilities folder +Utilities/net*/ +Utilities/netstandard*/ diff --git a/Directory.Build.props b/Directory.Build.props index 259856157..af4d4837d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,18 +6,20 @@ portable - https://github.com/LTRData/DiscUtils + https://github.com/Devedse/DiscUtils MIT git LTR Data Kenneth Bell;LordMike;Olof Lagerkvist ..\$(Configuration) - 1.0.85 + 1.0.107 + 1.0.107 true CS1591;CS0649 + false diff --git a/DiscUtils.slnx b/DiscUtils.slnx index 443f48333..d957e9987 100644 --- a/DiscUtils.slnx +++ b/DiscUtils.slnx @@ -48,6 +48,7 @@ + diff --git a/Library/Directory.Build.props b/Library/Directory.Build.props index ed51024c0..3c0fe7c12 100644 --- a/Library/Directory.Build.props +++ b/Library/Directory.Build.props @@ -5,7 +5,7 @@ netstandard2.0;netstandard2.1;net46;net48;net8.0;net9.0;net10.0 true - LTRData.$(MSBuildProjectName) + Devedse.$(MSBuildProjectName) $(LocalNuGetPath) $(FileVersion) README.md @@ -26,7 +26,21 @@ $(MSBuildThisFileDirectory)../SigningKey.snk false + false + $(BaseIntermediateOutputPath)Generated false + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/Library/DiscUtils.Core/DiskImageBuilder.cs b/Library/DiscUtils.Core/DiskImageBuilder.cs index 73663b373..cf12403f9 100644 --- a/Library/DiscUtils.Core/DiskImageBuilder.cs +++ b/Library/DiscUtils.Core/DiskImageBuilder.cs @@ -102,9 +102,18 @@ public static DiskImageBuilder GetBuilder(string type, string variant) /// 'foo', the files 'foo.vmdk' and 'foo-flat.vmdk' could be returned. public abstract IEnumerable Build(string baseName); + // Set this to true to enable AoT compatiblity + public static bool ShouldUseVirtualDiskManagerTypeMap { get; set; } + [MemberNotNull(nameof(_typeMap))] private static void InitializeMaps() { + if (ShouldUseVirtualDiskManagerTypeMap) + { + _typeMap = VirtualDiskManager.TypeMap; + return; + } + var typeMap = new Dictionary(); foreach (var type in typeof(VirtualDisk).Assembly.GetTypes()) diff --git a/Library/DiscUtils.Core/Partitions/PartitionTable.cs b/Library/DiscUtils.Core/Partitions/PartitionTable.cs index dd59c5dfb..9def19505 100644 --- a/Library/DiscUtils.Core/Partitions/PartitionTable.cs +++ b/Library/DiscUtils.Core/Partitions/PartitionTable.cs @@ -54,11 +54,13 @@ public abstract class PartitionTable /// public abstract Geometry? DiskGeometry { get; } + private static List? _factories; + private static List Factories { get { - if (field == null) + if (_factories == null) { var factories = new List(); @@ -66,17 +68,25 @@ private static List Factories { foreach (var attr in type.GetCustomAttributes(false)) { - factories.Add((PartitionTableFactory)Activator.CreateInstance(type)!); + factories.Add((PartitionTableFactory)Activator.CreateInstance(type, true)!); } } - field = factories; + _factories = factories; } - return field; + return _factories; + } + } + + internal static void RegisterPartitionTableFactory(PartitionTableFactory factory) + { + if (_factories == null) + { + _factories = new List(); } - set; + _factories.Add(factory); } /// diff --git a/Library/DiscUtils.Core/VirtualDisk.cs b/Library/DiscUtils.Core/VirtualDisk.cs index 15eea8f37..3a4046af7 100644 --- a/Library/DiscUtils.Core/VirtualDisk.cs +++ b/Library/DiscUtils.Core/VirtualDisk.cs @@ -375,12 +375,12 @@ public static VirtualDisk CreateDisk(DiscFileSystem fileSystem, string type, str var uri = PathToUri(path); VirtualDisk result; - if (!VirtualDiskManager.DiskTransports.TryGetValue(uri.Scheme, out var transportType)) + if (!VirtualDiskManager.DiskTransports.TryGetValue(uri.Scheme, out var transportFactory)) { throw new FileNotFoundException($"Unable to parse path '{path}'", path); } - var transport = (VirtualDiskTransport)Activator.CreateInstance(transportType)!; + var transport = transportFactory(); try { @@ -499,12 +499,12 @@ public static VirtualDisk CreateDisk(DiscFileSystem fileSystem, string type, str var uri = PathToUri(path); VirtualDisk? result = null; - if (!VirtualDiskManager.DiskTransports.TryGetValue(uri.Scheme, out var transportType)) + if (!VirtualDiskManager.DiskTransports.TryGetValue(uri.Scheme, out var transportFactory)) { throw new FileNotFoundException($"Unable to parse path '{uri}'", path); } - var transport = (VirtualDiskTransport)Activator.CreateInstance(transportType)!; + var transport = transportFactory(); try { diff --git a/Library/DiscUtils.Core/VirtualDiskManager.cs b/Library/DiscUtils.Core/VirtualDiskManager.cs index b6179de34..a25858960 100644 --- a/Library/DiscUtils.Core/VirtualDiskManager.cs +++ b/Library/DiscUtils.Core/VirtualDiskManager.cs @@ -1,7 +1,7 @@ -using System; +using DiscUtils.Internal; +using System; using System.Collections.Generic; using System.Reflection; -using DiscUtils.Internal; namespace DiscUtils; @@ -14,10 +14,10 @@ static VirtualDiskManager() { ExtensionMap = new Dictionary(StringComparer.OrdinalIgnoreCase); TypeMap = new Dictionary(StringComparer.OrdinalIgnoreCase); - DiskTransports = new Dictionary(StringComparer.OrdinalIgnoreCase); + DiskTransports = new Dictionary>(StringComparer.OrdinalIgnoreCase); } - internal static Dictionary DiskTransports { get; } + internal static Dictionary> DiskTransports { get; } internal static Dictionary ExtensionMap { get; } /// @@ -33,7 +33,46 @@ static VirtualDiskManager() internal static Dictionary TypeMap { get; } /// - /// Locates VirtualDiskFactory factories attributed with VirtualDiskFactoryAttribute, and types marked with VirtualDiskTransportAttribute, that are able to work with Virtual Disk types. + /// Registers a VirtualDiskFactory instance. + /// + /// The factory to register. + public static void RegisterVirtualDiskFactory(VirtualDiskFactory factory) + { + var type = factory.GetType(); + var diskFactoryAttribute = type.GetCustomAttribute(false); + if (diskFactoryAttribute != null) + { + if (!TypeMap.ContainsKey(diskFactoryAttribute.Type)) + { + TypeMap.Add(diskFactoryAttribute.Type, factory); + } + + foreach (var extension in diskFactoryAttribute.FileExtensions) + { + if (!ExtensionMap.ContainsKey(extension)) + { + ExtensionMap.Add(extension, factory); + } + } + } + } + + /// + /// Registers a VirtualDiskTransport factory. + /// + /// The URI scheme. + /// The factory method. + internal static void RegisterVirtualDiskTransport(string scheme, Func factory) + { + if (!DiskTransports.ContainsKey(scheme)) + { + DiskTransports.Add(scheme, factory); + } + } + + /// + /// Locates VirtualDiskFactory factories attributed with VirtualDiskFactoryAttribute, and types marked with VirtualDiskTransportAttribute, + /// that are able to work with Virtual Disk types. /// /// An assembly to scan public static void RegisterVirtualDiskTypes(Assembly assembly) @@ -55,7 +94,7 @@ public static void RegisterVirtualDiskTypes(Assembly assembly) var diskTransportAttribute = type.GetCustomAttribute(false); if (diskTransportAttribute != null) { - DiskTransports.Add(diskTransportAttribute.Scheme, type); + DiskTransports.Add(diskTransportAttribute.Scheme, () => (VirtualDiskTransport)Activator.CreateInstance(type)!); } } } diff --git a/Library/DiscUtils.Core/VolumeManager.cs b/Library/DiscUtils.Core/VolumeManager.cs index 417f09fc3..90bfa2a5b 100644 --- a/Library/DiscUtils.Core/VolumeManager.cs +++ b/Library/DiscUtils.Core/VolumeManager.cs @@ -82,26 +82,45 @@ public VolumeManager(Stream initialDiskContent) private static readonly object _syncObj = new(); + private static ConcurrentBag? _logicalVolumeFactories; + private static ConcurrentBag LogicalVolumeFactories { get { - if (field == null) + if (_logicalVolumeFactories == null) { lock (_syncObj) { - if (field == null) + if (_logicalVolumeFactories == null) { - var factories = new ConcurrentBag(GetLogicalVolumeFactories(_coreAssembly)); - field = factories; + _logicalVolumeFactories = new ConcurrentBag(GetLogicalVolumeFactories(_coreAssembly)); } } } - return field; + return _logicalVolumeFactories; + } + } + + /// + /// Register a new LogicalVolumeFactory instance. + /// + /// The factory to register. + internal static void RegisterLogicalVolumeFactory(LogicalVolumeFactory factory) + { + if (_logicalVolumeFactories == null) + { + lock (_syncObj) + { + if (_logicalVolumeFactories == null) + { + _logicalVolumeFactories = new ConcurrentBag(); + } + } } - set; + _logicalVolumeFactories.Add(factory); } private static IEnumerable GetLogicalVolumeFactories(Assembly assembly) diff --git a/Library/DiscUtils.SourceGenerator/DiscUtils.SourceGenerator.csproj b/Library/DiscUtils.SourceGenerator/DiscUtils.SourceGenerator.csproj new file mode 100644 index 000000000..35a9841d6 --- /dev/null +++ b/Library/DiscUtils.SourceGenerator/DiscUtils.SourceGenerator.csproj @@ -0,0 +1,22 @@ + + + + + netstandard2.0 + + true + Source Generator for DiscUtils + true + DiscUtils;SourceGenerator;Analyzer + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/Library/DiscUtils.SourceGenerator/FactoryGenerator.cs b/Library/DiscUtils.SourceGenerator/FactoryGenerator.cs new file mode 100644 index 000000000..c9b57a250 --- /dev/null +++ b/Library/DiscUtils.SourceGenerator/FactoryGenerator.cs @@ -0,0 +1,199 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +namespace DiscUtils.SourceGenerator +{ + [Generator] + public class FactoryGenerator : IIncrementalGenerator + { + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Debugging helper - uncomment to attach debugger + // if (!System.Diagnostics.Debugger.IsAttached) System.Diagnostics.Debugger.Launch(); + + var classDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: IsSyntaxTargetForGeneration, + transform: GetSemanticTargetForGeneration) + .Where(m => m != null); + + var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect()); + + context.RegisterSourceOutput(compilationAndClasses, (spc, source) => Execute(spc, source.Left, source.Right)); + } + + private bool IsSyntaxTargetForGeneration(SyntaxNode node, CancellationToken cancellationToken) + { + return node is ClassDeclarationSyntax cds && cds.BaseList != null; + } + + private INamedTypeSymbol? GetSemanticTargetForGeneration(GeneratorSyntaxContext context, CancellationToken cancellationToken) + { + var classDeclaration = (ClassDeclarationSyntax)context.Node; + var symbol = context.SemanticModel.GetDeclaredSymbol(classDeclaration) as INamedTypeSymbol; + + if (symbol == null) return null; + + if (InheritsFrom(symbol, "DiscUtils.Vfs.VfsFileSystemFactory") || + InheritsFrom(symbol, "DiscUtils.Internal.VirtualDiskFactory") || + InheritsFrom(symbol, "DiscUtils.Internal.LogicalVolumeFactory") || + InheritsFrom(symbol, "DiscUtils.Internal.VirtualDiskTransport") || + InheritsFrom(symbol, "DiscUtils.Partitions.PartitionTableFactory")) + { + return symbol; + } + + return null; + } + + private bool InheritsFrom(INamedTypeSymbol symbol, string typeName) + { + var current = symbol.BaseType; + while (current != null) + { + if (current.ToDisplayString() == typeName) + { + return true; + } + current = current.BaseType; + } + return false; + } + + private void Execute(SourceProductionContext context, Compilation compilation, System.Collections.Immutable.ImmutableArray classes) + { + try + { + if (classes.IsDefaultOrEmpty && compilation.AssemblyName != "DiscUtils" && compilation.AssemblyName != "LTRData.DiscUtils") + { + return; + } + + var distinctClasses = classes.Where(c => c != null).Distinct(SymbolEqualityComparer.Default).Cast().ToList(); + + // Generate AssemblyRegistration for libraries + if (distinctClasses.Any()) + { + GenerateAssemblyRegistration(context, compilation, distinctClasses); + } + + // If this is the main DiscUtils assembly, generate the aggregator + if (compilation.AssemblyName == "DiscUtils" || compilation.AssemblyName == "LTRData.DiscUtils") + { + GenerateSetupHelper(context, compilation); + } + } + catch (Exception ex) + { + // Generate a file with the error so we can see it + context.AddSource("GeneratorError.g.cs", SourceText.From($"/* Error: {ex.ToString()} */", Encoding.UTF8)); + } + } + + private void GenerateAssemblyRegistration(SourceProductionContext context, Compilation compilation, List classes) + { + var sb = new StringBuilder(); + var assemblyName = compilation.AssemblyName?.Replace(".", "_"); + + sb.AppendLine("using System;"); + sb.AppendLine("using DiscUtils.Core;"); + sb.AppendLine(); + sb.AppendLine($"namespace {compilation.AssemblyName}"); + sb.AppendLine("{"); + sb.AppendLine($" public static class AssemblyRegistration_{assemblyName}"); + sb.AppendLine(" {"); + sb.AppendLine(" public static void Register()"); + sb.AppendLine(" {"); + + foreach (var classSymbol in classes) + { + if (InheritsFrom(classSymbol, "DiscUtils.Vfs.VfsFileSystemFactory")) + { + sb.AppendLine($" DiscUtils.FileSystemManager.RegisterFileSystems(new {classSymbol.ToDisplayString()}());"); + } + else if (InheritsFrom(classSymbol, "DiscUtils.Internal.VirtualDiskFactory")) + { + sb.AppendLine($" DiscUtils.VirtualDiskManager.RegisterVirtualDiskFactory(new {classSymbol.ToDisplayString()}());"); + } + else if (InheritsFrom(classSymbol, "DiscUtils.Internal.LogicalVolumeFactory")) + { + sb.AppendLine($" DiscUtils.VolumeManager.RegisterLogicalVolumeFactory(new {classSymbol.ToDisplayString()}());"); + } + else if (InheritsFrom(classSymbol, "DiscUtils.Partitions.PartitionTableFactory")) + { + sb.AppendLine($" DiscUtils.Partitions.PartitionTable.RegisterPartitionTableFactory(new {classSymbol.ToDisplayString()}());"); + } + else if (InheritsFrom(classSymbol, "DiscUtils.Internal.VirtualDiskTransport")) + { + var attributes = classSymbol.GetAttributes().Where(ad => ad.AttributeClass?.ToDisplayString() == "DiscUtils.Internal.VirtualDiskTransportAttribute"); + foreach(var attr in attributes) + { + if (attr.ConstructorArguments.Length > 0) + { + string scheme = attr.ConstructorArguments[0].Value?.ToString(); + if (!string.IsNullOrEmpty(scheme)) + { + sb.AppendLine($" DiscUtils.VirtualDiskManager.RegisterVirtualDiskTransport(\"{scheme}\", () => new {classSymbol.ToDisplayString()}());"); + } + } + } + } + } + + sb.AppendLine(" }"); + sb.AppendLine(" }"); + sb.AppendLine("}"); + + context.AddSource($"AssemblyRegistration_{assemblyName}.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8)); + } + + private void GenerateSetupHelper(SourceProductionContext context, Compilation compilation) + { + var sb = new StringBuilder(); + sb.AppendLine("using System;"); + sb.AppendLine("using System.Reflection;"); + sb.AppendLine(); + sb.AppendLine("namespace DiscUtils.Setup"); + sb.AppendLine("{"); + sb.AppendLine(" public static class GeneratedSetupHelper"); + sb.AppendLine(" {"); + sb.AppendLine(" public static void RegisterFactories()"); + sb.AppendLine(" {"); + sb.AppendLine(" Console.WriteLine(\"GeneratedSetupHelper.RegisterFactories() called.\");"); + + foreach (var reference in compilation.References) + { + if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol) + { + var assemblyName = assemblySymbol.Name; + if ((assemblyName.StartsWith("DiscUtils.") || assemblyName.StartsWith("LTRData.DiscUtils.")) && !assemblyName.EndsWith("SourceGenerator")) + { + var safeAssemblyName = assemblyName.Replace(".", "_"); + var typeName = $"{assemblyName}.AssemblyRegistration_{safeAssemblyName}"; + + var typeSymbol = compilation.GetTypeByMetadataName(typeName); + + if (typeSymbol != null) + { + sb.AppendLine($" Console.WriteLine(\"Calling {typeName}.Register()\");"); + sb.AppendLine($" {typeName}.Register();"); + } + } + } + } + + sb.AppendLine(" }"); + sb.AppendLine(" }"); + sb.AppendLine("}"); + + context.AddSource("GeneratedSetupHelper.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8)); + } + } +} + diff --git a/Library/DiscUtils/SetupHelper.cs b/Library/DiscUtils/SetupHelper.cs index 9e68e892d..895416adf 100644 --- a/Library/DiscUtils/SetupHelper.cs +++ b/Library/DiscUtils/SetupHelper.cs @@ -6,6 +6,7 @@ using DiscUtils.Fat; using DiscUtils.HfsPlus; using DiscUtils.Iso9660; +using DiscUtils.Lvm; using DiscUtils.Nfs; using DiscUtils.Ntfs; using DiscUtils.OpticalDisk; @@ -53,4 +54,10 @@ public static void SetupComplete() Setup.SetupHelper.RegisterAssembly(typeof(Xva.Disk).Assembly); Setup.SetupHelper.RegisterAssembly(typeof(Lvm.LogicalVolumeManager).Assembly); } + + public static void SetupCompleteAot() + { + DiskImageBuilder.ShouldUseVirtualDiskManagerTypeMap = true; + DiscUtils.Setup.GeneratedSetupHelper.RegisterFactories(); + } } \ No newline at end of file diff --git a/generate-packages.ps1 b/generate-packages.ps1 new file mode 100644 index 000000000..30b4e2a18 --- /dev/null +++ b/generate-packages.ps1 @@ -0,0 +1,23 @@ +$outputDir = "C:\XGitPrivate\DevePXEBootStuff\DevePXEBootClean\DiscUtils\nupkgs" + +Write-Host "Cleaning output directory: $outputDir" +if (Test-Path -Path $outputDir) { + Get-ChildItem -Path $outputDir -Filter *.nupkg | Remove-Item -Force + Get-ChildItem -Path $outputDir -Filter *.snupkg | Remove-Item -Force +} else { + New-Item -ItemType Directory -Path $outputDir | Out-Null +} + +Write-Host "Cleaning solution..." +dotnet clean --configuration Release + +Write-Host "Restoring..." +dotnet restore + +Write-Host "Building..." +dotnet build --configuration Release + +Write-Host "Packing..." +dotnet pack --configuration Release --no-build --output $outputDir + +Write-Host "Done. Packages are in $outputDir"