diff --git a/src/dotnet/APIView/APIView/Languages/CompilationFactory.cs b/src/dotnet/APIView/APIView/Languages/CompilationFactory.cs index 8ae475a7d50..62f9a6fcd90 100644 --- a/src/dotnet/APIView/APIView/Languages/CompilationFactory.cs +++ b/src/dotnet/APIView/APIView/Languages/CompilationFactory.cs @@ -13,7 +13,9 @@ public static class CompilationFactory { private static HashSet AllowedAssemblies = new HashSet(new[] { - "Microsoft.Bcl.AsyncInterfaces" + "Microsoft.Bcl.AsyncInterfaces", + "System.ClientModel" + }, StringComparer.InvariantCultureIgnoreCase); public static IAssemblySymbol GetCompilation(string file) @@ -24,7 +26,7 @@ public static IAssemblySymbol GetCompilation(string file) } } - public static IAssemblySymbol GetCompilation(Stream stream, Stream documentationStream) + public static IAssemblySymbol GetCompilation(Stream stream, Stream documentationStream, IEnumerable dependencyPaths = null) { PortableExecutableReference reference; @@ -59,6 +61,17 @@ public static IAssemblySymbol GetCompilation(Stream stream, Stream documentation compilation = compilation.AddReferences(MetadataReference.CreateFromFile(tpl)); } } + if (dependencyPaths != null) + { + foreach (var dependencyFile in dependencyPaths) + { + if (!File.Exists(dependencyFile) || !AllowedAssemblies.Contains(Path.GetFileNameWithoutExtension(dependencyFile))) + { + continue; + } + compilation = compilation.AddReferences(MetadataReference.CreateFromFile(dependencyFile)); + } + } return (IAssemblySymbol)compilation.GetAssemblyOrModuleSymbol(reference); } diff --git a/src/dotnet/APIView/APIViewWeb/APIViewWeb.csproj b/src/dotnet/APIView/APIViewWeb/APIViewWeb.csproj index 9957e6ba219..70d91dba9b6 100644 --- a/src/dotnet/APIView/APIViewWeb/APIViewWeb.csproj +++ b/src/dotnet/APIView/APIViewWeb/APIViewWeb.csproj @@ -57,6 +57,7 @@ + diff --git a/src/dotnet/APIView/APIViewWeb/Languages/CSharpLanguageService.cs b/src/dotnet/APIView/APIViewWeb/Languages/CSharpLanguageService.cs index 133a3d1ce55..52ab9d51d59 100644 --- a/src/dotnet/APIView/APIViewWeb/Languages/CSharpLanguageService.cs +++ b/src/dotnet/APIView/APIViewWeb/Languages/CSharpLanguageService.cs @@ -7,9 +7,15 @@ using System.IO.Compression; using System.Linq; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using ApiView; +using NuGet.Common; +using NuGet.Packaging; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; namespace APIViewWeb { @@ -44,9 +50,10 @@ public override bool CanUpdate(string versionString) return versionString != CodeFileBuilder.CurrentVersion; } - public override Task GetCodeFileAsync(string originalName, Stream stream, bool runAnalysis) + public override async Task GetCodeFileAsync(string originalName, Stream stream, bool runAnalysis) { ZipArchive archive = null; + string dependencyFilesTempDir = null; try { Stream dllStream = stream; @@ -62,7 +69,7 @@ public override Task GetCodeFileAsync(string originalName, Stream stre var dllEntries = archive.Entries.Where(entry => IsDll(entry.Name)).ToArray(); if (dllEntries.Length == 0) { - return Task.FromResult(GetDummyReviewCodeFile(originalName, dependencies)); + return GetDummyReviewCodeFile(originalName, dependencies); } var dllEntry = dllEntries.First(); @@ -99,17 +106,24 @@ public override Task GetCodeFileAsync(string originalName, Stream stre } } - var assemblySymbol = CompilationFactory.GetCompilation(dllStream, docStream); + dependencyFilesTempDir = await ExtractNugetDependencies(dependencies).ConfigureAwait(false); + var dependencyFilePaths = Directory.EnumerateFiles(dependencyFilesTempDir, "*.dll", SearchOption.AllDirectories); + + var assemblySymbol = CompilationFactory.GetCompilation(dllStream, docStream, dependencyFilePaths); if (assemblySymbol == null) { - return Task.FromResult(GetDummyReviewCodeFile(originalName, dependencies)); + return GetDummyReviewCodeFile(originalName, dependencies); } - return Task.FromResult(new CodeFileBuilder().Build(assemblySymbol, runAnalysis, dependencies)); + return new CodeFileBuilder().Build(assemblySymbol, runAnalysis, dependencies); } finally { archive?.Dispose(); + if (dependencyFilesTempDir != null && Directory.Exists(dependencyFilesTempDir)) + { + Directory.Delete(dependencyFilesTempDir, true); + } } } @@ -132,7 +146,7 @@ private CodeFile GetDummyReviewCodeFile(string originalName, List + /// Resolves the NuGet package dependencies and extracts them to a temporary folder. It is the responsibility of teh caller to clean up the folder. + /// + /// The dependency infos + /// A temporary path where the dependency files were extracted. + private async Task ExtractNugetDependencies(List dependencyInfos) + { + string tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + SourceCacheContext cache = new SourceCacheContext(); + SourceRepository repository = NuGet.Protocol.Core.Types.Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json"); + try + { + FindPackageByIdResource resource = await repository.GetResourceAsync().ConfigureAwait(false); + foreach (var dep in dependencyInfos) + { + using (MemoryStream packageStream = new MemoryStream()) + { + if (await resource.CopyNupkgToStreamAsync( + dep.Name, + new NuGetVersion(dep.Version), + packageStream, + cache, + NullLogger.Instance, + CancellationToken.None)) + { + using PackageArchiveReader reader = new PackageArchiveReader(packageStream); + NuspecReader nuspec = reader.NuspecReader; + var file = reader.GetFiles().FirstOrDefault(f => f.EndsWith(dep.Name + ".dll")); + if (file != null) + { + var fileInfo = new FileInfo(file); + var path = Path.Combine(tempFolder, dep.Name, fileInfo.Name); + var tmp = reader.ExtractFile(file, path, NullLogger.Instance); + } + } + } + } + } + finally + { + cache.Dispose(); + } + return tempFolder; + } } }