Skip to content

Commit 407837a

Browse files
committed
C#: Refactor dotnet source generator execution
1 parent 13a71a4 commit 407837a

File tree

5 files changed

+130
-71
lines changed

5 files changed

+130
-71
lines changed

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/Sdk.cs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
1-
using System.Collections.Generic;
2-
using Semmle.Util;
3-
using System.Text.RegularExpressions;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
44
using System.Linq;
5+
using System.Text.RegularExpressions;
6+
using Semmle.Util;
7+
using Semmle.Util.Logging;
58

69
namespace Semmle.Extraction.CSharp.DependencyFetching
710
{
811
internal partial class Sdk
912
{
1013
private readonly IDotNet dotNet;
14+
private readonly ILogger logger;
15+
private readonly Lazy<string?> cscPath;
16+
public string? CscPath => cscPath.Value;
1117

12-
public Sdk(IDotNet dotNet) => this.dotNet = dotNet;
18+
private readonly Lazy<DotNetVersion?> newestSdkVersion;
19+
public DotNetVersion? Version => newestSdkVersion.Value;
20+
21+
public Sdk(IDotNet dotNet, ILogger logger)
22+
{
23+
this.dotNet = dotNet;
24+
this.logger = logger;
25+
26+
newestSdkVersion = new Lazy<DotNetVersion?>(GetNewestSdk);
27+
cscPath = new Lazy<string?>(GetCscPath);
28+
}
1329

1430
[GeneratedRegex(@"^(\d+\.\d+\.\d+)(-([a-z]+)\.(\d+\.\d+\.\d+))?\s\[(.+)\]$")]
1531
private static partial Regex SdkRegex();
@@ -30,11 +46,31 @@ private static HashSet<DotNetVersion> ParseSdks(IList<string> listed)
3046
return sdks;
3147
}
3248

33-
public DotNetVersion? GetNewestSdk()
49+
private DotNetVersion? GetNewestSdk()
3450
{
3551
var listed = dotNet.GetListedSdks();
3652
var sdks = ParseSdks(listed);
3753
return sdks.Max();
3854
}
55+
56+
private string? GetCscPath()
57+
{
58+
var sdk = GetNewestSdk();
59+
if (sdk is null)
60+
{
61+
logger.LogWarning("No dotnet SDK found.");
62+
return null;
63+
}
64+
65+
var path = Path.Combine(sdk.FullPath, "Roslyn", "bincore", "csc.dll");
66+
logger.LogInfo($"Source generator CSC: '{path}'");
67+
if (!File.Exists(path))
68+
{
69+
logger.LogInfo($"csc.dll not found at '{path}'.");
70+
return null;
71+
}
72+
73+
return path;
74+
}
3975
}
4076
}

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/SourceGenerators/Razor.cs renamed to csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/SourceGenerators/DotnetSourceGeneratorWrapper/DotnetSourceGeneratorWrapper.cs

Lines changed: 28 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,65 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Text;
5-
using System.Linq;
65
using Semmle.Util;
76
using Semmle.Util.Logging;
87

98
namespace Semmle.Extraction.CSharp.DependencyFetching
109
{
11-
internal class Razor
10+
internal abstract class DotnetSourceGeneratorWrapper
1211
{
13-
private readonly DotNetVersion sdk;
14-
private readonly ILogger logger;
15-
private readonly IDotNet dotNet;
16-
private readonly string sourceGeneratorFolder;
17-
private readonly string cscPath;
12+
protected readonly ILogger logger;
13+
private readonly Sdk sdk;
14+
protected readonly IDotNet dotnet;
1815

19-
public Razor(DotNetVersion sdk, IDotNet dotNet, ILogger logger)
16+
protected abstract string SourceGeneratorFolder { get; init; }
17+
protected abstract string FileType { get; }
18+
19+
public DotnetSourceGeneratorWrapper(
20+
Sdk sdk,
21+
IDotNet dotnet,
22+
ILogger logger)
2023
{
21-
this.sdk = sdk;
2224
this.logger = logger;
23-
this.dotNet = dotNet;
24-
25-
sourceGeneratorFolder = Path.Combine(this.sdk.FullPath, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators");
26-
this.logger.LogInfo($"Razor source generator folder: {sourceGeneratorFolder}");
27-
if (!Directory.Exists(sourceGeneratorFolder))
28-
{
29-
this.logger.LogInfo($"Razor source generator folder {sourceGeneratorFolder} does not exist.");
30-
throw new Exception($"Razor source generator folder {sourceGeneratorFolder} does not exist.");
31-
}
32-
33-
cscPath = Path.Combine(this.sdk.FullPath, "Roslyn", "bincore", "csc.dll");
34-
this.logger.LogInfo($"Razor source generator CSC: {cscPath}");
35-
if (!File.Exists(cscPath))
36-
{
37-
this.logger.LogInfo($"Csc.exe not found at {cscPath}.");
38-
throw new Exception($"csc.dll {cscPath} does not exist.");
39-
}
25+
this.sdk = sdk;
26+
this.dotnet = dotnet;
4027
}
4128

42-
private static void GenerateAnalyzerConfig(IEnumerable<string> cshtmls, string analyzerConfigPath)
43-
{
44-
using var sw = new StreamWriter(analyzerConfigPath);
45-
sw.WriteLine("is_global = true");
29+
protected abstract void GenerateAnalyzerConfig(IEnumerable<string> additionalFiles, string analyzerConfigPath);
4630

47-
foreach (var f in cshtmls.Select(f => f.Replace('\\', '/')))
31+
public IEnumerable<string> RunSourceGenerator(IEnumerable<string> additionalFiles, IEnumerable<string> references, string targetDir)
32+
{
33+
if (sdk.CscPath is null)
4834
{
49-
sw.WriteLine($"\n[{f}]");
50-
var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(f)); // TODO: this should be the relative path of the file.
51-
sw.WriteLine($"build_metadata.AdditionalFiles.TargetPath = {base64}");
35+
logger.LogWarning("Not running source generator because csc path is not available.");
36+
return [];
5237
}
53-
}
5438

55-
public IEnumerable<string> GenerateFiles(IEnumerable<string> cshtmls, IEnumerable<string> references, string workingDirectory)
56-
{
5739
var name = Guid.NewGuid().ToString("N").ToUpper();
5840
var tempPath = FileUtils.GetTemporaryWorkingDirectory(out var shouldCleanUp);
5941
var analyzerConfig = Path.Combine(tempPath, $"{name}.txt");
6042
var dllPath = Path.Combine(tempPath, $"{name}.dll");
6143
var cscArgsPath = Path.Combine(tempPath, $"{name}.rsp");
62-
var outputFolder = Path.Combine(workingDirectory, name);
44+
var outputFolder = Path.Combine(targetDir, name);
6345
Directory.CreateDirectory(outputFolder);
6446

6547
try
6648
{
6749
logger.LogInfo("Produce analyzer config content.");
68-
GenerateAnalyzerConfig(cshtmls, analyzerConfig);
50+
GenerateAnalyzerConfig(additionalFiles, analyzerConfig);
6951

7052
logger.LogDebug($"Analyzer config content: {File.ReadAllText(analyzerConfig)}");
7153

7254
var args = new StringBuilder();
7355
args.Append($"/target:exe /generatedfilesout:\"{outputFolder}\" /out:\"{dllPath}\" /analyzerconfig:\"{analyzerConfig}\" ");
7456

75-
foreach (var f in Directory.GetFiles(sourceGeneratorFolder, "*.dll", new EnumerationOptions { RecurseSubdirectories = false, MatchCasing = MatchCasing.CaseInsensitive }))
57+
foreach (var f in Directory.GetFiles(SourceGeneratorFolder, "*.dll", new EnumerationOptions { RecurseSubdirectories = false, MatchCasing = MatchCasing.CaseInsensitive }))
7658
{
7759
args.Append($"/analyzer:\"{f}\" ");
7860
}
7961

80-
foreach (var f in cshtmls)
62+
foreach (var f in additionalFiles)
8163
{
8264
args.Append($"/additionalfile:\"{f}\" ");
8365
}
@@ -89,19 +71,19 @@ public IEnumerable<string> GenerateFiles(IEnumerable<string> cshtmls, IEnumerabl
8971

9072
var argsString = args.ToString();
9173

92-
logger.LogInfo($"Running CSC to generate Razor source files.");
93-
logger.LogDebug($"Running CSC to generate Razor source files with arguments: {argsString}.");
74+
logger.LogInfo($"Running CSC to generate source files from {FileType} files.");
75+
logger.LogDebug($"Running CSC to generate source files from {FileType} files with arguments: {argsString}.");
9476

9577
using (var sw = new StreamWriter(cscArgsPath))
9678
{
9779
sw.Write(argsString);
9880
}
9981

100-
dotNet.Exec($"\"{cscPath}\" /noconfig @\"{cscArgsPath}\"");
82+
dotnet.Exec($"\"{sdk.CscPath}\" /noconfig @\"{cscArgsPath}\"");
10183

10284
var files = Directory.GetFiles(outputFolder, "*.*", new EnumerationOptions { RecurseSubdirectories = true });
10385

104-
logger.LogInfo($"Generated {files.Length} source files from cshtml files.");
86+
logger.LogInfo($"Generated {files.Length} source files from {FileType} files.");
10587

10688
return files;
10789
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using Semmle.Util.Logging;
7+
8+
namespace Semmle.Extraction.CSharp.DependencyFetching
9+
{
10+
internal class Razor : DotnetSourceGeneratorWrapper
11+
{
12+
protected override string FileType => "cshtml";
13+
14+
protected override string SourceGeneratorFolder { get; init; }
15+
16+
public Razor(Sdk sdk, IDotNet dotNet, ILogger logger) : base(sdk, dotNet, logger)
17+
{
18+
var sdkPath = sdk.Version?.FullPath;
19+
if (sdkPath is null)
20+
{
21+
throw new Exception("No SDK path available.");
22+
}
23+
24+
SourceGeneratorFolder = Path.Combine(sdkPath, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators");
25+
this.logger.LogInfo($"Razor source generator folder: {SourceGeneratorFolder}");
26+
if (!Directory.Exists(SourceGeneratorFolder))
27+
{
28+
throw new Exception($"Razor source generator folder {SourceGeneratorFolder} does not exist.");
29+
}
30+
}
31+
32+
protected override void GenerateAnalyzerConfig(IEnumerable<string> cshtmls, string analyzerConfigPath)
33+
{
34+
using var sw = new StreamWriter(analyzerConfigPath);
35+
sw.WriteLine("is_global = true");
36+
37+
foreach (var f in cshtmls.Select(f => f.Replace('\\', '/')))
38+
{
39+
sw.WriteLine($"\n[{f}]");
40+
var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(f)); // TODO: this should be the relative path of the file.
41+
sw.WriteLine($"build_metadata.AdditionalFiles.TargetPath = {base64}");
42+
}
43+
}
44+
}
45+
}

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/SourceGenerators/WebViewGenerator.cs

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,24 +63,20 @@ private IEnumerable<string> GenerateSourceFilesFromWebViews()
6363

6464
logger.LogInfo("Generating source files from cshtml and razor files...");
6565

66-
var sdk = new Sdk(dotnet).GetNewestSdk();
67-
if (sdk != null)
66+
try
6867
{
69-
try
70-
{
71-
var razor = new Razor(sdk, dotnet, logger);
72-
var targetDir = GetTemporaryWorkingDirectory("razor");
73-
var generatedFiles = razor.GenerateFiles(views, references, targetDir);
74-
return generatedFiles ?? [];
75-
}
76-
catch (Exception ex)
77-
{
78-
// It's okay, we tried our best to generate source files from cshtml files.
79-
logger.LogInfo($"Failed to generate source files from cshtml files: {ex.Message}");
80-
}
68+
var sdk = new Sdk(dotnet, logger);
69+
var razor = new Razor(sdk, dotnet, logger);
70+
var targetDir = GetTemporaryWorkingDirectory("razor");
71+
var generatedFiles = razor.RunSourceGenerator(views, references, targetDir);
72+
return generatedFiles ?? [];
73+
}
74+
catch (Exception ex)
75+
{
76+
// It's okay, we tried our best to generate source files from cshtml files.
77+
logger.LogInfo($"Failed to generate source files from cshtml files: {ex.Message}");
78+
return [];
8179
}
82-
83-
return [];
8480
}
8581
}
8682
}

csharp/extractor/Semmle.Extraction.Tests/Runtime.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,10 @@ public void TestSdk1()
164164
"6.0.301 [/usr/local/share/dotnet/sdk7]",
165165
};
166166
var dotnet = new DotNetStub(null!, listedSdks);
167-
var sdk = new Sdk(dotnet);
167+
var sdk = new Sdk(dotnet, new LoggerStub());
168168

169169
// Execute
170-
var version = sdk.GetNewestSdk();
170+
var version = sdk.Version;
171171

172172
// Verify
173173
Assert.NotNull(version);
@@ -186,10 +186,10 @@ public void TestSdk2()
186186
"7.0.400 [/usr/local/share/dotnet/sdk4]",
187187
};
188188
var dotnet = new DotNetStub(null!, listedSdks);
189-
var sdk = new Sdk(dotnet);
189+
var sdk = new Sdk(dotnet, new LoggerStub());
190190

191191
// Execute
192-
var version = sdk.GetNewestSdk();
192+
var version = sdk.Version;
193193

194194
// Verify
195195
Assert.NotNull(version);

0 commit comments

Comments
 (0)