Skip to content

Commit

Permalink
feat: Add DLL references support for file-based API metadata generati…
Browse files Browse the repository at this point in the history
…on (#9825)

feat: Add  DLL references support for file-based metadata generation
  • Loading branch information
filzrev authored Apr 1, 2024
1 parent 009dd5e commit 2799e6f
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 16 deletions.
16 changes: 8 additions & 8 deletions src/Docfx.Dotnet/CompilationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static bool CheckDiagnostics(this Compilation compilation, bool errorAsWa
return errorCount > 0;
}

public static Compilation CreateCompilationFromCSharpFiles(IEnumerable<string> files, IDictionary<string, string> msbuildProperties)
public static Compilation CreateCompilationFromCSharpFiles(IEnumerable<string> files, IDictionary<string, string> msbuildProperties, MetadataReference[] references)
{
var parserOption = GetCSharpParseOptions(msbuildProperties);
var syntaxTrees = files.Select(path => CS.CSharpSyntaxTree.ParseText(File.ReadAllText(path), parserOption, path: path));
Expand All @@ -63,7 +63,7 @@ public static Compilation CreateCompilationFromCSharpFiles(IEnumerable<string> f
assemblyName: null,
options: new CS.CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, xmlReferenceResolver: XmlFileResolver.Default),
syntaxTrees: syntaxTrees,
references: GetDefaultMetadataReferences("C#"));
references: GetDefaultMetadataReferences("C#").Concat(references));
}

public static Compilation CreateCompilationFromCSharpCode(string code, IDictionary<string, string> msbuildProperties, string? name = null, params MetadataReference[] references)
Expand All @@ -75,10 +75,10 @@ public static Compilation CreateCompilationFromCSharpCode(string code, IDictiona
name,
options: new CS.CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, xmlReferenceResolver: XmlFileResolver.Default),
syntaxTrees: [syntaxTree],
references: GetDefaultMetadataReferences("C#").Concat(references));
references: GetDefaultMetadataReferences("C#").Concat(references ?? []));
}

public static Compilation CreateCompilationFromVBFiles(IEnumerable<string> files, IDictionary<string, string> msbuildProperties)
public static Compilation CreateCompilationFromVBFiles(IEnumerable<string> files, IDictionary<string, string> msbuildProperties, MetadataReference[] references)
{
var parserOption = GetVisualBasicParseOptions(msbuildProperties);
var syntaxTrees = files.Select(path => VB.VisualBasicSyntaxTree.ParseText(File.ReadAllText(path), parserOption, path: path));
Expand All @@ -87,7 +87,7 @@ public static Compilation CreateCompilationFromVBFiles(IEnumerable<string> files
assemblyName: null,
options: new VB.VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary, globalImports: GetVBGlobalImports(), xmlReferenceResolver: XmlFileResolver.Default),
syntaxTrees: syntaxTrees,
references: GetDefaultMetadataReferences("VB"));
references: GetDefaultMetadataReferences("VB").Concat(references));
}

public static Compilation CreateCompilationFromVBCode(string code, IDictionary<string, string> msbuildProperties, string? name = null, params MetadataReference[] references)
Expand All @@ -99,19 +99,19 @@ public static Compilation CreateCompilationFromVBCode(string code, IDictionary<s
name,
options: new VB.VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary, globalImports: GetVBGlobalImports(), xmlReferenceResolver: XmlFileResolver.Default),
syntaxTrees: [syntaxTree],
references: GetDefaultMetadataReferences("VB").Concat(references));
references: GetDefaultMetadataReferences("VB").Concat(references ?? []));
}

public static (Compilation, IAssemblySymbol) CreateCompilationFromAssembly(string assemblyPath, IEnumerable<string>? references = null)
public static (Compilation, IAssemblySymbol) CreateCompilationFromAssembly(string assemblyPath, params MetadataReference[] references)
{
var metadataReference = CreateMetadataReference(assemblyPath);
var compilation = CS.CSharpCompilation.Create(
assemblyName: null,
options: new CS.CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary),
syntaxTrees: s_assemblyBootstrap,
references: GetReferenceAssemblies(assemblyPath)
.Concat(references ?? Enumerable.Empty<string>())
.Select(CreateMetadataReference)
.Concat(references ?? [])
.Append(metadataReference));

var assembly = (IAssemblySymbol)compilation.GetAssemblyOrModuleSymbol(metadataReference)!;
Expand Down
18 changes: 14 additions & 4 deletions src/Docfx.Dotnet/DotnetApiCatalog.Compile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ partial class DotnetApiCatalog
_ => LoggerVerbosity.Quiet,
});

var workspace = MSBuildWorkspace.Create(msbuildProperties);
using var workspace = MSBuildWorkspace.Create(msbuildProperties);
workspace.WorkspaceFailed += (sender, e) => Logger.LogWarning($"{e.Diagnostic}");

if (files.TryGetValue(FileType.NotSupported, out var unsupportedFiles))
Expand Down Expand Up @@ -88,26 +88,36 @@ await LoadCompilationFromProject(project.AbsolutePath) is { } compilation)
assemblies.Add((compilation.Assembly, compilation));
}

var references = config.References ?? [];
var metadataReferences = references.Select(assemblyPath =>
{
var documentation = XmlDocumentationProvider.CreateFromFile(Path.ChangeExtension(assemblyPath, ".xml"));
return MetadataReference.CreateFromFile(assemblyPath, documentation: documentation);
}).ToArray();

// LoadCompilationFrom C# source files
if (files.TryGetValue(FileType.CSSourceCode, out var csFiles))
{
var compilation = CompilationHelper.CreateCompilationFromCSharpFiles(csFiles.Select(f => f.NormalizedPath), msbuildProperties);
var compilation = CompilationHelper.CreateCompilationFromCSharpFiles(csFiles.Select(f => f.NormalizedPath), msbuildProperties, metadataReferences);
hasCompilationError |= compilation.CheckDiagnostics(config.AllowCompilationErrors);
assemblies.Add((compilation.Assembly, compilation));
}

// LoadCompilationFrom VB source files
if (files.TryGetValue(FileType.VBSourceCode, out var vbFiles))
{
var compilation = CompilationHelper.CreateCompilationFromVBFiles(vbFiles.Select(f => f.NormalizedPath), msbuildProperties);
var compilation = CompilationHelper.CreateCompilationFromVBFiles(vbFiles.Select(f => f.NormalizedPath), msbuildProperties, metadataReferences);
hasCompilationError |= compilation.CheckDiagnostics(config.AllowCompilationErrors);
assemblies.Add((compilation.Assembly, compilation));
}

// Load Compilation from assembly files
if (files.TryGetValue(FileType.Assembly, out var assemblyFiles))
{
foreach (var assemblyFile in assemblyFiles)
{
Logger.LogInfo($"Loading assembly {assemblyFile.NormalizedPath}");
var (compilation, assembly) = CompilationHelper.CreateCompilationFromAssembly(assemblyFile.NormalizedPath, config.References);
var (compilation, assembly) = CompilationHelper.CreateCompilationFromAssembly(assemblyFile.NormalizedPath, metadataReferences);
hasCompilationError |= compilation.CheckDiagnostics(config.AllowCompilationErrors);
assemblies.Add((assembly, compilation));
}
Expand Down
30 changes: 28 additions & 2 deletions test/Docfx.Dotnet.Tests/GenerateMetadataFromCSUnitTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ public class GenerateMetadataFromCSUnitTest
{
private static readonly Dictionary<string, string> EmptyMSBuildProperties = new();

private static MetadataItem Verify(string code, ExtractMetadataConfig config = null, IDictionary<string, string> msbuildProperties = null)
private static MetadataItem Verify(string code, ExtractMetadataConfig config = null, IDictionary<string, string> msbuildProperties = null, MetadataReference[] references = null)
{
var compilation = CompilationHelper.CreateCompilationFromCSharpCode(code, msbuildProperties ?? EmptyMSBuildProperties, "test.dll");
var compilation = CompilationHelper.CreateCompilationFromCSharpCode(code, msbuildProperties ?? EmptyMSBuildProperties, "test.dll", references);
var extensionMethods = compilation.Assembly.FindExtensionMethods(new(new(), new())).ToArray();
return compilation.Assembly.GenerateMetadataItem(compilation, config, extensionMethods: extensionMethods);
}
Expand Down Expand Up @@ -3771,4 +3771,30 @@ public void F1() {}
Assert.Empty(foo.Items);
}
}

[Fact]
public void TestGenerateMetadataWithReference()
{
string code = @"
namespace Test
{
public class Foo
{
public TupleLibrary.XmlTasks Tasks{ get;set; }
}
}
";
var references = new string[] { "TestData/TupleLibrary.dll" }.Select(assemblyPath =>
{
var documentation = XmlDocumentationProvider.CreateFromFile(Path.ChangeExtension(assemblyPath, ".xml"));
return MetadataReference.CreateFromFile(assemblyPath, documentation: documentation);
}).ToArray();

// Act
var output = Verify(code, references: references);

// Assert
Assert.Contains("TupleLibrary", output.References.Keys);
Assert.Contains("TupleLibrary.XmlTasks", output.References.Keys);
}
}
36 changes: 34 additions & 2 deletions test/Docfx.Dotnet.Tests/GenerateMetadataFromVBUnitTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ public class GenerateMetadataFromVBUnitTest
{
private static readonly Dictionary<string, string> EmptyMSBuildProperties = new();

private static MetadataItem Verify(string code, ExtractMetadataConfig options = null, IDictionary<string, string> msbuildProperties = null, params MetadataReference[] references)
private static MetadataItem Verify(string code, ExtractMetadataConfig config = null, IDictionary<string, string> msbuildProperties = null, MetadataReference[] references = null)
{
var compilation = CompilationHelper.CreateCompilationFromVBCode(code, msbuildProperties ?? EmptyMSBuildProperties, "test.dll", references);
return compilation.Assembly.GenerateMetadataItem(compilation, options);
return compilation.Assembly.GenerateMetadataItem(compilation, config);
}

[Trait("Related", "Generic")]
Expand Down Expand Up @@ -1643,4 +1643,36 @@ End Namespace
Assert.Empty(foo.Items);
}
}

[Fact]
public void TestGenerateMetadataWithReference()
{
string code = @"
Namespace Test
Public Class Foo
Property Tasks As TupleLibrary.XmlTasks
End Class
End Namespace
namespace Test
{
public class Foo
{
public TupleLibrary.XmlTasks Tasks { get;set; }
}
}
";

var references = new string[] { "TestData/TupleLibrary.dll" }.Select(assemblyPath =>
{
var documentation = XmlDocumentationProvider.CreateFromFile(Path.ChangeExtension(assemblyPath, ".xml"));
return MetadataReference.CreateFromFile(assemblyPath, documentation: documentation);
}).ToArray();

// Act
var output = Verify(code, references: references);

// Assert
Assert.Contains("TupleLibrary", output.References.Keys);
Assert.Contains("TupleLibrary.XmlTasks", output.References.Keys);
}
}

0 comments on commit 2799e6f

Please sign in to comment.