diff --git a/src/Microsoft.DocAsCode.Metadata.ManagedReference/ExtractMetadataWorker.cs b/src/Microsoft.DocAsCode.Metadata.ManagedReference/ExtractMetadataWorker.cs index 258a6ab7367..ba60e6eb6e6 100644 --- a/src/Microsoft.DocAsCode.Metadata.ManagedReference/ExtractMetadataWorker.cs +++ b/src/Microsoft.DocAsCode.Metadata.ManagedReference/ExtractMetadataWorker.cs @@ -12,13 +12,12 @@ namespace Microsoft.DocAsCode.Metadata.ManagedReference using System.Threading.Tasks; using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.MSBuild; - using Microsoft.DotNet.ProjectModel.Workspaces; using Microsoft.DocAsCode.Common; using Microsoft.DocAsCode.DataContracts.Common; using Microsoft.DocAsCode.DataContracts.ManagedReference; using Microsoft.DocAsCode.Exceptions; + using Microsoft.DocAsCode.Metadata.ManagedReference.MSBuildWorkspaces; public sealed class ExtractMetadataWorker : IDisposable { @@ -33,7 +32,7 @@ public sealed class ExtractMetadataWorker : IDisposable private readonly Dictionary _msbuildProperties; //Lacks UT for shared workspace - private readonly Lazy _workspace; + private AdhocWorkspace _workspace; internal const string IndexFileName = ".manifest"; @@ -68,16 +67,6 @@ public ExtractMetadataWorker(ExtractMetadataInputModel input) { _msbuildProperties["Configuration"] = "Release"; } - - _workspace = new Lazy(() => - { - var workspace = MSBuildWorkspace.Create(_msbuildProperties); - workspace.WorkspaceFailed += (s, e) => - { - Logger.LogWarning($"Workspace failed with: {e.Diagnostic}"); - }; - return workspace; - }); } public async Task ExtractMetadataAsync() @@ -206,7 +195,7 @@ private async Task SaveAllMembersFromCacheAsync() foreach (var path in solutions) { documentCache.AddDocument(path, path); - var solution = await GetSolutionAsync(path); + var solution = GetSolution(path); if (solution != null) { foreach (var project in solution.Projects) @@ -235,15 +224,6 @@ private async Task SaveAllMembersFromCacheAsync() } } - if (_files.TryGetValue(FileType.ProjectJsonProject, out var pjp)) - { - await pjp.Select(s => s.NormalizedPath).ForEachInParallelAsync(path => - { - projectCache.GetOrAdd(path, s => GetProjectJsonProject(s)); - return Task.CompletedTask; - }, 60); - } - foreach (var item in projectCache) { var path = item.Key; @@ -900,14 +880,15 @@ private static Dictionary MergeYamlProjectReferences(List return result; } - private async Task GetSolutionAsync(string path) + private Solution GetSolution(string path) { try { Logger.LogVerbose($"Loading solution {path}", file: path); - var solution = await _workspace.Value.OpenSolutionAsync(path); - _workspace.Value.CloseSolution(); - return solution; + + _workspace = MSBuildWorkspace.FromSolutionFile(path, _msbuildProperties); + + return _workspace.CurrentSolution; } catch (Exception e) { @@ -923,15 +904,10 @@ private Project GetProject(ConcurrentDictionary cache, string p try { Logger.LogVerbose($"Loading project {s}", file: s); - var project = _workspace.Value.CurrentSolution.Projects.FirstOrDefault( - p => FilePathComparer.OSPlatformSensitiveRelativePathComparer.Equals(p.FilePath, s)); - - if (project != null) - { - return project; - } - - return _workspace.Value.OpenProjectAsync(s).Result; + + return _workspace.CurrentSolution.Projects.FirstOrDefault( + project => project.FilePath == path + ); } catch (AggregateException e) { @@ -946,21 +922,6 @@ private Project GetProject(ConcurrentDictionary cache, string p }); } - private Project GetProjectJsonProject(string path) - { - try - { - Logger.LogVerbose($"Loading project {path}", file: path); - var workspace = new ProjectJsonWorkspace(path); - return workspace.CurrentSolution.Projects.FirstOrDefault(p => p.FilePath == Path.GetFullPath(path)); - } - catch (Exception e) - { - Logger.Log(LogLevel.Warning, $"Error opening project {path}: {e.Message}. Ignored."); - return null; - } - } - /// /// use DFS to get topological sorted items /// diff --git a/src/Microsoft.DocAsCode.Metadata.ManagedReference/FileInformation.cs b/src/Microsoft.DocAsCode.Metadata.ManagedReference/FileInformation.cs index 32ccba79deb..043cfee7baa 100644 --- a/src/Microsoft.DocAsCode.Metadata.ManagedReference/FileInformation.cs +++ b/src/Microsoft.DocAsCode.Metadata.ManagedReference/FileInformation.cs @@ -46,10 +46,6 @@ private static FileType GetFileType(string filePath) { var extension = Path.GetExtension(filePath); var fileName = Path.GetFileName(filePath); - if (fileName.Equals("project.json", StringComparison.OrdinalIgnoreCase)) - { - return FileType.ProjectJsonProject; - } switch (extension.ToLowerInvariant()) { diff --git a/src/Microsoft.DocAsCode.Metadata.ManagedReference/FileInformationExtension.cs b/src/Microsoft.DocAsCode.Metadata.ManagedReference/FileInformationExtension.cs index 87886b61625..f4347fa8860 100644 --- a/src/Microsoft.DocAsCode.Metadata.ManagedReference/FileInformationExtension.cs +++ b/src/Microsoft.DocAsCode.Metadata.ManagedReference/FileInformationExtension.cs @@ -11,9 +11,6 @@ namespace Microsoft.DocAsCode.Metadata.ManagedReference internal static class FileInformationExtension { - public static bool IsSupportedProject(this FileInformation file) - { - return file.Type == FileType.Project || file.Type == FileType.ProjectJsonProject; - } + public static bool IsSupportedProject(this FileInformation file) => file.Type == FileType.Project; } } diff --git a/src/Microsoft.DocAsCode.Metadata.ManagedReference/FileType.cs b/src/Microsoft.DocAsCode.Metadata.ManagedReference/FileType.cs index 3f7e3e982ac..e578922e8e4 100644 --- a/src/Microsoft.DocAsCode.Metadata.ManagedReference/FileType.cs +++ b/src/Microsoft.DocAsCode.Metadata.ManagedReference/FileType.cs @@ -8,7 +8,6 @@ internal enum FileType NotSupported, Solution, Project, - ProjectJsonProject, VBSourceCode, CSSourceCode, Assembly, diff --git a/src/Microsoft.DocAsCode.Metadata.ManagedReference/Incremental/IncrementalCheck.cs b/src/Microsoft.DocAsCode.Metadata.ManagedReference/Incremental/IncrementalCheck.cs index 9f937749126..d6b6b38d60c 100644 --- a/src/Microsoft.DocAsCode.Metadata.ManagedReference/Incremental/IncrementalCheck.cs +++ b/src/Microsoft.DocAsCode.Metadata.ManagedReference/Incremental/IncrementalCheck.cs @@ -10,14 +10,11 @@ namespace Microsoft.DocAsCode.Metadata.ManagedReference using System.Linq; using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.MSBuild; using Microsoft.DocAsCode.Common; internal class IncrementalCheck { - private static readonly Lazy Workspace = new Lazy(() => MSBuildWorkspace.Create()); - private VersionStamp _versionToBeCompared; private ConcurrentDictionary _metadataVersionCache; diff --git a/src/Microsoft.DocAsCode.Metadata.ManagedReference/MSBuildWorkspaces/DotNetRuntimeInfo.cs b/src/Microsoft.DocAsCode.Metadata.ManagedReference/MSBuildWorkspaces/DotNetRuntimeInfo.cs new file mode 100644 index 00000000000..8a6b01284fb --- /dev/null +++ b/src/Microsoft.DocAsCode.Metadata.ManagedReference/MSBuildWorkspaces/DotNetRuntimeInfo.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.DocAsCode.Metadata.ManagedReference.MSBuildWorkspaces +{ + using System; + using System.Collections.Concurrent; + using System.Diagnostics; + + /// + /// Information about the .NET Core runtime. + /// + public class DotNetRuntimeInfo + { + /// + /// A cache of .NET runtime information by target directory. + /// + static readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + + /// + /// The .NET Core version. + /// + public string Version { get; set; } + + /// + /// The .NET Core base directory. + /// + public string BaseDirectory { get; set; } + + /// + /// The current runtime identifier (RID). + /// + public string RID { get; set; } + + /// + /// Get information about the current .NET Core runtime. + /// + /// + /// An optional base directory where dotnet.exe should be run (this may affect the version it reports due to global.json). + /// + /// + /// A containing the runtime information. + /// + public static DotNetRuntimeInfo GetCurrent(string baseDirectory = null) + { + return _cache.GetOrAdd(baseDirectory, _ => + { + DotNetRuntimeInfo runtimeInfo = new DotNetRuntimeInfo(); + + Process dotnetInfoProcess = Process.Start(new ProcessStartInfo + { + FileName = "dotnet", + WorkingDirectory = baseDirectory, + Arguments = "--info", + UseShellExecute = false, + RedirectStandardOutput = true + }); + using (dotnetInfoProcess) + { + dotnetInfoProcess.WaitForExit(); + + string currentSection = null; + string currentLine; + while ((currentLine = dotnetInfoProcess.StandardOutput.ReadLine()) != null) + { + if (String.IsNullOrWhiteSpace(currentLine)) + continue; + + if (!currentLine.StartsWith(" ")) + { + currentSection = currentLine; + + continue; + } + + string[] property = currentLine.Split(new char[] { ':' }, count: 2); + if (property.Length != 2) + continue; + + property[0] = property[0].Trim(); + property[1] = property[1].Trim(); + + switch (currentSection) + { + case "Product Information:": + { + switch (property[0]) + { + case "Version": + { + runtimeInfo.Version = property[1]; + + break; + } + } + + break; + } + case "Runtime Environment:": + { + switch (property[0]) + { + case "Base Path": + { + runtimeInfo.BaseDirectory = property[1]; + + break; + } + case "RID": + { + runtimeInfo.RID = property[1]; + + break; + } + } + + break; + } + } + } + } + + return runtimeInfo; + }); + } + + /// + /// Clear the cache of .NET runtime information. + /// + public static void ClearCache() + { + _cache.Clear(); + } + } +} diff --git a/src/Microsoft.DocAsCode.Metadata.ManagedReference/MSBuildWorkspaces/MSBuildHelper.cs b/src/Microsoft.DocAsCode.Metadata.ManagedReference/MSBuildWorkspaces/MSBuildHelper.cs new file mode 100644 index 00000000000..aa9504b403d --- /dev/null +++ b/src/Microsoft.DocAsCode.Metadata.ManagedReference/MSBuildWorkspaces/MSBuildHelper.cs @@ -0,0 +1,263 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.DocAsCode.Metadata.ManagedReference.MSBuildWorkspaces +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.IO; + + using MSB = Microsoft.Build.Evaluation; + using MSBC = Microsoft.Build.Construction; + + /// + /// Helper methods for working with MSBuild projects. + /// + public static class MSBuildHelper + { + /// + /// The names of well-known item metadata. + /// + public static readonly ImmutableSortedSet WellknownMetadataNames = + ImmutableSortedSet.Create( + "FullPath", + "RootDir", + "Filename", + "Extension", + "RelativeDir", + "Directory", + "RecursiveDir", + "Identity", + "ModifiedTime", + "CreatedTime", + "AccessedTime" + ); + + /// + /// Create an MSBuild project collection. + /// + /// + /// The base (i.e. solution) directory. + /// + /// + /// The project collection. + /// + public static MSB.ProjectCollection CreateProjectCollection(string solutionDirectory) + { + return CreateProjectCollection(solutionDirectory, + DotNetRuntimeInfo.GetCurrent(solutionDirectory) + ); + } + + /// + /// Create an MSBuild project collection. + /// + /// + /// The base (i.e. solution) directory. + /// + /// + /// Information about the current .NET Core runtime. + /// + /// + /// The project collection. + /// + public static MSB.ProjectCollection CreateProjectCollection(string solutionDirectory, DotNetRuntimeInfo runtimeInfo) + { + if (String.IsNullOrWhiteSpace(solutionDirectory)) + throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'baseDir'.", nameof(solutionDirectory)); + + if (runtimeInfo == null) + throw new ArgumentNullException(nameof(runtimeInfo)); + + if (String.IsNullOrWhiteSpace(runtimeInfo.BaseDirectory)) + throw new InvalidOperationException("Cannot determine base directory for .NET Core."); + + Dictionary globalProperties = CreateGlobalMSBuildProperties(runtimeInfo, solutionDirectory); + EnsureMSBuildEnvironment(globalProperties); + + var projectCollection = new MSB.ProjectCollection(globalProperties) { IsBuildEnabled = false }; + + // Override toolset paths (for some reason these point to the main directory where the dotnet executable lives). + var toolset = new MSB.Toolset( + toolsVersion: "15.0", + toolsPath: globalProperties["MSBuildExtensionsPath"], + projectCollection: projectCollection, + msbuildOverrideTasksPath: "" + ); + projectCollection.AddToolset(toolset); + + return projectCollection; + } + + /// + /// Create global properties for MSBuild. + /// + /// + /// Information about the current .NET Core runtime. + /// + /// + /// The base (i.e. solution) directory. + /// + /// + /// A dictionary containing the global properties. + /// + public static Dictionary CreateGlobalMSBuildProperties(DotNetRuntimeInfo runtimeInfo, string solutionDirectory) + { + if (runtimeInfo == null) + throw new ArgumentNullException(nameof(runtimeInfo)); + + if (String.IsNullOrWhiteSpace(solutionDirectory)) + throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'solutionDirectory'.", nameof(solutionDirectory)); + + if (solutionDirectory.Length > 0 && solutionDirectory[solutionDirectory.Length - 1] != Path.DirectorySeparatorChar) + solutionDirectory += Path.DirectorySeparatorChar; + + return new Dictionary + { + [WellKnownPropertyNames.DesignTimeBuild] = "true", + [WellKnownPropertyNames.BuildProjectReferences] = "false", + [WellKnownPropertyNames.ResolveReferenceDependencies] = "true", + [WellKnownPropertyNames.SolutionDir] = solutionDirectory, + [WellKnownPropertyNames.MSBuildExtensionsPath] = runtimeInfo.BaseDirectory, + [WellKnownPropertyNames.MSBuildSDKsPath] = Path.Combine(runtimeInfo.BaseDirectory, "Sdks"), + [WellKnownPropertyNames.RoslynTargetsPath] = Path.Combine(runtimeInfo.BaseDirectory, "Roslyn") + }; + } + + /// + /// Ensure that environment variables are populated using the specified MSBuild global properties. + /// + /// + /// The MSBuild global properties + /// + public static void EnsureMSBuildEnvironment(Dictionary globalMSBuildProperties) + { + if (globalMSBuildProperties == null) + throw new ArgumentNullException(nameof(globalMSBuildProperties)); + + // Kinda sucks that the simplest way to get MSBuild to resolve SDKs correctly is using environment variables, but there you go. + Environment.SetEnvironmentVariable( + WellKnownPropertyNames.MSBuildExtensionsPath, + globalMSBuildProperties[WellKnownPropertyNames.MSBuildExtensionsPath] + ); + Environment.SetEnvironmentVariable( + WellKnownPropertyNames.MSBuildSDKsPath, + globalMSBuildProperties[WellKnownPropertyNames.MSBuildSDKsPath] + ); + } + + /// + /// Does the specified property name represent a private property? + /// + /// + /// The property name. + /// + /// + /// true, if the property name starts with an underscore; otherwise, false. + /// + public static bool IsPrivateProperty(string propertyName) => propertyName?.StartsWith("_") ?? false; + + /// + /// Does the specified metadata name represent a private property? + /// + /// + /// The metadata name. + /// + /// + /// true, if the metadata name starts with an underscore; otherwise, false. + /// + public static bool IsPrivateMetadata(string metadataName) => metadataName?.StartsWith("_") ?? false; + + /// + /// Does the specified item type represent a private property? + /// + /// + /// The item type. + /// + /// + /// true, if the item type starts with an underscore; otherwise, false. + /// + public static bool IsPrivateItemType(string itemType) => itemType?.StartsWith("_") ?? false; + + /// + /// Determine whether the specified metadata name represents well-known (built-in) item metadata. + /// + /// + /// The metadata name. + /// + /// + /// true, if represents well-known item metadata; otherwise, false. + /// + public static bool IsWellKnownItemMetadata(string metadataName) => WellknownMetadataNames.Contains(metadataName); + + /// + /// Create a copy of the project for caching. + /// + /// + /// The MSBuild project. + /// + /// + /// The project copy (independent of original, but sharing the same ). + /// + /// + /// You can only create a single cached copy for a given project. + /// + public static MSB.Project CloneAsCachedProject(this MSB.Project project) + { + if (project == null) + throw new ArgumentNullException(nameof(project)); + + MSBC.ProjectRootElement clonedXml = project.Xml.DeepClone(); + var clonedProject = new MSB.Project(clonedXml, project.GlobalProperties, project.ToolsVersion, project.ProjectCollection) + { + FullPath = Path.ChangeExtension(project.FullPath, + ".cached" + Path.GetExtension(project.FullPath) + ) + }; + + return clonedProject; + } + + /// + /// The names of well-known MSBuild properties. + /// + public static class WellKnownPropertyNames + { + /// + /// The "MSBuildExtensionsPath" property. + /// + public static readonly string MSBuildExtensionsPath = "MSBuildExtensionsPath"; + + /// + /// The "MSBuildSDKsPath" property. + /// + public static readonly string MSBuildSDKsPath = "MSBuildSDKsPath"; + + /// + /// The "SolutionDir" property. + /// + public static readonly string SolutionDir = "SolutionDir"; + + /// + /// The "_ResolveReferenceDependencies" property. + /// + public static readonly string ResolveReferenceDependencies = "_ResolveReferenceDependencies"; + + /// + /// The "DesignTimeBuild" property. + /// + public static readonly string DesignTimeBuild = "DesignTimeBuild"; + + /// + /// The "BuildProjectReferences" property. + /// + public static readonly string BuildProjectReferences = "BuildProjectReferences"; + + /// + /// The "RoslynTargetsPath" property. + /// + public static readonly string RoslynTargetsPath = "RoslynTargetsPath"; + } + } +} diff --git a/src/Microsoft.DocAsCode.Metadata.ManagedReference/MSBuildWorkspaces/MSBuildWorkspace.cs b/src/Microsoft.DocAsCode.Metadata.ManagedReference/MSBuildWorkspaces/MSBuildWorkspace.cs new file mode 100644 index 00000000000..82a7b580408 --- /dev/null +++ b/src/Microsoft.DocAsCode.Metadata.ManagedReference/MSBuildWorkspaces/MSBuildWorkspace.cs @@ -0,0 +1,179 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.DocAsCode.Metadata.ManagedReference.MSBuildWorkspaces +{ + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.Text; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + + using MSB = Microsoft.Build.Evaluation; + using MSBC = Microsoft.Build.Construction; + using MSBF = Microsoft.Build.Framework; + using MSBX = Microsoft.Build.Execution; + + /// + /// Generates a Roslyn from an MSBuild project. + /// + public static class MSBuildWorkspace + { + /// + /// Generate a Roslyn from a solution (.sln) file. + /// + /// + /// The full path to the solution file. + /// + /// + /// An optional containing global MSBuild properties used to configure the underlying project collection. + /// + /// + /// The configured . + /// + public static AdhocWorkspace FromSolutionFile(string solutionPath, IDictionary msbuildProperties = null) + { + if (string.IsNullOrWhiteSpace(solutionPath)) + throw new ArgumentException($"Argument cannot be null, empty, or entirely composed of whitespace: {nameof(solutionPath)}.", nameof(solutionPath)); + + return FromSolutionFile( + new FileInfo(solutionPath) + ); + } + + /// + /// Generate a Roslyn from a solution (.sln) file. + /// + /// + /// A representing the solution file. + /// + /// + /// An optional containing global MSBuild properties used to configure the underlying project collection. + /// + /// + /// The configured . + /// + public static AdhocWorkspace FromSolutionFile(FileInfo solutionFile, IDictionary msbuildProperties = null) + { + if (solutionFile == null) + throw new ArgumentNullException(nameof(solutionFile)); + + AdhocWorkspace workspace = new AdhocWorkspace(); + + Solution solution = workspace.AddSolution(SolutionInfo.Create( + SolutionId.CreateNewId(), + VersionStamp.Create(), + filePath: solutionFile.FullName + )); + + MSBC.SolutionFile msbuildSolution = MSBC.SolutionFile.Parse(solutionFile.FullName); + + MSB.ProjectCollection projectCollection = MSBuildHelper.CreateProjectCollection( + solutionDirectory: solutionFile.Directory.FullName + ); + if (msbuildProperties != null) + { + foreach (string propertyName in msbuildProperties.Keys) + { + string propertyValue = msbuildProperties[propertyName]; + projectCollection.SetGlobalProperty(propertyName, propertyValue); + } + } + + foreach (var solutionProject in msbuildSolution.ProjectsInOrder) + { + var msbuildProject = projectCollection.LoadProject(solutionProject.AbsolutePath); + + string language; + if (solutionProject.AbsolutePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase)) + language = "C#"; + else if (solutionProject.AbsolutePath.EndsWith(".vbproj", StringComparison.OrdinalIgnoreCase)) + language = "VB"; + else + continue; + + var projectId = ProjectId.CreateNewId(); + + List projectDocuments = new List(); + foreach (MSB.ProjectItem item in msbuildProject.GetItems("Compile")) + { + string itemPath = item.GetMetadataValue("FullPath"); + + projectDocuments.Add( + DocumentInfo.Create( + DocumentId.CreateNewId(projectId), + name: Path.GetFileName(itemPath), + filePath: itemPath, + loader: TextLoader.From( + TextAndVersion.Create( + SourceText.From(File.ReadAllText(itemPath)), + VersionStamp.Create() + ) + ) + ) + ); + } + + List references = new List(); + foreach (string assemblyPath in ResolveReferences(msbuildProject)) + { + references.Add( + MetadataReference.CreateFromFile(assemblyPath, MetadataReferenceProperties.Assembly) + ); + } + + solution = solution.AddProject(ProjectInfo.Create( + projectId, + VersionStamp.Create(), + name: Path.GetFileNameWithoutExtension(msbuildProject.FullPath), + assemblyName: Path.GetFileNameWithoutExtension(msbuildProject.FullPath), + language: language, + filePath: msbuildProject.FullPath, + outputFilePath: msbuildProject.GetPropertyValue("TargetPath"), + documents: projectDocuments, + metadataReferences: references + )); + + var project = solution.GetProject(projectId); + solution = solution.WithProjectCompilationOptions(projectId, project.CompilationOptions.WithSpecificDiagnosticOptions( + new Dictionary + { + ["CS1701"] = ReportDiagnostic.Suppress + } + )); + + workspace.TryApplyChanges(solution); + } + + return workspace; + } + + /// + /// Resolve referenced assembly paths from the specified project. + /// + /// + /// The MSBuild project to examine. + /// + /// + /// A sequence of referenced assembly paths. + /// + static IEnumerable ResolveReferences(MSB.Project msbuildProject) + { + MSBX.ProjectInstance snapshot = msbuildProject.CreateProjectInstance(); + + IDictionary outputs; + if (!snapshot.Build(new string[] { "ResolveAssemblyReferences" }, null, out outputs)) + yield break; + + foreach (string targetName in outputs.Keys) + { + MSBX.TargetResult targetResult = outputs[targetName]; + MSBF.ITaskItem[] items = targetResult.Items.ToArray(); + + foreach (MSBF.ITaskItem item in items) + yield return item.GetMetadata("FullPath"); + } + } + } +} diff --git a/src/Microsoft.DocAsCode.Metadata.ManagedReference/Microsoft.DocAsCode.Metadata.ManagedReference.csproj b/src/Microsoft.DocAsCode.Metadata.ManagedReference/Microsoft.DocAsCode.Metadata.ManagedReference.csproj index 18b175734bb..c899ab4793f 100644 --- a/src/Microsoft.DocAsCode.Metadata.ManagedReference/Microsoft.DocAsCode.Metadata.ManagedReference.csproj +++ b/src/Microsoft.DocAsCode.Metadata.ManagedReference/Microsoft.DocAsCode.Metadata.ManagedReference.csproj @@ -1,5 +1,5 @@  - + @@ -23,7 +23,6 @@ -