diff --git a/CecilBasedAnnotator/Program.cs b/CecilBasedAnnotator/Program.cs index 9658dcc..a4d924c 100644 --- a/CecilBasedAnnotator/Program.cs +++ b/CecilBasedAnnotator/Program.cs @@ -3,11 +3,18 @@ namespace CecilBasedAnnotator { + using System.Collections.Immutable; + internal class Program { private static void Main(string[] args) { - TunnelVisionLabs.ReferenceAssemblyAnnotator.Program.Main(log: null, args[0], args[1], args[2]); + TunnelVisionLabs.ReferenceAssemblyAnnotator.Program.Main( + log: null, + referenceAssembly: args[0], + targetFrameworkDirectories: args[1].Split(';').ToImmutableArray(), + annotatedReferenceAssembly: args[2], + outputAssembly: args[3]); } } } diff --git a/CecilBasedAnnotator/Properties/launchSettings.json b/CecilBasedAnnotator/Properties/launchSettings.json index 2160454..fce9750 100644 --- a/CecilBasedAnnotator/Properties/launchSettings.json +++ b/CecilBasedAnnotator/Properties/launchSettings.json @@ -1,12 +1,12 @@ { "profiles": { - "mscorlib": { + "Microsoft.Win32.Primitives (netstandard1.6)": { "commandName": "Project", - "commandLineArgs": "\"$(NuGetPackageRoot)\\microsoft.netframework.referenceassemblies.net45\\1.0.0-preview.2\\build\\.NETFramework\\v4.5\\mscorlib.dll\" \"$(NuGetPackageRoot)\\microsoft.netcore.app.ref\\3.0.0-preview8-28405-07\\ref\\netcoreapp3.0\\mscorlib.dll\" mscorlib2.dll" + "commandLineArgs": "\"$(NuGetPackageRoot)\\microsoft.win32.primitives\\4.3.0\\ref\\netstandard1.3\\Microsoft.Win32.Primitives.dll\" \"$(NuGetPackageRoot)\\microsoft.win32.primitives\\4.3.0\\ref\\netstandard1.3\\;$(NuGetPackageRoot)\\system.runtime\\4.3.0\\ref\\netstandard1.5\\\\\" \"$(NuGetPackageRoot)\\microsoft.netcore.app.ref\\3.0.0\\ref\\netcoreapp3.0\\Microsoft.Win32.Primitives.dll\" Microsoft.Win32.Primitives.dll" }, - "System": { + "System.ComponentModel.DataAnnotations (netcoreapp2.0)": { "commandName": "Project", - "commandLineArgs": "\"$(NuGetPackageRoot)\\microsoft.netframework.referenceassemblies.net45\\1.0.0-preview.2\\build\\.NETFramework\\v4.5\\System.dll\" \"$(NuGetPackageRoot)\\microsoft.netcore.app.ref\\3.0.0-preview8-28405-07\\ref\\netcoreapp3.0\\System.dll\" System2.dll" + "commandLineArgs": "\"$(NuGetPackageRoot)\\microsoft.netcore.app\\2.0.0\\ref\\netcoreapp2.0\\System.ComponentModel.DataAnnotations.dll\" \"$(NuGetPackageRoot)\\microsoft.netcore.app\\2.0.0\\ref\\netcoreapp2.0\\\\\" \"$(NuGetPackageRoot)\\microsoft.netcore.app.ref\\3.0.0\\ref\\netcoreapp3.0\\System.ComponentModel.DataAnnotations.dll\" System.ComponentModel.DataAnnotations.dll" } } -} +} \ No newline at end of file diff --git a/TunnelVisionLabs.ReferenceAssemblyAnnotator/AnnotatorBuildTask.cs b/TunnelVisionLabs.ReferenceAssemblyAnnotator/AnnotatorBuildTask.cs index 570b2d9..f992064 100644 --- a/TunnelVisionLabs.ReferenceAssemblyAnnotator/AnnotatorBuildTask.cs +++ b/TunnelVisionLabs.ReferenceAssemblyAnnotator/AnnotatorBuildTask.cs @@ -4,6 +4,7 @@ namespace TunnelVisionLabs.ReferenceAssemblyAnnotator { using System; + using System.Collections.Immutable; using System.IO; using System.Linq; using Microsoft.Build.Framework; @@ -96,7 +97,14 @@ public override bool Execute() Directory.CreateDirectory(OutputPath); string outputAssembly = Path.Combine(OutputPath, Path.GetFileName(unannotatedReferenceAssembly)); - Program.Main(log, unannotatedReferenceAssembly, annotatedReferenceAssembly, outputAssembly); + + Program.Main( + log, + unannotatedReferenceAssembly, + TargetFrameworkDirectories.Select(item => item.ItemSpec).ToImmutableArray(), + annotatedReferenceAssembly, + outputAssembly); + GeneratedAssemblies = new[] { new TaskItem(outputAssembly) }; string sourceDocumentation = Path.ChangeExtension(unannotatedReferenceAssembly, ".xml"); diff --git a/TunnelVisionLabs.ReferenceAssemblyAnnotator/AssemblyResolver.cs b/TunnelVisionLabs.ReferenceAssemblyAnnotator/AssemblyResolver.cs new file mode 100644 index 0000000..7849716 --- /dev/null +++ b/TunnelVisionLabs.ReferenceAssemblyAnnotator/AssemblyResolver.cs @@ -0,0 +1,77 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace TunnelVisionLabs.ReferenceAssemblyAnnotator +{ + using System.Collections.Generic; + using System.Collections.Immutable; + using System.IO; + using System.Linq; + using Mono.Cecil; + + internal sealed class AssemblyResolver : IAssemblyResolver + { + private static readonly ReaderParameters DefaultReaderParameters = new ReaderParameters(ReadingMode.Deferred); + + private readonly ImmutableArray _searchDirectories; + private readonly Dictionary _assembliesByFileName = new Dictionary(); + + public AssemblyResolver(ImmutableArray searchDirectories) + { + _searchDirectories = searchDirectories; + } + + public AssemblyDefinition? Resolve(AssemblyNameReference name) + { + return Resolve(name, DefaultReaderParameters); + } + + public AssemblyDefinition? Resolve(AssemblyNameReference name, ReaderParameters parameters) + { + if (!_assembliesByFileName.TryGetValue(name.Name, out var assembly)) + { + var stream = _searchDirectories + .Select(directory => OpenReadIfExists(Path.Combine(directory, name.Name + ".dll"))) + .FirstOrDefault(s => s is object); + + assembly = stream is null ? null : AssemblyDefinition.ReadAssembly(stream, parameters); + + _assembliesByFileName.Add(name.Name, assembly); + } + + return assembly is object && Matches(assembly.Name, name) ? assembly : null; + } + + private static FileStream? OpenReadIfExists(string path) + { + try + { + return File.OpenRead(path); + } + catch (FileNotFoundException) + { + return null; + } + } + + /// + /// The point of this method is to be a high-performance sanity check, not to reproduce .NET assembly loading + /// behavior. + /// + private static bool Matches(AssemblyNameDefinition definition, AssemblyNameReference reference) + { + return definition.Name == reference.Name + && definition.Version >= reference.Version; + } + + public void Dispose() + { + foreach (var loadedAssembly in _assembliesByFileName.Values) + { + loadedAssembly?.Dispose(); + } + + _assembliesByFileName.Clear(); + } + } +} diff --git a/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs b/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs index 6f8c0be..a79fc55 100644 --- a/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs +++ b/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs @@ -5,6 +5,7 @@ namespace TunnelVisionLabs.ReferenceAssemblyAnnotator { using System; using System.Collections.Generic; + using System.Collections.Immutable; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -13,10 +14,9 @@ namespace TunnelVisionLabs.ReferenceAssemblyAnnotator internal class Program { - internal static void Main(SuppressibleLoggingHelper? log, string referenceAssembly, string annotatedReferenceAssembly, string outputAssembly) + internal static void Main(SuppressibleLoggingHelper? log, string referenceAssembly, ImmutableArray targetFrameworkDirectories, string annotatedReferenceAssembly, string outputAssembly) { - var assemblyResolver = new DefaultAssemblyResolver(); - assemblyResolver.AddSearchDirectory(Path.GetDirectoryName(referenceAssembly)); + using var assemblyResolver = new AssemblyResolver(targetFrameworkDirectories); using var assemblyDefinition = AssemblyDefinition.ReadAssembly(referenceAssembly, new ReaderParameters(ReadingMode.Deferred) { AssemblyResolver = assemblyResolver }); foreach (var module in assemblyDefinition.Modules) @@ -26,10 +26,15 @@ internal static void Main(SuppressibleLoggingHelper? log, string referenceAssemb log?.LogWarning("RA1000", "Skipping mixed-mode implementation assembly '{0}'", assemblyDefinition.Name); return; } + + if (module.TypeSystem.Object.Resolve() is null) + { + log?.LogWarning("RA1001", "Cannot resolve core library for assembly '{0}', skipping", assemblyDefinition.Name); + return; + } } - var annotatedAssemblyResolver = new DefaultAssemblyResolver(); - annotatedAssemblyResolver.AddSearchDirectory(Path.GetDirectoryName(annotatedReferenceAssembly)); + using var annotatedAssemblyResolver = new AssemblyResolver(ImmutableArray.Create(Path.GetDirectoryName(Path.GetFullPath(annotatedReferenceAssembly))!)); using var annotatedAssemblyDefinition = AssemblyDefinition.ReadAssembly(annotatedReferenceAssembly, new ReaderParameters(ReadingMode.Deferred) { AssemblyResolver = annotatedAssemblyResolver }); var wellKnownTypes = new WellKnownTypes(assemblyDefinition);