Skip to content

Commit 79fe5f8

Browse files
committed
C#: Add resource generator
1 parent 407837a commit 79fe5f8

File tree

12 files changed

+241
-95
lines changed

12 files changed

+241
-95
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ void exitCallback(int ret, string msg, bool silent)
152152
var sourceGenerators = new ISourceGenerator[]
153153
{
154154
new ImplicitUsingsGenerator(fileContent, logger, tempWorkingDirectory),
155-
new WebViewGenerator(fileProvider, fileContent, dotnet, this, logger, tempWorkingDirectory, usedReferences.Keys)
155+
new RazorGenerator(fileProvider, fileContent, dotnet, this, logger, tempWorkingDirectory, usedReferences.Keys),
156+
new ResxGenerator(fileProvider, fileContent, dotnet, this, logger, tempWorkingDirectory, usedReferences.Keys),
156157
};
157158

158159
foreach (var sourceGenerator in sourceGenerators)

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
22
{
33
internal class EnvironmentVariableNames
44
{
5+
/// <summary>
6+
/// Controls whether to generate source files from resources (`.resx`).
7+
/// </summary>
8+
public const string ResourceGeneration = "CODEQL_EXTRACTOR_CSHARP_BUILDLESS_EXTRACT_RESOURCES";
9+
510
/// <summary>
611
/// Controls whether to generate source files from Asp.Net Core views (`.cshtml`, `.razor`).
712
/// </summary>

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class FileProvider
2222
private readonly Lazy<string[]> nugetConfigs;
2323
private readonly Lazy<string[]> globalJsons;
2424
private readonly Lazy<string[]> razorViews;
25+
private readonly Lazy<string[]> resources;
2526
private readonly Lazy<string?> rootNugetConfig;
2627

2728
public FileProvider(DirectoryInfo sourceDir, ILogger logger)
@@ -44,6 +45,7 @@ public FileProvider(DirectoryInfo sourceDir, ILogger logger)
4445
nugetConfigs = new Lazy<string[]>(() => allNonBinary.Value.SelectFileNamesByName("nuget.config").ToArray());
4546
globalJsons = new Lazy<string[]>(() => allNonBinary.Value.SelectFileNamesByName("global.json").ToArray());
4647
razorViews = new Lazy<string[]>(() => SelectTextFileNamesByExtension("razor view", ".cshtml", ".razor"));
48+
resources = new Lazy<string[]>(() => SelectTextFileNamesByExtension("resource", ".resx"));
4749

4850
rootNugetConfig = new Lazy<string?>(() => all.SelectRootFiles(SourceDir).SelectFileNamesByName("nuget.config").FirstOrDefault());
4951
}
@@ -116,5 +118,6 @@ private FileInfo[] GetAllFiles()
116118
public string? RootNugetConfig => rootNugetConfig.Value;
117119
public IEnumerable<string> GlobalJsons => globalJsons.Value;
118120
public ICollection<string> RazorViews => razorViews.Value;
121+
public ICollection<string> Resources => resources.Value;
119122
}
120123
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Semmle.Util;
4+
using Semmle.Util.Logging;
5+
6+
namespace Semmle.Extraction.CSharp.DependencyFetching
7+
{
8+
internal abstract class DotnetSourceGeneratorBase<T> : SourceGeneratorBase where T : DotnetSourceGeneratorWrapper
9+
{
10+
protected readonly FileProvider fileProvider;
11+
protected readonly FileContent fileContent;
12+
protected readonly IDotNet dotnet;
13+
protected readonly ICompilationInfoContainer compilationInfoContainer;
14+
protected readonly IEnumerable<string> references;
15+
16+
public DotnetSourceGeneratorBase(
17+
FileProvider fileProvider,
18+
FileContent fileContent,
19+
IDotNet dotnet,
20+
ICompilationInfoContainer compilationInfoContainer,
21+
ILogger logger,
22+
TemporaryDirectory tempWorkingDirectory,
23+
IEnumerable<string> references) : base(logger, tempWorkingDirectory)
24+
{
25+
this.fileProvider = fileProvider;
26+
this.fileContent = fileContent;
27+
this.dotnet = dotnet;
28+
this.compilationInfoContainer = compilationInfoContainer;
29+
this.references = references;
30+
}
31+
32+
protected override IEnumerable<string> Run()
33+
{
34+
var additionalFiles = AdditionalFiles;
35+
if (additionalFiles.Count == 0)
36+
{
37+
logger.LogDebug($"No {FileType} files found.");
38+
return [];
39+
}
40+
41+
logger.LogInfo($"Found {additionalFiles.Count} {FileType} files.");
42+
43+
if (!fileContent.IsAspNetCoreDetected)
44+
{
45+
logger.LogInfo($"Generating source files from {FileType} files is only supported for new (SDK-style) project files");
46+
return [];
47+
}
48+
49+
logger.LogInfo($"Generating source files from {FileType} files...");
50+
51+
try
52+
{
53+
var sdk = new Sdk(dotnet, logger);
54+
var sourceGenerator = GetSourceGenerator(sdk);
55+
var targetDir = GetTemporaryWorkingDirectory(FileType.ToLowerInvariant());
56+
// todo: run the below in a loop, on groups of files belonging to the same project:
57+
var generatedFiles = sourceGenerator.RunSourceGenerator(additionalFiles, references, targetDir);
58+
return generatedFiles ?? [];
59+
}
60+
catch (Exception ex)
61+
{
62+
// It's okay, we tried our best to generate source files.
63+
logger.LogInfo($"Failed to generate source files from {FileType} files: {ex.Message}");
64+
return [];
65+
}
66+
}
67+
68+
protected abstract ICollection<string> AdditionalFiles { get; }
69+
70+
protected abstract string FileType { get; }
71+
72+
protected abstract T GetSourceGenerator(Sdk sdk);
73+
74+
}
75+
}

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
1010
internal abstract class DotnetSourceGeneratorWrapper
1111
{
1212
protected readonly ILogger logger;
13-
private readonly Sdk sdk;
1413
protected readonly IDotNet dotnet;
14+
private readonly string cscPath;
1515

1616
protected abstract string SourceGeneratorFolder { get; init; }
1717
protected abstract string FileType { get; }
@@ -22,20 +22,19 @@ public DotnetSourceGeneratorWrapper(
2222
ILogger logger)
2323
{
2424
this.logger = logger;
25-
this.sdk = sdk;
2625
this.dotnet = dotnet;
26+
27+
if (sdk.CscPath is null)
28+
{
29+
throw new Exception($"Not running {FileType} source generator because CSC path is not available.");
30+
}
31+
this.cscPath = sdk.CscPath;
2732
}
2833

2934
protected abstract void GenerateAnalyzerConfig(IEnumerable<string> additionalFiles, string analyzerConfigPath);
3035

3136
public IEnumerable<string> RunSourceGenerator(IEnumerable<string> additionalFiles, IEnumerable<string> references, string targetDir)
3237
{
33-
if (sdk.CscPath is null)
34-
{
35-
logger.LogWarning("Not running source generator because csc path is not available.");
36-
return [];
37-
}
38-
3938
var name = Guid.NewGuid().ToString("N").ToUpper();
4039
var tempPath = FileUtils.GetTemporaryWorkingDirectory(out var shouldCleanUp);
4140
var analyzerConfig = Path.Combine(tempPath, $"{name}.txt");
@@ -79,7 +78,7 @@ public IEnumerable<string> RunSourceGenerator(IEnumerable<string> additionalFile
7978
sw.Write(argsString);
8079
}
8180

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

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
99
{
1010
internal class Razor : DotnetSourceGeneratorWrapper
1111
{
12-
protected override string FileType => "cshtml";
12+
protected override string FileType => "Razor";
1313

1414
protected override string SourceGeneratorFolder { get; init; }
1515

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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 Resx : DotnetSourceGeneratorWrapper
11+
{
12+
protected override string FileType => "Resx";
13+
14+
protected override string SourceGeneratorFolder { get; init; }
15+
16+
public Resx(Sdk sdk, IDotNet dotNet, ILogger logger, string? sourceGeneratorFolder) : base(sdk, dotNet, logger)
17+
{
18+
if (sourceGeneratorFolder is null)
19+
{
20+
throw new Exception("No resx source generator folder available.");
21+
}
22+
SourceGeneratorFolder = sourceGeneratorFolder;
23+
}
24+
25+
protected override void GenerateAnalyzerConfig(IEnumerable<string> resources, string analyzerConfigPath)
26+
{
27+
using var sw = new StreamWriter(analyzerConfigPath);
28+
sw.WriteLine("is_global = true");
29+
30+
foreach (var f in resources.Select(f => f.Replace('\\', '/')))
31+
{
32+
sw.WriteLine($"\n[{f}]");
33+
sw.WriteLine($"build_metadata.AdditionalFiles.EmitFormatMethods = true");
34+
}
35+
}
36+
}
37+
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ public ImplicitUsingsGenerator(FileContent fileContent, ILogger logger, Temporar
1616
this.fileContent = fileContent;
1717
}
1818

19-
public override IEnumerable<string> Generate()
19+
protected override bool IsEnabled() => true;
20+
21+
protected override IEnumerable<string> Run()
2022
{
2123
var usings = new HashSet<string>();
2224
if (!fileContent.UseImplicitUsings)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Semmle.Util;
4+
using Semmle.Util.Logging;
5+
6+
namespace Semmle.Extraction.CSharp.DependencyFetching
7+
{
8+
internal class RazorGenerator : DotnetSourceGeneratorBase<Razor>
9+
{
10+
public RazorGenerator(
11+
FileProvider fileProvider,
12+
FileContent fileContent,
13+
IDotNet dotnet,
14+
ICompilationInfoContainer compilationInfoContainer,
15+
ILogger logger,
16+
TemporaryDirectory tempWorkingDirectory,
17+
IEnumerable<string> references) : base(fileProvider, fileContent, dotnet, compilationInfoContainer, logger, tempWorkingDirectory, references)
18+
{
19+
}
20+
21+
protected override bool IsEnabled()
22+
{
23+
var webViewExtractionOption = Environment.GetEnvironmentVariable(EnvironmentVariableNames.WebViewGeneration);
24+
if (webViewExtractionOption == null ||
25+
bool.TryParse(webViewExtractionOption, out var shouldExtractWebViews) &&
26+
shouldExtractWebViews)
27+
{
28+
compilationInfoContainer.CompilationInfos.Add(("WebView extraction enabled", "1"));
29+
return true;
30+
}
31+
32+
compilationInfoContainer.CompilationInfos.Add(("WebView extraction enabled", "0"));
33+
return false;
34+
}
35+
36+
protected override ICollection<string> AdditionalFiles => fileProvider.RazorViews;
37+
38+
protected override string FileType => "Razor";
39+
40+
protected override Razor GetSourceGenerator(Sdk sdk) => new Razor(sdk, dotnet, logger);
41+
}
42+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Semmle.Util;
4+
using Semmle.Util.Logging;
5+
6+
namespace Semmle.Extraction.CSharp.DependencyFetching
7+
{
8+
internal class ResxGenerator : DotnetSourceGeneratorBase<Resx>
9+
{
10+
private readonly string? sourceGeneratorFolder = null;
11+
12+
public ResxGenerator(
13+
FileProvider fileProvider,
14+
FileContent fileContent,
15+
IDotNet dotnet,
16+
ICompilationInfoContainer compilationInfoContainer,
17+
ILogger logger,
18+
TemporaryDirectory tempWorkingDirectory,
19+
IEnumerable<string> references) : base(fileProvider, fileContent, dotnet, compilationInfoContainer, logger, tempWorkingDirectory, references)
20+
{
21+
try
22+
{
23+
// todo: download latest `Microsoft.CodeAnalysis.ResxSourceGenerator` and set `sourceGeneratorFolder`
24+
}
25+
catch (Exception e)
26+
{
27+
logger.LogWarning($"Failed to download source generator: {e.Message}");
28+
sourceGeneratorFolder = null;
29+
}
30+
}
31+
32+
protected override bool IsEnabled()
33+
{
34+
var resourceExtractionOption = Environment.GetEnvironmentVariable(EnvironmentVariableNames.ResourceGeneration);
35+
if (resourceExtractionOption == null ||
36+
bool.TryParse(resourceExtractionOption, out var shouldExtractResources) &&
37+
shouldExtractResources)
38+
{
39+
compilationInfoContainer.CompilationInfos.Add(("Resource extraction enabled", "1"));
40+
return true;
41+
}
42+
43+
compilationInfoContainer.CompilationInfos.Add(("Resource extraction enabled", "0"));
44+
return false;
45+
}
46+
47+
protected override ICollection<string> AdditionalFiles => fileProvider.Resources;
48+
49+
protected override string FileType => "Resx";
50+
51+
protected override Resx GetSourceGenerator(Sdk sdk) => new Resx(sdk, dotnet, logger, sourceGeneratorFolder);
52+
}
53+
}

0 commit comments

Comments
 (0)