Skip to content

Commit afe1e9c

Browse files
authored
Merge pull request github#13957 from tamasvajk/razor-standalone-2
C#: Generate source files from cshtml files in standalone
2 parents fe36230 + 2575db3 commit afe1e9c

File tree

18 files changed

+447
-103
lines changed

18 files changed

+447
-103
lines changed

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

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@ public sealed class DependencyManager : IDisposable
2323
private readonly IDictionary<string, string> unresolvedReferences = new ConcurrentDictionary<string, string>();
2424
private int failedProjects;
2525
private int succeededProjects;
26-
private readonly string[] allSources;
26+
private readonly List<string> allSources;
2727
private int conflictedReferences = 0;
2828
private readonly IDependencyOptions options;
2929
private readonly DirectoryInfo sourceDir;
3030
private readonly DotNet dotnet;
3131
private readonly FileContent fileContent;
3232
private readonly TemporaryDirectory packageDirectory;
33+
private TemporaryDirectory? razorWorkingDirectory;
3334

3435

3536
/// <summary>
@@ -60,7 +61,7 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
6061
packageDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName));
6162

6263
this.fileContent = new FileContent(packageDirectory, progressMonitor, () => GetFiles("*.*"));
63-
this.allSources = GetFiles("*.cs").ToArray();
64+
this.allSources = GetFiles("*.cs").ToList();
6465
var allProjects = GetFiles("*.csproj");
6566
var solutions = options.SolutionFile is not null
6667
? new[] { options.SolutionFile }
@@ -131,6 +132,13 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
131132
progressMonitor.UnresolvedReference(r.Key, r.Value);
132133
}
133134

135+
var webViewExtractionOption = Environment.GetEnvironmentVariable("CODEQL_EXTRACTOR_CSHARP_STANDALONE_EXTRACT_WEB_VIEWS");
136+
if (bool.TryParse(webViewExtractionOption, out var shouldExtractWebViews) &&
137+
shouldExtractWebViews)
138+
{
139+
GenerateSourceFilesFromWebViews();
140+
}
141+
134142
progressMonitor.Summary(
135143
AllSourceFiles.Count(),
136144
ProjectSourceFiles.Count(),
@@ -143,6 +151,38 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
143151
DateTime.Now - startTime);
144152
}
145153

154+
private void GenerateSourceFilesFromWebViews()
155+
{
156+
progressMonitor.LogInfo($"Generating source files from cshtml and razor files.");
157+
158+
var views = GetFiles("*.cshtml")
159+
.Concat(GetFiles("*.razor"))
160+
.ToArray();
161+
162+
if (views.Length > 0)
163+
{
164+
progressMonitor.LogInfo($"Found {views.Length} cshtml and razor files.");
165+
166+
// TODO: use SDK specified in global.json
167+
var sdk = new Sdk(dotnet).GetNewestSdk();
168+
if (sdk != null)
169+
{
170+
try
171+
{
172+
var razor = new Razor(sdk, dotnet, progressMonitor);
173+
razorWorkingDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName, "razor"));
174+
var generatedFiles = razor.GenerateFiles(views, usedReferences.Keys, razorWorkingDirectory.ToString());
175+
this.allSources.AddRange(generatedFiles);
176+
}
177+
catch (Exception ex)
178+
{
179+
// It's okay, we tried our best to generate source files from cshtml files.
180+
progressMonitor.LogInfo($"Failed to generate source files from cshtml files: {ex.Message}");
181+
}
182+
}
183+
}
184+
}
185+
146186
public DependencyManager(string srcDir) : this(srcDir, DependencyOptions.Default, new ConsoleLogger(Verbosity.Info)) { }
147187

148188
private IEnumerable<string> GetFiles(string pattern, bool recurseSubdirectories = true)
@@ -156,17 +196,16 @@ private IEnumerable<string> GetFiles(string pattern, bool recurseSubdirectories
156196
/// Computes a unique temp directory for the packages associated
157197
/// with this source tree. Use a SHA1 of the directory name.
158198
/// </summary>
159-
/// <param name="srcDir"></param>
160199
/// <returns>The full path of the temp directory.</returns>
161-
private static string ComputeTempDirectory(string srcDir)
200+
private static string ComputeTempDirectory(string srcDir, string subfolderName = "packages")
162201
{
163202
var bytes = Encoding.Unicode.GetBytes(srcDir);
164203
var sha = SHA1.HashData(bytes);
165204
var sb = new StringBuilder();
166205
foreach (var b in sha.Take(8))
167206
sb.AppendFormat("{0:x2}", b);
168207

169-
return Path.Combine(Path.GetTempPath(), "GitHub", "packages", sb.ToString());
208+
return Path.Combine(Path.GetTempPath(), "GitHub", subfolderName, sb.ToString());
170209
}
171210

172211
/// <summary>
@@ -392,6 +431,10 @@ private void AnalyseSolutions(IEnumerable<string> solutions)
392431
});
393432
}
394433

395-
public void Dispose() => packageDirectory?.Dispose();
434+
public void Dispose()
435+
{
436+
packageDirectory?.Dispose();
437+
razorWorkingDirectory?.Dispose();
438+
}
396439
}
397440
}

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

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,6 @@
55

66
namespace Semmle.Extraction.CSharp.DependencyFetching
77
{
8-
internal interface IDotNet
9-
{
10-
bool RestoreToDirectory(string project, string directory, string? pathToNugetConfig = null);
11-
bool New(string folder);
12-
bool AddPackage(string folder, string package);
13-
IList<string> GetListedRuntimes();
14-
}
15-
168
/// <summary>
179
/// Utilities to run the "dotnet" command.
1810
/// </summary>
@@ -30,29 +22,31 @@ public DotNet(ProgressMonitor progressMonitor)
3022
private void Info()
3123
{
3224
// TODO: make sure the below `dotnet` version is matching the one specified in global.json
33-
progressMonitor.RunningProcess($"{dotnet} --info");
34-
using var proc = Process.Start(dotnet, "--info");
35-
proc.WaitForExit();
36-
var ret = proc.ExitCode;
37-
38-
if (ret != 0)
25+
var res = RunCommand("--info");
26+
if (!res)
3927
{
40-
progressMonitor.CommandFailed(dotnet, "--info", ret);
41-
throw new Exception($"{dotnet} --info failed with exit code {ret}.");
28+
throw new Exception($"{dotnet} --info failed.");
4229
}
4330
}
4431

32+
private static ProcessStartInfo MakeDotnetStartInfo(string args, bool redirectStandardOutput) =>
33+
new ProcessStartInfo(dotnet, args)
34+
{
35+
UseShellExecute = false,
36+
RedirectStandardOutput = redirectStandardOutput
37+
};
38+
4539
private bool RunCommand(string args)
4640
{
4741
progressMonitor.RunningProcess($"{dotnet} {args}");
48-
using var proc = Process.Start(dotnet, args);
49-
proc.WaitForExit();
50-
if (proc.ExitCode != 0)
42+
using var proc = Process.Start(MakeDotnetStartInfo(args, redirectStandardOutput: false));
43+
proc?.WaitForExit();
44+
var exitCode = proc?.ExitCode ?? -1;
45+
if (exitCode != 0)
5146
{
52-
progressMonitor.CommandFailed(dotnet, args, proc.ExitCode);
47+
progressMonitor.CommandFailed(dotnet, args, exitCode);
5348
return false;
5449
}
55-
5650
return true;
5751
}
5852

@@ -76,23 +70,28 @@ public bool AddPackage(string folder, string package)
7670
return RunCommand(args);
7771
}
7872

79-
public IList<string> GetListedRuntimes()
73+
public IList<string> GetListedRuntimes() => GetListed("--list-runtimes", "runtime");
74+
75+
public IList<string> GetListedSdks() => GetListed("--list-sdks", "SDK");
76+
77+
private IList<string> GetListed(string args, string artifact)
8078
{
81-
const string args = "--list-runtimes";
8279
progressMonitor.RunningProcess($"{dotnet} {args}");
83-
var pi = new ProcessStartInfo(dotnet, args)
84-
{
85-
RedirectStandardOutput = true,
86-
UseShellExecute = false
87-
};
88-
var exitCode = pi.ReadOutput(out var runtimes);
80+
var pi = MakeDotnetStartInfo(args, redirectStandardOutput: true);
81+
var exitCode = pi.ReadOutput(out var artifacts);
8982
if (exitCode != 0)
9083
{
9184
progressMonitor.CommandFailed(dotnet, args, exitCode);
9285
return new List<string>();
9386
}
94-
progressMonitor.LogInfo($"Found runtimes: {string.Join("\n", runtimes)}");
95-
return runtimes;
87+
progressMonitor.LogInfo($"Found {artifact}s: {string.Join("\n", artifacts)}");
88+
return artifacts;
89+
}
90+
91+
public bool Exec(string execArgs)
92+
{
93+
var args = $"exec {execArgs}";
94+
return RunCommand(args);
9695
}
9796
}
9897
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
using System.IO;
3+
4+
namespace Semmle.Extraction.CSharp.DependencyFetching
5+
{
6+
internal record DotnetVersion : IComparable<DotnetVersion>
7+
{
8+
private readonly string dir;
9+
private readonly Version version;
10+
private readonly Version? preReleaseVersion;
11+
private readonly string? preReleaseVersionType;
12+
private bool IsPreRelease => preReleaseVersionType is not null && preReleaseVersion is not null;
13+
public string FullPath
14+
{
15+
get
16+
{
17+
var preRelease = IsPreRelease ? $"-{preReleaseVersionType}.{preReleaseVersion}" : "";
18+
var version = this.version + preRelease;
19+
return Path.Combine(dir, version);
20+
}
21+
}
22+
23+
public DotnetVersion(string dir, string version, string preReleaseVersionType, string preReleaseVersion)
24+
{
25+
this.dir = dir;
26+
this.version = Version.Parse(version);
27+
if (!string.IsNullOrEmpty(preReleaseVersion) && !string.IsNullOrEmpty(preReleaseVersionType))
28+
{
29+
this.preReleaseVersionType = preReleaseVersionType;
30+
this.preReleaseVersion = Version.Parse(preReleaseVersion);
31+
}
32+
}
33+
34+
public int CompareTo(DotnetVersion? other)
35+
{
36+
var c = version.CompareTo(other?.version);
37+
if (c == 0 && IsPreRelease)
38+
{
39+
if (!other!.IsPreRelease)
40+
{
41+
return -1;
42+
}
43+
44+
// Both are pre-release like runtime versions.
45+
// The pre-release version types are sorted alphabetically (e.g. alpha, beta, preview, rc)
46+
// and the pre-release version types are more important that the pre-release version numbers.
47+
return preReleaseVersionType != other!.preReleaseVersionType
48+
? preReleaseVersionType!.CompareTo(other!.preReleaseVersionType)
49+
: preReleaseVersion!.CompareTo(other!.preReleaseVersion);
50+
}
51+
52+
return c;
53+
}
54+
55+
public override string ToString() => FullPath;
56+
}
57+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Collections.Generic;
2+
3+
namespace Semmle.Extraction.CSharp.DependencyFetching
4+
{
5+
internal interface IDotNet
6+
{
7+
bool RestoreToDirectory(string project, string directory, string? pathToNugetConfig = null);
8+
bool New(string folder);
9+
bool AddPackage(string folder, string package);
10+
IList<string> GetListedRuntimes();
11+
IList<string> GetListedSdks();
12+
bool Exec(string execArgs);
13+
}
14+
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,14 @@ public void MultipleNugetConfig(string[] nugetConfigs) =>
108108

109109
internal void NoTopLevelNugetConfig() =>
110110
LogInfo("Could not find a top-level nuget.config file.");
111+
112+
internal void RazorSourceGeneratorMissing(string fullPath) =>
113+
LogInfo($"Razor source generator folder {fullPath} does not exist.");
114+
115+
internal void CscMissing(string cscPath) =>
116+
LogInfo($"Csc.exe not found at {cscPath}.");
117+
118+
internal void RazorCscArgs(string args) =>
119+
LogInfo($"Running CSC to generate Razor source files. Args: {args}.");
111120
}
112121
}

0 commit comments

Comments
 (0)