public string[] Generate() { var globalExecution = Stopwatch.StartNew(); var details = ProjectLoader.LoadProjectDetails(_environment); if (this.Log().IsEnabled(LogLevel.Debug)) { this.Log().Debug($"Got project details after {globalExecution.Elapsed}"); } if (!details.Generators.Any()) { this.Log().Info($"No generators were found."); return(new string[0]); } var compilationResult = GetCompilation().Result; if (this.Log().IsEnabled(LogLevel.Debug)) { this.Log().Debug($"Got compilation after {globalExecution.Elapsed}"); } // Build dependencies graph var generatorsByName = details.Generators.ToDictionary(g => g.generatorType.FullName); var generatorNames = generatorsByName.Keys; // Dependencies list, in the form (before, after) var afterGenerators = details.Generators .Select(g => g.generatorType) .SelectMany(t => t.GetCustomAttributes <GenerateAfterAttribute>() .Select(a => a.GeneratorToExecuteBefore) .Intersect(generatorNames, StringComparer.InvariantCultureIgnoreCase) .Select(dependency => ((string incoming, string outgoing))(t.FullName, dependency))); var beforeGenerators = details.Generators .Select(g => g.generatorType) .SelectMany(t => t.GetCustomAttributes <GenerateBeforeAttribute>() .Select(a => a.GeneratorToExecuteAfter) .Intersect(generatorNames, StringComparer.InvariantCultureIgnoreCase) .Select(dependent => ((string incoming, string outgoing))(dependent, t.FullName))); var dependencies = afterGenerators.Concat(beforeGenerators) .Where(x => x.incoming != x.outgoing) .Distinct() .ToList(); if (dependencies.Any()) { this.Log().Info($"Generators Ordering restrictions:\n\t{dependencies.Select(d => $"{d.incoming} -> {d.outgoing}").JoinBy("\n\t")}"); } var groupedGenerators = generatorNames.GroupSort(dependencies); if (groupedGenerators == null) { this.Log().Error("There is a cyclic ordering in the generators. You need to fix it. You may need to set your build output to 'normal' to see dependencies list."); return(new string[0]); } if (dependencies.Any()) { this.Log().Info($"**Generators Execution Plan**\n\tConcurrently: {groupedGenerators.Select(grp=>grp.JoinBy(", ")).JoinBy("\n\tFollowed by: ")}"); } // Run var output = new List <string>(); (string filePath, string content)[] generatedFilesAndContent = null;
public string[] Generate() { var globalExecution = Stopwatch.StartNew(); var details = ProjectLoader.LoadProjectDetails(_environment); if (this.Log().IsEnabled(LogLevel.Debug)) { this.Log().Debug($"Got project details after {globalExecution.Elapsed}"); } if (!details.Generators.Any()) { this.Log().Info($"No generators were found."); return(new string[0]); } var compilationResult = GetCompilation(details).Result; if (this.Log().IsEnabled(LogLevel.Debug)) { this.Log().Debug($"Got compilation after {globalExecution.Elapsed}"); } var generatorResults = details.Generators .AsParallel() .Select(generatorDef => { var generator = generatorDef.builder(); try { var context = new InternalSourceGeneratorContext(compilationResult.Item1, compilationResult.Item2); context.SetProjectInstance(details.ExecutedProject); var w = Stopwatch.StartNew(); generator.Execute(context); if (this.Log().IsEnabled(LogLevel.Debug)) { this.Log().Debug($"Ran {w.Elapsed} for [{generator.GetType()}]"); } return(new { Generator = generator, Context = context }); } catch (Exception e) { // Wrap the exception into a string to avoid serialization issue when // parts of the stack are coming from an assembly the msbuild task is // not able to load properly. throw new InvalidOperationException($"Generation failed for {generator.GetType()}. {e}"); } }) .ToArray(); var files = from result in generatorResults from tree in result.Context.Trees select new { FilePath = Path.Combine(_environment.OutputPath ?? details.IntermediatePath, BuildTreeFileName(result.Generator, tree.Key)), Content = tree.Value }; files = files.ToArray(); foreach (var file in files) { Directory.CreateDirectory(Path.GetDirectoryName(file.FilePath)); if (File.Exists(file.FilePath)) { if (File.ReadAllText(file.FilePath).SequenceEqual(file.Content)) { if (this.Log().IsEnabled(LogLevel.Information)) { this.Log().Info($"Skipping generated file with same content: {file.FilePath}"); } continue; } else { if (this.Log().IsEnabled(LogLevel.Information)) { this.Log().Info($"Overwriting generated file with different content: {file.FilePath}"); } } } File.WriteAllText(file.FilePath, file.Content); } if (this.Log().IsEnabled(LogLevel.Debug)) { this.Log().Debug($"Code generation ran for {globalExecution.Elapsed}"); } return(files.Select(f => f.FilePath).ToArray()); }