private async Task SaveAllMembersFromCacheAsync() { var forceRebuild = _rebuild; var outputFolder = _outputFolder; var projectCache = new ConcurrentDictionary <string, AbstractProject>(); // Project<=>Documents var documentCache = new ProjectDocumentCache(); var projectDependencyGraph = new ConcurrentDictionary <string, List <string> >(); DateTime triggeredTime = DateTime.UtcNow; // Exclude not supported files from inputs var cacheKey = GetCacheKey(_files.SelectMany(s => s.Value)); Logger.LogInfo("Loading projects..."); if (_files.TryGetValue(FileType.Solution, out var sln)) { var solutions = sln.Select(s => s.NormalizedPath); // No matter is incremental or not, we have to load solutions into memory foreach (var path in solutions) { using (new LoggerFileScope(path)) { documentCache.AddDocument(path, path); var solution = await GetSolutionAsync(path); if (solution != null) { foreach (var project in solution.Projects) { var projectFile = new FileInformation(project.FilePath); // If the project is supported, add to project dictionary, otherwise, ignore if (projectFile.Type == FileType.Project) { projectCache.GetOrAdd(projectFile.NormalizedPath, s => _loader.Load(projectFile.NormalizedPath)); } else { Logger.LogWarning($"Project {projectFile.RawPath} inside solution {path} is ignored, supported projects are csproj, fsproj and vbproj."); } } } } } } if (_files.TryGetValue(FileType.Project, out var p)) { foreach (var pp in p) { GetProject(projectCache, pp.NormalizedPath); } } foreach (var item in projectCache) { var path = item.Key; var project = item.Value; documentCache.AddDocument(path, path); if (project.HasDocuments) { documentCache.AddDocuments(path, project.Documents.Select(s => s.FilePath)); } else { Logger.Log(LogLevel.Warning, $"Project '{project.FilePath}' does not contain any documents."); } documentCache.AddDocuments(path, project.PortableExecutableMetadataReferences); FillProjectDependencyGraph(projectCache, projectDependencyGraph, project); } var csFiles = new List <string>(); var vbFiles = new List <string>(); var assemblyFiles = new List <string>(); if (_files.TryGetValue(FileType.CSSourceCode, out var cs)) { csFiles.AddRange(cs.Select(s => s.NormalizedPath)); documentCache.AddDocuments(csFiles); } if (_files.TryGetValue(FileType.VBSourceCode, out var vb)) { vbFiles.AddRange(vb.Select(s => s.NormalizedPath)); documentCache.AddDocuments(vbFiles); } if (_files.TryGetValue(FileType.Assembly, out var asm)) { assemblyFiles.AddRange(asm.Select(s => s.NormalizedPath)); documentCache.AddDocuments(assemblyFiles); } // Incremental check for inputs as a whole: var applicationCache = ApplicationLevelCache.Get(cacheKey); var options = _options; if (!forceRebuild) { var buildInfo = applicationCache.GetValidConfig(cacheKey); if (buildInfo != null) { IncrementalCheck check = new IncrementalCheck(buildInfo); if (!options.HasChanged(check, true)) { // 1. Check if sln files/ project files and its contained documents/ source files are modified var projectModified = check.AreFilesModified(documentCache.Documents); if (!projectModified) { // 2. Check if documents/ assembly references are changed in a project // e.g. <Compile Include="*.cs* /> and file added/deleted foreach (var project in projectCache.Values) { var key = StringExtension.ToNormalizedFullPath(project.FilePath); IEnumerable <string> currentContainedFiles = documentCache.GetDocuments(project.FilePath); var previousDocumentCache = new ProjectDocumentCache(buildInfo.ContainedFiles); IEnumerable <string> previousContainedFiles = previousDocumentCache.GetDocuments(project.FilePath); if (previousContainedFiles != null && currentContainedFiles != null) { projectModified = !previousContainedFiles.SequenceEqual(currentContainedFiles); } else { // When one of them is not null, project is modified if (!object.Equals(previousContainedFiles, currentContainedFiles)) { projectModified = true; } } if (projectModified) { break; } } } if (!projectModified) { // Nothing modified, use the result in cache try { CopyFromCachedResult(buildInfo, cacheKey, outputFolder); return; } catch (Exception e) { Logger.Log(LogLevel.Warning, $"Unable to copy results from cache: {e.Message}. Rebuild starts."); } } } } } Logger.LogInfo("Generating metadata for each project..."); // Build all the projects to get the output and save to cache List <MetadataItem> projectMetadataList = new List <MetadataItem>(); ConcurrentDictionary <string, bool> projectRebuildInfo = new ConcurrentDictionary <string, bool>(); ConcurrentDictionary <string, AbstractCompilation> compilationCache = await GetProjectCompilationAsync(projectCache); var roslynProjects = compilationCache.Values.OfType <RoslynCompilation>().Select(rc => rc.Compilation); options.RoslynExtensionMethods = RoslynIntermediateMetadataExtractor.GetAllExtensionMethodsFromCompilation(roslynProjects); foreach (var key in GetTopologicalSortedItems(projectDependencyGraph)) { var dependencyRebuilt = projectDependencyGraph[key].Any(r => projectRebuildInfo[r]); var k = documentCache.GetDocuments(key); var input = new ProjectFileInputParameters(options, k, key, dependencyRebuilt); var controller = compilationCache[key].GetBuildController(); var projectMetadataResult = GetMetadataFromProjectLevelCache(controller, input); var projectMetadata = projectMetadataResult.Item1; if (projectMetadata != null) { projectMetadataList.Add(projectMetadata); } projectRebuildInfo[key] = projectMetadataResult.Item2; } if (csFiles.Count > 0) { var csContent = string.Join(Environment.NewLine, csFiles.Select(s => File.ReadAllText(s))); var csCompilation = CompilationUtility.CreateCompilationFromCsharpCode(csContent); if (csCompilation != null) { var input = new SourceFileInputParameters(options, csFiles); var controller = new RoslynSourceFileBuildController(csCompilation); var csMetadata = GetMetadataFromProjectLevelCache(controller, input); if (csMetadata != null) { projectMetadataList.Add(csMetadata.Item1); } } } if (vbFiles.Count > 0) { var vbContent = string.Join(Environment.NewLine, vbFiles.Select(s => File.ReadAllText(s))); var vbCompilation = CompilationUtility.CreateCompilationFromVBCode(vbContent); if (vbCompilation != null) { var input = new SourceFileInputParameters(options, vbFiles); var controller = new RoslynSourceFileBuildController(vbCompilation); var vbMetadata = GetMetadataFromProjectLevelCache(controller, input); if (vbMetadata != null) { projectMetadataList.Add(vbMetadata.Item1); } } } if (assemblyFiles.Count > 0) { var assemblyCompilation = CompilationUtility.CreateCompilationFromAssembly( assemblyFiles.Concat(_references ?? Enumerable.Empty <string>())); if (assemblyCompilation != null) { var commentFiles = (from file in assemblyFiles select Path.ChangeExtension(file, XmlCommentFileExtension) into xmlFile where File.Exists(xmlFile) select xmlFile).ToList(); var referencedAssemblyList = CompilationUtility.GetAssemblyFromAssemblyComplation(assemblyCompilation, assemblyFiles).ToList(); // TODO: why not merge with compilation's extension methods? var assemblyExtension = RoslynIntermediateMetadataExtractor.GetAllExtensionMethodsFromAssembly(assemblyCompilation, referencedAssemblyList.Select(s => s.assembly)); options.RoslynExtensionMethods = assemblyExtension; foreach (var(reference, assembly) in referencedAssemblyList) { var input = new AssemblyFileInputParameters(options, reference.Display); var controller = new RoslynSourceFileBuildController(assemblyCompilation, assembly); var mta = GetMetadataFromProjectLevelCache(controller, input); if (mta != null) { MergeCommentsHelper.MergeComments(mta.Item1, commentFiles); projectMetadataList.Add(mta.Item1); } } } } Dictionary <string, MetadataItem> allMembers; Dictionary <string, ReferenceItem> allReferences; using (new PerformanceScope("MergeMetadata")) { allMembers = MergeYamlProjectMetadata(projectMetadataList); } using (new PerformanceScope("MergeReference")) { allReferences = MergeYamlProjectReferences(projectMetadataList); } if (allMembers == null || allMembers.Count == 0) { var value = StringExtension.ToDelimitedString(projectMetadataList.Select(s => s.Name)); Logger.Log(LogLevel.Warning, $"No metadata is generated for {value}."); applicationCache.SaveToCache(cacheKey, null, triggeredTime, outputFolder, null, options); } else { // TODO: need an intermediate folder? when to clean it up? // Save output to output folder List <string> outputFiles; using (new PerformanceScope("ResolveAndExport")) { outputFiles = ResolveAndExportYamlMetadata(allMembers, allReferences, outputFolder, options.PreserveRawInlineComments, options.ShouldSkipMarkup, _useCompatibilityFileName).ToList(); } applicationCache.SaveToCache(cacheKey, documentCache.Cache, triggeredTime, outputFolder, outputFiles, options); } }
public MetadataItem ExtractMetadata(IInputParameters parameters) { var extractor = new RoslynIntermediateMetadataExtractor(this); return(extractor.Extract(parameters)); }