Esempio n. 1
0
        private Tuple <MetadataItem, bool> GetMetadataFromProjectLevelCache(IBuildController controller, IInputParameters key)
        {
            DateTime triggeredTime     = DateTime.UtcNow;
            var      projectLevelCache = key.Cache;
            var      projectConfig     = key.BuildInfo;
            var      rebuildProject    = _rebuild || projectConfig == null || key.HasChanged(projectConfig);

            MetadataItem projectMetadata;

            if (!rebuildProject)
            {
                // Load from cache
                var cacheFile = Path.Combine(projectConfig.OutputFolder, projectConfig.RelativeOutputFiles.First());
                Logger.Log(LogLevel.Info, $"'{projectConfig.InputFilesKey}' keep up-to-date since '{projectConfig.TriggeredUtcTime.ToString()}', cached intermediate result '{cacheFile}' is used.");
                if (TryParseYamlMetadataFile(cacheFile, out projectMetadata))
                {
                    return(Tuple.Create(projectMetadata, rebuildProject));
                }
                else
                {
                    Logger.Log(LogLevel.Info, $"'{projectConfig.InputFilesKey}' is invalid, rebuild needed.");
                }
            }

            projectMetadata = new IntermediateMetadataExtractor(controller).Extract(key);
            var file = Path.GetRandomFileName();
            var cacheOutputFolder = projectLevelCache.OutputFolder;
            var path = Path.Combine(cacheOutputFolder, file);

            YamlUtility.Serialize(path, projectMetadata);
            Logger.Log(LogLevel.Verbose, $"Successfully generated metadata {cacheOutputFolder} for {projectMetadata.Name}");

            // Save to cache
            projectLevelCache.SaveToCache(key.Key, key.Files, triggeredTime, cacheOutputFolder, new List <string>()
            {
                file
            }, key.Options);

            return(Tuple.Create(projectMetadata, rebuildProject));
        }
Esempio n. 2
0
        private async Task SaveAllMembersFromCacheAsync()
        {
            var forceRebuild = _rebuild;
            var outputFolder = _outputFolder;

            var projectCache = new ConcurrentDictionary <string, Project>();

            // 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 csproj/vbproj, add to project dictionary, otherwise, ignore
                                if (projectFile.IsSupportedProject())
                                {
                                    projectCache.GetOrAdd(projectFile.NormalizedPath, s => project);
                                }
                                else
                                {
                                    Logger.LogWarning($"Project {projectFile.RawPath} inside solution {path} is ignored, supported projects are csproj and vbproj.");
                                }
                            }
                        }
                    }
                }
            }

            if (_files.TryGetValue(FileType.Project, out var p))
            {
                foreach (var pp in p)
                {
                    GetProject(projectCache, pp.NormalizedPath);
                }
            }

            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;
                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.MetadataReferences
                                           .Where(s => s is PortableExecutableReference)
                                           .Select(s => ((PortableExecutableReference)s).FilePath));
                FillProjectDependencyGraph(projectCache, projectDependencyGraph, project);
                // duplicate project references will fail Project.GetCompilationAsync
                var groups = project.ProjectReferences.GroupBy(r => r);
                if (groups.Any(g => g.Count() > 1))
                {
                    projectCache[path] = project.WithProjectReferences(groups.Select(g => g.Key));
                }
            }

            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, Compilation> compilationCache   = await GetProjectCompilationAsync(projectCache);

            var extensionMethods = IntermediateMetadataExtractor.GetAllExtensionMethodsFromCompilation(compilationCache.Values);

            options.ExtensionMethods = extensionMethods;
            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 = new SourceFileBuildController(compilationCache[key]);

                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 SourceFileBuildController(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 SourceFileBuildController(vbCompilation);

                    var vbMetadata = GetMetadataFromProjectLevelCache(controller, input);
                    if (vbMetadata != null)
                    {
                        projectMetadataList.Add(vbMetadata.Item1);
                    }
                }
            }

            if (assemblyFiles.Count > 0)
            {
                var assemblyCompilation = CompilationUtility.CreateCompilationFromAssembly(assemblyFiles);
                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).ToList();
                    // TODO: why not merge with compilation's extension methods?
                    var assemblyExtension = IntermediateMetadataExtractor.GetAllExtensionMethodsFromAssembly(assemblyCompilation, referencedAssemblyList.Select(s => s.Item2));
                    options.ExtensionMethods = assemblyExtension;
                    foreach (var assembly in referencedAssemblyList)
                    {
                        var input      = new AssemblyFileInputParameters(options, assembly.Item1.Display);
                        var controller = new SourceFileBuildController(assemblyCompilation, assembly.Item2);

                        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);
            }
        }