From bdf7b3943f2b11b3e157d2ddba4686da45214fd2 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Tue, 31 Aug 2021 13:43:36 -0600 Subject: [PATCH] Enable creating C# projections based on multiple input winmd's and docs --- .../AnalyzerReleases.Unshipped.md | 3 +- src/Microsoft.Windows.CsWin32/Docs.cs | 43 +++- src/Microsoft.Windows.CsWin32/Generator.cs | 32 +-- .../SourceGenerator.cs | 233 ++++++++++++------ .../build/Microsoft.Windows.CsWin32.props | 14 ++ src/Win32MetaGeneration/Program.cs | 2 +- .../GeneratorTests.cs | 90 +++---- .../Microsoft.Windows.CsWin32.Tests.csproj | 17 +- 8 files changed, 272 insertions(+), 162 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/AnalyzerReleases.Unshipped.md b/src/Microsoft.Windows.CsWin32/AnalyzerReleases.Unshipped.md index 25203f87..bcacfa87 100644 --- a/src/Microsoft.Windows.CsWin32/AnalyzerReleases.Unshipped.md +++ b/src/Microsoft.Windows.CsWin32/AnalyzerReleases.Unshipped.md @@ -9,4 +9,5 @@ PInvoke001 | Functionality | Warning | SourceGenerator PInvoke002 | Functionality | Warning | SourceGenerator PInvoke003 | Functionality | Warning | SourceGenerator PInvoke004 | Functionality | Warning | SourceGenerator -PInvoke005 | Functionality | Warning | SourceGenerator \ No newline at end of file +PInvoke005 | Functionality | Warning | SourceGenerator +PInvoke006 | Configuration | Warning | SourceGenerator \ No newline at end of file diff --git a/src/Microsoft.Windows.CsWin32/Docs.cs b/src/Microsoft.Windows.CsWin32/Docs.cs index 3d2fdcb6..e07f51eb 100644 --- a/src/Microsoft.Windows.CsWin32/Docs.cs +++ b/src/Microsoft.Windows.CsWin32/Docs.cs @@ -7,11 +7,14 @@ namespace Microsoft.Windows.CsWin32 using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; - using System.Reflection; + using System.Linq; using MessagePack; using Microsoft.Windows.SDK.Win32Docs; - internal class Docs + /// + /// An in-memory representation of API documentation. + /// + public class Docs { private static readonly Dictionary DocsByPath = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -22,7 +25,12 @@ private Docs(Dictionary apisAndDocs) this.apisAndDocs = apisAndDocs; } - internal static Docs Get(string docsPath) + /// + /// Loads docs from a file. + /// + /// The messagepack docs file to read from. + /// An instance of that accesses the documentation in the file specified by . + public static Docs Get(string docsPath) { lock (DocsByPath) { @@ -48,6 +56,35 @@ internal static Docs Get(string docsPath) } } + /// + /// Returns a instance that contains all the merged documentation from a list of docs. + /// + /// The docs to be merged. When API documentation is provided by multiple docs in this list, the first one appearing in this list is taken. + /// An instance that contains all the docs provided. When contains exactly one element, that element is returned. + public static Docs Merge(IReadOnlyList docs) + { + if (docs.Count == 1) + { + // Nothing to merge. + return docs[0]; + } + + Dictionary mergedDocs = new(docs.Sum(d => d.apisAndDocs.Count), StringComparer.OrdinalIgnoreCase); + foreach (Docs doc in docs) + { + foreach (KeyValuePair api in doc.apisAndDocs) + { + // We want a first one wins policy. + if (!mergedDocs.ContainsKey(api.Key)) + { + mergedDocs.Add(api.Key, api.Value); + } + } + } + + return new Docs(mergedDocs); + } + internal bool TryGetApiDocs(string apiName, [NotNullWhen(true)] out ApiDetails? docs) => this.apisAndDocs.TryGetValue(apiName, out docs); } } diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index cb13793c..3e3c15d9 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -286,16 +286,16 @@ public class Generator : IDisposable /// Initializes a new instance of the class. /// /// The path to the winmd metadata to generate APIs from. - /// The path to the API docs file. + /// The API docs to include in the generated code. /// Options that influence the result of generation. /// The compilation that the generated code will be added to. /// The parse options that will be used for the generated code. - public Generator(string metadataLibraryPath, string? apiDocsPath, GeneratorOptions? options = null, CSharpCompilation? compilation = null, CSharpParseOptions? parseOptions = null) + public Generator(string metadataLibraryPath, Docs? docs, GeneratorOptions options, CSharpCompilation? compilation = null, CSharpParseOptions? parseOptions = null) { this.MetadataIndex = MetadataIndex.Get(metadataLibraryPath, compilation?.Options.Platform); - this.ApiDocs = apiDocsPath is object ? Docs.Get(apiDocsPath) : null; + this.ApiDocs = docs; - this.options = options ??= new GeneratorOptions(); + this.options = options; this.options.Validate(); this.compilation = compilation; this.parseOptions = parseOptions; @@ -312,11 +312,6 @@ public Generator(string metadataLibraryPath, string? apiDocsPath, GeneratorOptio this.generateSupportedOSPlatformAttributesOnInterfaces = (targets & AttributeTargets.Interface) == AttributeTargets.Interface; } - if (options.AllowMarshaling) - { - this.BannedAPIs.Add("VARIANT", "Use `object` instead of VARIANT when in COM interface mode. VARIANT can only be emitted when emitting COM interfaces as structs."); - } - bool useComInterfaces = options.AllowMarshaling; this.generalTypeSettings = new TypeSyntaxSettings( this, @@ -343,13 +338,16 @@ private enum FriendlyOverloadOf InterfaceMethod, } - internal Dictionary BannedAPIs { get; } = new Dictionary - { - { "GetLastError", "Do not generate GetLastError. Call Marshal.GetLastWin32Error() instead. Learn more from https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshal.getlastwin32error" }, - { "OLD_LARGE_INTEGER", "Use the C# long keyword instead." }, - { "LARGE_INTEGER", "Use the C# long keyword instead." }, - { "ULARGE_INTEGER", "Use the C# ulong keyword instead." }, - }; + internal static ImmutableDictionary BannedAPIsWithoutMarshaling { get; } = ImmutableDictionary.Empty + .Add("GetLastError", "Do not generate GetLastError. Call Marshal.GetLastWin32Error() instead. Learn more from https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshal.getlastwin32error") + .Add("OLD_LARGE_INTEGER", "Use the C# long keyword instead.") + .Add("LARGE_INTEGER", "Use the C# long keyword instead.") + .Add("ULARGE_INTEGER", "Use the C# ulong keyword instead."); + + internal static ImmutableDictionary BannedAPIsWithMarshaling { get; } = BannedAPIsWithoutMarshaling + .Add("VARIANT", "Use `object` instead of VARIANT when in COM interface mode. VARIANT can only be emitted when emitting COM interfaces as structs."); + + internal ImmutableDictionary BannedAPIs => GetBannedAPIs(this.options); internal MetadataIndex MetadataIndex { get; } @@ -970,6 +968,8 @@ nsContents.Key is object return normalizedResults; } + internal static ImmutableDictionary GetBannedAPIs(GeneratorOptions options) => options.AllowMarshaling ? BannedAPIsWithMarshaling : BannedAPIsWithoutMarshaling; + [return: NotNullIfNotNull("marshalAs")] internal static AttributeSyntax? MarshalAs(MarshalAsAttribute? marshalAs) { diff --git a/src/Microsoft.Windows.CsWin32/SourceGenerator.cs b/src/Microsoft.Windows.CsWin32/SourceGenerator.cs index 1d2d0407..9f03ed94 100644 --- a/src/Microsoft.Windows.CsWin32/SourceGenerator.cs +++ b/src/Microsoft.Windows.CsWin32/SourceGenerator.cs @@ -90,6 +90,14 @@ public class SourceGenerator : ISourceGenerator DiagnosticSeverity.Warning, isEnabledByDefault: true); + private static readonly DiagnosticDescriptor DocParsingError = new DiagnosticDescriptor( + "PInvoke006", + "DocsParseError", + "An error occurred while reading docs file: \"{0}\": {1}", + "Configuration", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + /// public void Initialize(GeneratorInitializationContext context) { @@ -98,20 +106,12 @@ public void Initialize(GeneratorInitializationContext context) /// public void Execute(GeneratorExecutionContext context) { - if (!(context.Compilation is CSharpCompilation)) + if (context.Compilation is not CSharpCompilation compilation) { return; } - if (!context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.MicrosoftWindowsSdkWin32MetadataBasePath", out string? metadataBasePath) || - string.IsNullOrWhiteSpace(metadataBasePath)) - { - return; - } - - context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.MicrosoftWindowsSdkApiDocsPath", out string? apiDocsPath); - - GeneratorOptions? options = null; + GeneratorOptions options; AdditionalText? nativeMethodsJsonFile = context.AdditionalFiles .FirstOrDefault(af => string.Equals(Path.GetFileName(af.Path), NativeMethodsJsonAdditionalFileName, StringComparison.OrdinalIgnoreCase)); if (nativeMethodsJsonFile is object) @@ -124,6 +124,10 @@ public void Execute(GeneratorExecutionContext context) PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }); } + else + { + options = new GeneratorOptions(); + } AdditionalText? nativeMethodsTxtFile = context.AdditionalFiles .FirstOrDefault(af => string.Equals(Path.GetFileName(af.Path), NativeMethodsTxtAdditionalFileName, StringComparison.OrdinalIgnoreCase)); @@ -132,8 +136,6 @@ public void Execute(GeneratorExecutionContext context) return; } - string metadataPath = Path.Combine(metadataBasePath, "Windows.Win32.winmd"); - var compilation = (CSharpCompilation)context.Compilation; var parseOptions = (CSharpParseOptions)context.ParseOptions; if (!compilation.Options.AllowUnsafe) @@ -141,100 +143,142 @@ public void Execute(GeneratorExecutionContext context) context.ReportDiagnostic(Diagnostic.Create(UnsafeCodeRequired, location: null)); } - using var generator = new Generator(metadataPath, apiDocsPath, options, compilation, parseOptions); - - SourceText? nativeMethodsTxt = nativeMethodsTxtFile.GetText(context.CancellationToken); - if (nativeMethodsTxt is null) + Docs? docs = ParseDocs(context); + IReadOnlyList generators = CollectMetadataPaths(context).Select(path => new Generator(path, docs, options, compilation, parseOptions)).ToList(); + try { - return; - } - - foreach (TextLine line in nativeMethodsTxt.Lines) - { - context.CancellationToken.ThrowIfCancellationRequested(); - string name = line.ToString(); - if (string.IsNullOrWhiteSpace(name) || name.StartsWith("//", StringComparison.InvariantCulture)) + SourceText? nativeMethodsTxt = nativeMethodsTxtFile.GetText(context.CancellationToken); + if (nativeMethodsTxt is null) { - continue; + return; } - name = name.Trim(); - var location = Location.Create(nativeMethodsTxtFile.Path, line.Span, nativeMethodsTxt.Lines.GetLinePositionSpan(line.Span)); - try + foreach (TextLine line in nativeMethodsTxt.Lines) { - if (generator.BannedAPIs.TryGetValue(name, out string? reason)) + context.CancellationToken.ThrowIfCancellationRequested(); + string name = line.ToString(); + if (string.IsNullOrWhiteSpace(name) || name.StartsWith("//", StringComparison.InvariantCulture)) { - context.ReportDiagnostic(Diagnostic.Create(BannedApi, location, reason)); + continue; } - else if (name.EndsWith(".*", StringComparison.Ordinal)) + + name = name.Trim(); + var location = Location.Create(nativeMethodsTxtFile.Path, line.Span, nativeMethodsTxt.Lines.GetLinePositionSpan(line.Span)); + try { - var moduleName = name.Substring(0, name.Length - 2); - if (!generator.TryGenerateAllExternMethods(moduleName, context.CancellationToken)) + if (Generator.GetBannedAPIs(options).TryGetValue(name, out string? reason)) { - context.ReportDiagnostic(Diagnostic.Create(NoMethodsForModule, location, moduleName)); + context.ReportDiagnostic(Diagnostic.Create(BannedApi, location, reason)); + continue; } - } - else if (!generator.TryGenerate(name, context.CancellationToken)) - { - if (generator.TryGetEnumName(name, out string? declaringEnum)) + + if (name.EndsWith(".*", StringComparison.Ordinal)) { - context.ReportDiagnostic(Diagnostic.Create(UseEnumValueDeclaringType, location, declaringEnum)); - if (!generator.TryGenerate(declaringEnum, context.CancellationToken)) + var moduleName = name.Substring(0, name.Length - 2); + bool matchModule = false; + foreach (Generator generator in generators) { - ReportNoMatch(location, declaringEnum); + matchModule |= generator.TryGenerateAllExternMethods(moduleName, context.CancellationToken); } + + if (!matchModule) + { + context.ReportDiagnostic(Diagnostic.Create(NoMethodsForModule, location, moduleName)); + } + + continue; } - else + + bool matchApi = false; + foreach (Generator generator in generators) + { + if (generator.TryGenerate(name, context.CancellationToken)) + { + matchApi = true; + continue; + } + + if (generator.TryGetEnumName(name, out string? declaringEnum)) + { + context.ReportDiagnostic(Diagnostic.Create(UseEnumValueDeclaringType, location, declaringEnum)); + if (generator.TryGenerate(declaringEnum, context.CancellationToken)) + { + matchApi = true; + continue; + } + else + { + ReportNoMatch(location, declaringEnum); + } + } + } + + if (!matchApi) { ReportNoMatch(location, name); } } - } - catch (GenerationFailedException ex) - { - if (Generator.IsPlatformCompatibleException(ex)) + catch (GenerationFailedException ex) { - context.ReportDiagnostic(Diagnostic.Create(CpuArchitectureIncompatibility, location)); + if (Generator.IsPlatformCompatibleException(ex)) + { + context.ReportDiagnostic(Diagnostic.Create(CpuArchitectureIncompatibility, location)); + } + else + { + // Build up a complete error message. + context.ReportDiagnostic(Diagnostic.Create(InternalError, location, AssembleFullExceptionMessage(ex))); + } } - else + } + + foreach (Generator generator in generators) + { + var compilationUnits = generator.GetCompilationUnits(context.CancellationToken) + .OrderBy(pair => pair.Key, StringComparer.OrdinalIgnoreCase) + .ThenBy(pair => pair.Key, StringComparer.Ordinal); + foreach (var unit in compilationUnits) { - // Build up a complete error message. - context.ReportDiagnostic(Diagnostic.Create(InternalError, location, AssembleFullExceptionMessage(ex))); + context.AddSource(unit.Key, unit.Value.ToFullString()); } } - } - var compilationUnits = generator.GetCompilationUnits(context.CancellationToken) - .OrderBy(pair => pair.Key, StringComparer.OrdinalIgnoreCase) - .ThenBy(pair => pair.Key, StringComparer.Ordinal); - foreach (var unit in compilationUnits) - { - context.AddSource(unit.Key, unit.Value.ToFullString()); - } - - void ReportNoMatch(Location? location, string failedAttempt) - { - var suggestions = generator.GetSuggestions(failedAttempt).Take(4).ToList(); - if (suggestions.Count > 0) + void ReportNoMatch(Location? location, string failedAttempt) { - var suggestionBuilder = new StringBuilder(); - for (int i = 0; i < suggestions.Count; i++) + List suggestions = new(); + foreach (Generator generator in generators) + { + suggestions.AddRange(generator.GetSuggestions(failedAttempt).Take(4)); + } + + if (suggestions.Count > 0) { - if (i > 0) + var suggestionBuilder = new StringBuilder(); + for (int i = 0; i < suggestions.Count; i++) { - suggestionBuilder.Append(i < suggestions.Count - 1 ? ", " : " or "); + if (i > 0) + { + suggestionBuilder.Append(i < suggestions.Count - 1 ? ", " : " or "); + } + + suggestionBuilder.Append('"'); + suggestionBuilder.Append(suggestions[i]); + suggestionBuilder.Append('"'); } - suggestionBuilder.Append('"'); - suggestionBuilder.Append(suggestions[i]); - suggestionBuilder.Append('"'); + context.ReportDiagnostic(Diagnostic.Create(NoMatchingMethodOrTypeWithSuggestions, location, failedAttempt, suggestionBuilder)); + } + else + { + context.ReportDiagnostic(Diagnostic.Create(NoMatchingMethodOrType, location, failedAttempt)); } - - context.ReportDiagnostic(Diagnostic.Create(NoMatchingMethodOrTypeWithSuggestions, location, failedAttempt, suggestionBuilder)); } - else + } + finally + { + foreach (Generator generator in generators) { - context.ReportDiagnostic(Diagnostic.Create(NoMatchingMethodOrType, location, failedAttempt)); + generator.Dispose(); } } } @@ -258,5 +302,46 @@ private static string AssembleFullExceptionMessage(Exception ex) return sb.ToString(); } + + private static IReadOnlyList CollectMetadataPaths(GeneratorExecutionContext context) + { + if (!context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.CsWin32InputMetadataPaths", out string? delimitedMetadataBasePaths) || + string.IsNullOrWhiteSpace(delimitedMetadataBasePaths)) + { + return Array.Empty(); + } + + string[] metadataBasePaths = delimitedMetadataBasePaths.Split(';'); + return metadataBasePaths; + } + + private static Docs? ParseDocs(GeneratorExecutionContext context) + { + Docs? docs = null; + if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.CsWin32InputDocPaths", out string? delimitedApiDocsPaths) && + !string.IsNullOrWhiteSpace(delimitedApiDocsPaths)) + { + string[] apiDocsPaths = delimitedApiDocsPaths!.Split(';'); + if (apiDocsPaths.Length > 0) + { + List docsList = new(apiDocsPaths.Length); + foreach (string path in apiDocsPaths) + { + try + { + docsList.Add(Docs.Get(path)); + } + catch (Exception e) + { + context.ReportDiagnostic(Diagnostic.Create(DocParsingError, null, path, e.Message)); + } + } + + docs = Docs.Merge(docsList); + } + } + + return docs; + } } } diff --git a/src/Microsoft.Windows.CsWin32/build/Microsoft.Windows.CsWin32.props b/src/Microsoft.Windows.CsWin32/build/Microsoft.Windows.CsWin32.props index 1a86b066..132fc745 100644 --- a/src/Microsoft.Windows.CsWin32/build/Microsoft.Windows.CsWin32.props +++ b/src/Microsoft.Windows.CsWin32/build/Microsoft.Windows.CsWin32.props @@ -7,4 +7,18 @@ + + + + + + + + + + + @(ProjectionMetadataWinmd) + @(ProjectionDocs) + + diff --git a/src/Win32MetaGeneration/Program.cs b/src/Win32MetaGeneration/Program.cs index b4918d28..90ea2b88 100644 --- a/src/Win32MetaGeneration/Program.cs +++ b/src/Win32MetaGeneration/Program.cs @@ -47,7 +47,7 @@ private static void Main(string[] args) string apiDocsPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location!)!, "apidocs.msgpack"); using var generator = new Generator( metadataPath, - apiDocsPath, + Docs.Get(apiDocsPath), new GeneratorOptions { WideCharOnly = true, diff --git a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs index 50983408..1d5d9601 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -837,14 +838,6 @@ public void ProjectReferenceBetweenTwoGeneratingProjects(bool internalsVisibleTo [Fact] public async Task TestSimpleStructure() { - var basePath = this.GetType().Assembly.GetCustomAttributes().SingleOrDefault(metadata => metadata.Key == "MicrosoftWindowsSdkWin32MetadataBasePath")?.Value; - var docsPath = this.GetType().Assembly.GetCustomAttributes().SingleOrDefault(metadata => metadata.Key == "MicrosoftWindowsSdkApiDocsPath")?.Value; - - var globalconfig = $@"is_global = true - -build_property.MicrosoftWindowsSdkApiDocsPath = {docsPath} -build_property.MicrosoftWindowsSdkWin32MetadataBasePath = {basePath} -"; await new VerifyTest { TestState = @@ -856,7 +849,7 @@ public async Task TestSimpleStructure() }, AnalyzerConfigFiles = { - ("/.globalconfig", globalconfig), + ("/.globalconfig", ConstructGlobalConfigString()), }, GeneratedSources = { @@ -906,14 +899,6 @@ public static implicit operator bool(BOOL value) [Fact] public async Task TestSimpleEnum() { - var basePath = this.GetType().Assembly.GetCustomAttributes().SingleOrDefault(metadata => metadata.Key == "MicrosoftWindowsSdkWin32MetadataBasePath")?.Value; - var docsPath = this.GetType().Assembly.GetCustomAttributes().SingleOrDefault(metadata => metadata.Key == "MicrosoftWindowsSdkApiDocsPath")?.Value; - - var globalconfig = $@"is_global = true - -build_property.MicrosoftWindowsSdkApiDocsPath = {docsPath} -build_property.MicrosoftWindowsSdkWin32MetadataBasePath = {basePath} -"; await new VerifyTest { TestState = @@ -925,7 +910,7 @@ public async Task TestSimpleEnum() }, AnalyzerConfigFiles = { - ("/.globalconfig", globalconfig), + ("/.globalconfig", ConstructGlobalConfigString()), }, GeneratedSources = { @@ -979,12 +964,6 @@ internal enum DISPLAYCONFIG_SCANLINE_ORDERING [Fact] public async Task TestSimpleEnumWithoutDocs() { - var basePath = this.GetType().Assembly.GetCustomAttributes().SingleOrDefault(metadata => metadata.Key == "MicrosoftWindowsSdkWin32MetadataBasePath")?.Value; - - var globalconfig = $@"is_global = true - -build_property.MicrosoftWindowsSdkWin32MetadataBasePath = {basePath} -"; await new VerifyTest { TestState = @@ -996,7 +975,7 @@ public async Task TestSimpleEnumWithoutDocs() }, AnalyzerConfigFiles = { - ("/.globalconfig", globalconfig), + ("/.globalconfig", ConstructGlobalConfigString(omitDocs: true)), }, GeneratedSources = { @@ -1040,14 +1019,6 @@ internal enum DISPLAYCONFIG_SCANLINE_ORDERING [Fact] public async Task TestFlagsEnum() { - var basePath = this.GetType().Assembly.GetCustomAttributes().SingleOrDefault(metadata => metadata.Key == "MicrosoftWindowsSdkWin32MetadataBasePath")?.Value; - var docsPath = this.GetType().Assembly.GetCustomAttributes().SingleOrDefault(metadata => metadata.Key == "MicrosoftWindowsSdkApiDocsPath")?.Value; - - var globalconfig = $@"is_global = true - -build_property.MicrosoftWindowsSdkApiDocsPath = {docsPath} -build_property.MicrosoftWindowsSdkWin32MetadataBasePath = {basePath} -"; await new VerifyTest { TestState = @@ -1059,7 +1030,7 @@ public async Task TestFlagsEnum() }, AnalyzerConfigFiles = { - ("/.globalconfig", globalconfig), + ("/.globalconfig", ConstructGlobalConfigString()), }, GeneratedSources = { @@ -1124,14 +1095,6 @@ internal enum FILE_ACCESS_FLAGS : uint [Fact] public async Task TestSimpleDelegate() { - var basePath = this.GetType().Assembly.GetCustomAttributes().SingleOrDefault(metadata => metadata.Key == "MicrosoftWindowsSdkWin32MetadataBasePath")?.Value; - var docsPath = this.GetType().Assembly.GetCustomAttributes().SingleOrDefault(metadata => metadata.Key == "MicrosoftWindowsSdkApiDocsPath")?.Value; - - var globalconfig = $@"is_global = true - -build_property.MicrosoftWindowsSdkApiDocsPath = {docsPath} -build_property.MicrosoftWindowsSdkWin32MetadataBasePath = {basePath} -"; await new VerifyTest { TestState = @@ -1143,7 +1106,7 @@ public async Task TestSimpleDelegate() }, AnalyzerConfigFiles = { - ("/.globalconfig", globalconfig), + ("/.globalconfig", ConstructGlobalConfigString()), }, GeneratedSources = { @@ -1298,14 +1261,6 @@ internal readonly partial struct LPARAM [Fact] public async Task TestSimpleMethod() { - var basePath = this.GetType().Assembly.GetCustomAttributes().SingleOrDefault(metadata => metadata.Key == "MicrosoftWindowsSdkWin32MetadataBasePath")?.Value; - var docsPath = this.GetType().Assembly.GetCustomAttributes().SingleOrDefault(metadata => metadata.Key == "MicrosoftWindowsSdkApiDocsPath")?.Value; - - var globalconfig = $@"is_global = true - -build_property.MicrosoftWindowsSdkApiDocsPath = {docsPath} -build_property.MicrosoftWindowsSdkWin32MetadataBasePath = {basePath} -"; await new VerifyTest { TestState = @@ -1317,7 +1272,7 @@ public async Task TestSimpleMethod() }, AnalyzerConfigFiles = { - ("/.globalconfig", globalconfig), + ("/.globalconfig", ConstructGlobalConfigString()), }, GeneratedSources = { @@ -1450,14 +1405,6 @@ internal static partial class PInvoke [Fact] public async Task TestMethodWithOverloads() { - var basePath = this.GetType().Assembly.GetCustomAttributes().SingleOrDefault(metadata => metadata.Key == "MicrosoftWindowsSdkWin32MetadataBasePath")?.Value; - var docsPath = this.GetType().Assembly.GetCustomAttributes().SingleOrDefault(metadata => metadata.Key == "MicrosoftWindowsSdkApiDocsPath")?.Value; - - var globalconfig = $@"is_global = true - -build_property.MicrosoftWindowsSdkApiDocsPath = {docsPath} -build_property.MicrosoftWindowsSdkWin32MetadataBasePath = {basePath} -"; await new VerifyTest { TestState = @@ -1469,7 +1416,7 @@ public async Task TestMethodWithOverloads() }, AnalyzerConfigFiles = { - ("/.globalconfig", globalconfig), + ("/.globalconfig", ConstructGlobalConfigString()), }, GeneratedSources = { @@ -2022,6 +1969,25 @@ internal partial struct SECURITY_ATTRIBUTES }.RunAsync(); } + private static string ConstructGlobalConfigString(bool omitDocs = false) + { + StringBuilder globalConfigBuilder = new(); + globalConfigBuilder.AppendLine("is_global = true"); + globalConfigBuilder.AppendLine(); + globalConfigBuilder.AppendLine($"build_property.CsWin32InputMetadataPaths = {JoinAssemblyMetadata("ProjectionMetadataWinmd")}"); + if (!omitDocs) + { + globalConfigBuilder.AppendLine($"build_property.CsWin32InputDocPaths = {JoinAssemblyMetadata("ProjectionDocs")}"); + } + + return globalConfigBuilder.ToString(); + + static string JoinAssemblyMetadata(string name) + { + return string.Join(";", typeof(GeneratorTests).Assembly.GetCustomAttributes().Where(metadata => metadata.Key == name).Select(metadata => metadata.Value)); + } + } + private static ImmutableArray FilterDiagnostics(ImmutableArray diagnostics) => diagnostics.Where(d => d.Severity > DiagnosticSeverity.Hidden).ToImmutableArray(); private static bool IsAttributePresent(AttributeListSyntax al, string attributeName) => al.Attributes.Any(a => a.Name.ToString() == attributeName); @@ -2174,7 +2140,7 @@ private async Task CreateCompilationAsync(ReferenceAssemblies return compilation; } - private Generator CreateGenerator(GeneratorOptions? options = null, CSharpCompilation? compilation = null) => new Generator(MetadataPath, ApiDocsPath, options ?? DefaultTestGeneratorOptions, compilation ?? this.compilation, this.parseOptions); + private Generator CreateGenerator(GeneratorOptions? options = null, CSharpCompilation? compilation = null) => new Generator(MetadataPath, Docs.Get(ApiDocsPath), options ?? DefaultTestGeneratorOptions, compilation ?? this.compilation, this.parseOptions); private static class MyReferenceAssemblies { diff --git a/test/Microsoft.Windows.CsWin32.Tests/Microsoft.Windows.CsWin32.Tests.csproj b/test/Microsoft.Windows.CsWin32.Tests/Microsoft.Windows.CsWin32.Tests.csproj index 079ea0f5..fb3008d7 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/Microsoft.Windows.CsWin32.Tests.csproj +++ b/test/Microsoft.Windows.CsWin32.Tests/Microsoft.Windows.CsWin32.Tests.csproj @@ -4,17 +4,24 @@ net5.0;netcoreapp3.1;net472 - + - - + <_AssemblyMetadata1 Include="@(ProjectionMetadataWinmd)"> + %(Identity) + + + + <_AssemblyMetadata2 Include="@(ProjectionDocs)"> + %(Identity) + + - + PreserveNewest - + PreserveNewest