-
Notifications
You must be signed in to change notification settings - Fork 24
/
SourceGenerator.cs
146 lines (124 loc) · 6.29 KB
/
SourceGenerator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using NTypewriter.CodeModel;
using NTypewriter.CodeModel.Functions;
using NTypewriter.CodeModel.Roslyn;
using NTypewriter.Editor.Config;
using NTypewriter.Runtime;
using NTypewriter.SourceGenerator.Adapters;
using Scriban;
namespace NTypewriter.SourceGenerator
{
[Generator]
public class NTypewriterSourceGenerator : ISourceGenerator
{
private static readonly ConcurrentDictionary<(string AssemblyName, string OutputDir), GeneratorInfo> AssemblyGeneratorInfo = new ConcurrentDictionary<(string, string), GeneratorInfo>();
private static readonly string Version = typeof(NTypewriterSourceGenerator).Assembly.GetName().Version.ToString();
private static readonly object Padlock = new Object();
static NTypewriterSourceGenerator()
{
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolver.Resolve;
}
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForPostInitialization(PostInitializationContext);
}
public void PostInitializationContext(GeneratorPostInitializationContext context)
{
var paths = new string[]
{
$"// NTypewriterSourceGenerator : {typeof(NTypewriterSourceGenerator).Assembly.Location}",
$"// NTypewriter : {typeof(NTypeWriter).Assembly.Location}",
$"// NTypewriter.CodeModel : {typeof(ICodeModel).Assembly.Location}",
$"// NTypewriter.CodeModel.Functions : {typeof(ActionFunctions).Assembly.Location}",
$"// NTypewriter.CodeModel.Roslyn : {typeof(CodeModelConfiguration).Assembly.Location}",
$"// NTypewriter.Editor.Config : {typeof(EditorConfig).Assembly.Location}",
$"// NTypewriter.Runtime : {typeof(RenderTemplatesCommand).Assembly.Location}",
$"// Scriban.Signed : {typeof(Template).Assembly.Location}"
};
var allPaths = String.Join("\r\n", paths);
context.AddSource("Diagnostics.g.cs", allPaths);
}
public void Execute(GeneratorExecutionContext context)
{
var info = GetGeneratorInfo(context);
Interlocked.Increment(ref info.ExecuteCount);
info.LastTouch = File.GetLastWriteTime(info.TouchFilePath);
var thisRunId = DateTime.Now.ToString();
bool doRender = false;
lock (Padlock)
{
if (DateTime.Compare(info.LastTouch, info.LastRender) != 0)
{
Interlocked.Increment(ref info.RenderCount);
doRender = true;
info.LastRender = info.LastTouch;
}
}
var lines = new string[]
{
$"NTypewriter.SourceGenerator v{Version}",
$"total runs : {info.ExecuteCount}, total renders : {info.RenderCount}",
$"touch file : {info.TouchFilePath}",
$"log file : {info.LogFilePath}",
$"this run : {thisRunId}",
$"last build : {info.LastTouch}"
};
context.ReportDiagnostic(Diagnostic.Create(Diagnostics.ExecuteInfo, Location.None, String.Join("\n", lines)));
if (doRender == false) return;
try
{
var templates = context.AdditionalFiles.Where(x => x.Path.EndsWith(".nt")).Select(x => new TemplateToRender(x.Path, context.Compilation.AssemblyName) { Content = x.GetText().ToString() }).ToList();
var userCodePaths = context.Compilation.SyntaxTrees.Where(x => x.FilePath?.EndsWith(".nt.cs") == true).Select(x => x.FilePath).ToList();
var userCodeProvider = new UserCodeProvider(userCodePaths);
var userInterfaceOutputWriter = new UserInterfaceOutputWriter();
var cmd = new RenderTemplatesCommand(null, userCodeProvider, new GeneratedFileReaderWriter(context, info.ProjectDir), userInterfaceOutputWriter, null, null, null, null);
cmd.Execute(context.Compilation, templates).GetAwaiter().GetResult();
var log = userInterfaceOutputWriter.GetOutput();
context.ReportDiagnostic(Diagnostic.Create(Diagnostics.RenderInfo, Location.None, log));
File.WriteAllText(info.LogFilePath, log);
}
catch (Exception ex)
{
context.ReportDiagnostic(Diagnostic.Create(Diagnostics.Exception, Location.None, ex.ToString()));
}
}
private static GeneratorInfo GetGeneratorInfo(GeneratorExecutionContext context)
{
context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.OutputPath", out var outputPath);
context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.ProjectDir", out var projectDir);
var assemblyName = context.Compilation.AssemblyName;
var outputDir = GetOutputDir(outputPath, projectDir);
return AssemblyGeneratorInfo.GetOrAdd((assemblyName, outputDir), _ => new GeneratorInfo(assemblyName, projectDir, outputDir));
}
private static string GetOutputDir(string outputPath, string projectDir)
{
string fullOutputPath = Path.Combine(outputPath);
if (Path.IsPathRooted(outputPath) == false)
{
fullOutputPath = Path.Combine(projectDir, fullOutputPath);
}
return fullOutputPath;
}
private sealed class GeneratorInfo
{
public GeneratorInfo(string assemblyName, string projectDir, string outputDir)
{
(AssemblyName, ProjectDir, OutputDir) = (assemblyName, projectDir, outputDir);
}
public string AssemblyName;
public string ProjectDir;
public string OutputDir;
public long ExecuteCount;
public long RenderCount;
public DateTime LastRender;
public string TouchFilePath => Path.Combine(OutputDir, ".touch");
public DateTime LastTouch;
public string LogFilePath => Path.Combine(OutputDir, AssemblyName + ".ntsg.log");
}
}
}