Skip to content

Commit b75f138

Browse files
authored
Merge pull request github#12385 from github/mbg/csharp/readd-tsp-support
C#: Add support for the tool status page
2 parents 40d3112 + 9dc9925 commit b75f138

File tree

43 files changed

+1496
-81
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1496
-81
lines changed

cpp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Xunit;
22
using Semmle.Autobuild.Shared;
3+
using Semmle.Util;
34
using System.Collections.Generic;
45
using System;
56
using System.Linq;
@@ -75,6 +76,15 @@ int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory,
7576
throw new ArgumentException("Missing RunProcess " + pattern);
7677
}
7778

79+
int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory, IDictionary<string, string>? env, BuildOutputHandler onOutput, BuildOutputHandler onError)
80+
{
81+
var ret = (this as IBuildActions).RunProcess(cmd, args, workingDirectory, env, out var stdout);
82+
83+
stdout.ForEach(line => onOutput(line));
84+
85+
return ret;
86+
}
87+
7888
public IList<string> DirectoryDeleteIn = new List<string>();
7989

8090
void IBuildActions.DirectoryDelete(string dir, bool recursive)
@@ -184,6 +194,15 @@ public void DownloadFile(string address, string fileName)
184194
if (!DownloadFiles.Contains((address, fileName)))
185195
throw new ArgumentException($"Missing DownloadFile, {address}, {fileName}");
186196
}
197+
198+
public IDiagnosticsWriter CreateDiagnosticsWriter(string filename) => new TestDiagnosticWriter();
199+
}
200+
201+
internal class TestDiagnosticWriter : IDiagnosticsWriter
202+
{
203+
public IList<DiagnosticMessage> Diagnostics { get; } = new List<DiagnosticMessage>();
204+
205+
public void AddEntry(DiagnosticMessage message) => this.Diagnostics.Add(message);
187206
}
188207

189208
/// <summary>
@@ -243,6 +262,7 @@ CppAutobuilder CreateAutoBuilder(bool isWindows,
243262
Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_TRAP_DIR"] = "";
244263
Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_SOURCE_ARCHIVE_DIR"] = "";
245264
Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_ROOT"] = $@"C:\codeql\{codeqlUpperLanguage.ToLowerInvariant()}";
265+
Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_DIAGNOSTIC_DIR"] = "";
246266
Actions.GetEnvironmentVariable["CODEQL_JAVA_HOME"] = @"C:\codeql\tools\java";
247267
Actions.GetEnvironmentVariable["CODEQL_PLATFORM"] = "win64";
248268
Actions.GetEnvironmentVariable["SEMMLE_DIST"] = @"C:\odasa";

cpp/autobuilder/Semmle.Autobuild.Cpp/CppAutobuilder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Semmle.Autobuild.Shared;
2+
using Semmle.Util;
23

34
namespace Semmle.Autobuild.Cpp
45
{
@@ -21,7 +22,7 @@ public CppAutobuildOptions(IBuildActions actions) : base(actions)
2122

2223
public class CppAutobuilder : Autobuilder<CppAutobuildOptions>
2324
{
24-
public CppAutobuilder(IBuildActions actions, CppAutobuildOptions options) : base(actions, options) { }
25+
public CppAutobuilder(IBuildActions actions, CppAutobuildOptions options) : base(actions, options, new DiagnosticClassifier()) { }
2526

2627
public override BuildScript GetBuildScript()
2728
{

csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Xunit;
22
using Semmle.Autobuild.Shared;
3+
using Semmle.Util;
34
using System.Collections.Generic;
45
using System;
56
using System.Linq;
@@ -85,6 +86,15 @@ int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory,
8586
return ret;
8687
}
8788

89+
int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory, IDictionary<string, string>? env, BuildOutputHandler onOutput, BuildOutputHandler onError)
90+
{
91+
var ret = (this as IBuildActions).RunProcess(cmd, args, workingDirectory, env, out var stdout);
92+
93+
stdout.ForEach(line => onOutput(line));
94+
95+
return ret;
96+
}
97+
8898
public IList<string> DirectoryDeleteIn { get; } = new List<string>();
8999

90100
void IBuildActions.DirectoryDelete(string dir, bool recursive)
@@ -200,6 +210,16 @@ public void DownloadFile(string address, string fileName)
200210
if (!DownloadFiles.Contains((address, fileName)))
201211
throw new ArgumentException($"Missing DownloadFile, {address}, {fileName}");
202212
}
213+
214+
215+
public IDiagnosticsWriter CreateDiagnosticsWriter(string filename) => new TestDiagnosticWriter();
216+
}
217+
218+
internal class TestDiagnosticWriter : IDiagnosticsWriter
219+
{
220+
public IList<DiagnosticMessage> Diagnostics { get; } = new List<DiagnosticMessage>();
221+
222+
public void AddEntry(DiagnosticMessage message) => this.Diagnostics.Add(message);
203223
}
204224

205225
/// <summary>
@@ -391,6 +411,7 @@ private CSharpAutobuilder CreateAutoBuilder(bool isWindows,
391411
actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_TRAP_DIR"] = "";
392412
actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_SOURCE_ARCHIVE_DIR"] = "";
393413
actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_ROOT"] = $@"C:\codeql\{codeqlUpperLanguage.ToLowerInvariant()}";
414+
actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_DIAGNOSTIC_DIR"] = "";
394415
actions.GetEnvironmentVariable["CODEQL_JAVA_HOME"] = @"C:\codeql\tools\java";
395416
actions.GetEnvironmentVariable["CODEQL_PLATFORM"] = isWindows ? "win64" : "linux64";
396417
actions.GetEnvironmentVariable["LGTM_INDEX_VSTOOLS_VERSION"] = vsToolsVersion;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using Semmle.Autobuild.Shared;
2+
using Semmle.Extraction.CSharp;
3+
4+
namespace Semmle.Autobuild.CSharp
5+
{
6+
internal class AutoBuildRule : IBuildRule<CSharpAutobuildOptions>
7+
{
8+
private readonly CSharpAutobuilder autobuilder;
9+
10+
public DotNetRule DotNetRule { get; }
11+
12+
public MsBuildRule MsBuildRule { get; }
13+
14+
public BuildCommandAutoRule BuildCommandAutoRule { get; }
15+
16+
public AutoBuildRule(CSharpAutobuilder autobuilder)
17+
{
18+
this.autobuilder = autobuilder;
19+
this.DotNetRule = new DotNetRule();
20+
this.MsBuildRule = new MsBuildRule();
21+
this.BuildCommandAutoRule = new BuildCommandAutoRule(DotNetRule.WithDotNet);
22+
}
23+
24+
public BuildScript Analyse(IAutobuilder<CSharpAutobuildOptions> builder, bool auto)
25+
{
26+
var cleanTrapFolder =
27+
BuildScript.DeleteDirectory(this.autobuilder.TrapDir);
28+
var cleanSourceArchive =
29+
BuildScript.DeleteDirectory(this.autobuilder.SourceArchiveDir);
30+
var tryCleanExtractorArgsLogs =
31+
BuildScript.Create(actions =>
32+
{
33+
foreach (var file in Extractor.GetCSharpArgsLogs())
34+
{
35+
try
36+
{
37+
actions.FileDelete(file);
38+
}
39+
catch // lgtm[cs/catch-of-all-exceptions] lgtm[cs/empty-catch-block]
40+
{ }
41+
}
42+
43+
return 0;
44+
});
45+
var attemptExtractorCleanup =
46+
BuildScript.Try(cleanTrapFolder) &
47+
BuildScript.Try(cleanSourceArchive) &
48+
tryCleanExtractorArgsLogs &
49+
BuildScript.DeleteFile(Extractor.GetCSharpLogPath());
50+
51+
/// <summary>
52+
/// Execute script `s` and check that the C# extractor has been executed.
53+
/// If either fails, attempt to cleanup any artifacts produced by the extractor,
54+
/// and exit with code 1, in order to proceed to the next attempt.
55+
/// </summary>
56+
BuildScript IntermediateAttempt(BuildScript s) =>
57+
(s & this.autobuilder.CheckExtractorRun(false)) |
58+
(attemptExtractorCleanup & BuildScript.Failure);
59+
60+
return
61+
// First try .NET Core
62+
IntermediateAttempt(this.DotNetRule.Analyse(this.autobuilder, true)) |
63+
// Then MSBuild
64+
(() => IntermediateAttempt(this.MsBuildRule.Analyse(this.autobuilder, true))) |
65+
// And finally look for a script that might be a build script
66+
(() => this.BuildCommandAutoRule.Analyse(this.autobuilder, true) & this.autobuilder.CheckExtractorRun(true));
67+
}
68+
}
69+
}

csharp/autobuilder/Semmle.Autobuild.CSharp/CSharpAutobuilder.cs

Lines changed: 132 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using Semmle.Extraction.CSharp;
22
using Semmle.Util.Logging;
33
using Semmle.Autobuild.Shared;
4+
using Semmle.Util;
5+
using System.Linq;
46

57
namespace Semmle.Autobuild.CSharp
68
{
@@ -29,25 +31,16 @@ public CSharpAutobuildOptions(IBuildActions actions) : base(actions)
2931

3032
public class CSharpAutobuilder : Autobuilder<CSharpAutobuildOptions>
3133
{
32-
public CSharpAutobuilder(IBuildActions actions, CSharpAutobuildOptions options) : base(actions, options) { }
34+
private const string buildCommandDocsUrl =
35+
"https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages";
3336

34-
public override BuildScript GetBuildScript()
35-
{
36-
/// <summary>
37-
/// A script that checks that the C# extractor has been executed.
38-
/// </summary>
39-
BuildScript CheckExtractorRun(bool warnOnFailure) =>
40-
BuildScript.Create(actions =>
41-
{
42-
if (actions.FileExists(Extractor.GetCSharpLogPath()))
43-
return 0;
37+
private readonly AutoBuildRule autoBuildRule;
4438

45-
if (warnOnFailure)
46-
Log(Severity.Error, "No C# code detected during build.");
47-
48-
return 1;
49-
});
39+
public CSharpAutobuilder(IBuildActions actions, CSharpAutobuildOptions options) : base(actions, options, new CSharpDiagnosticClassifier()) =>
40+
this.autoBuildRule = new AutoBuildRule(this);
5041

42+
public override BuildScript GetBuildScript()
43+
{
5144
var attempt = BuildScript.Failure;
5245
switch (GetCSharpBuildStrategy())
5346
{
@@ -65,47 +58,9 @@ BuildScript CheckExtractorRun(bool warnOnFailure) =>
6558
attempt = new DotNetRule().Analyse(this, false) & CheckExtractorRun(true);
6659
break;
6760
case CSharpBuildStrategy.Auto:
68-
var cleanTrapFolder =
69-
BuildScript.DeleteDirectory(TrapDir);
70-
var cleanSourceArchive =
71-
BuildScript.DeleteDirectory(SourceArchiveDir);
72-
var tryCleanExtractorArgsLogs =
73-
BuildScript.Create(actions =>
74-
{
75-
foreach (var file in Extractor.GetCSharpArgsLogs())
76-
{
77-
try
78-
{
79-
actions.FileDelete(file);
80-
}
81-
catch // lgtm[cs/catch-of-all-exceptions] lgtm[cs/empty-catch-block]
82-
{ }
83-
}
84-
85-
return 0;
86-
});
87-
var attemptExtractorCleanup =
88-
BuildScript.Try(cleanTrapFolder) &
89-
BuildScript.Try(cleanSourceArchive) &
90-
tryCleanExtractorArgsLogs &
91-
BuildScript.DeleteFile(Extractor.GetCSharpLogPath());
92-
93-
/// <summary>
94-
/// Execute script `s` and check that the C# extractor has been executed.
95-
/// If either fails, attempt to cleanup any artifacts produced by the extractor,
96-
/// and exit with code 1, in order to proceed to the next attempt.
97-
/// </summary>
98-
BuildScript IntermediateAttempt(BuildScript s) =>
99-
(s & CheckExtractorRun(false)) |
100-
(attemptExtractorCleanup & BuildScript.Failure);
101-
10261
attempt =
103-
// First try .NET Core
104-
IntermediateAttempt(new DotNetRule().Analyse(this, true)) |
105-
// Then MSBuild
106-
(() => IntermediateAttempt(new MsBuildRule().Analyse(this, true))) |
107-
// And finally look for a script that might be a build script
108-
(() => new BuildCommandAutoRule(DotNetRule.WithDotNet).Analyse(this, true) & CheckExtractorRun(true)) |
62+
// Attempt a few different build strategies to see if one works
63+
this.autoBuildRule.Analyse(this, true) |
10964
// All attempts failed: print message
11065
AutobuildFailure();
11166
break;
@@ -114,6 +69,127 @@ BuildScript IntermediateAttempt(BuildScript s) =>
11469
return attempt;
11570
}
11671

72+
/// <summary>
73+
/// A script that checks that the C# extractor has been executed.
74+
/// </summary>
75+
public BuildScript CheckExtractorRun(bool warnOnFailure) =>
76+
BuildScript.Create(actions =>
77+
{
78+
if (actions.FileExists(Extractor.GetCSharpLogPath()))
79+
return 0;
80+
81+
if (warnOnFailure)
82+
Log(Severity.Error, "No C# code detected during build.");
83+
84+
return 1;
85+
});
86+
87+
protected override void AutobuildFailureDiagnostic()
88+
{
89+
// if `ScriptPath` is not null here, the `BuildCommandAuto` rule was
90+
// run and found at least one script to execute
91+
if (this.autoBuildRule.BuildCommandAutoRule.ScriptPath is not null)
92+
{
93+
var relScriptPath = this.MakeRelative(autoBuildRule.BuildCommandAutoRule.ScriptPath);
94+
95+
// if we found multiple build scripts in the project directory, then we can say
96+
// as much to indicate that we may have picked the wrong one;
97+
// otherwise, we just report that the one script we found didn't work
98+
DiagnosticMessage message =
99+
this.autoBuildRule.BuildCommandAutoRule.CandidatePaths.Count() > 1 ?
100+
new(
101+
this.Options.Language,
102+
"multiple-build-scripts",
103+
"There are multiple potential build scripts",
104+
markdownMessage:
105+
"CodeQL found multiple potential build scripts for your project and " +
106+
$"attempted to run `{relScriptPath}`, which failed. " +
107+
"This may not be the right build script for your project. " +
108+
$"Set up a [manual build command]({buildCommandDocsUrl})."
109+
) :
110+
new(
111+
this.Options.Language,
112+
"script-failure",
113+
"Unable to build project using build script",
114+
markdownMessage:
115+
"CodeQL attempted to build your project using a script located at " +
116+
$"`{relScriptPath}`, which failed. " +
117+
$"Set up a [manual build command]({buildCommandDocsUrl})."
118+
);
119+
120+
AddDiagnostic(message);
121+
}
122+
123+
// project files which don't exist get marked as not .NET core projects, but we don't want
124+
// to show an error for this if the files don't exist
125+
var foundNotDotNetProjects = autoBuildRule.DotNetRule.NotDotNetProjects.Where(
126+
proj => this.Actions.FileExists(proj.FullPath)
127+
);
128+
129+
// both dotnet and msbuild builds require project or solution files; if we haven't found any
130+
// then neither of those rules would've worked
131+
if (this.ProjectsOrSolutionsToBuild.Count == 0)
132+
{
133+
this.AddDiagnostic(new(
134+
this.Options.Language,
135+
"no-projects-or-solutions",
136+
"No project or solutions files found",
137+
markdownMessage:
138+
"CodeQL could not find any project or solution files in your repository. " +
139+
$"Set up a [manual build command]({buildCommandDocsUrl})."
140+
));
141+
}
142+
// show a warning if there are projects which are not compatible with .NET Core, in case that is unintentional
143+
else if (foundNotDotNetProjects.Any())
144+
{
145+
this.AddDiagnostic(new(
146+
this.Options.Language,
147+
"dotnet-incompatible-projects",
148+
"Some projects are incompatible with .NET Core",
149+
severity: DiagnosticMessage.TspSeverity.Warning,
150+
markdownMessage: $"""
151+
CodeQL found some projects which cannot be built with .NET Core:
152+
153+
{autoBuildRule.DotNetRule.NotDotNetProjects.Select(p => this.MakeRelative(p.FullPath)).ToMarkdownList(MarkdownUtil.CodeFormatter, 5)}
154+
"""
155+
));
156+
}
157+
158+
// report any projects that failed to build with .NET Core
159+
if (autoBuildRule.DotNetRule.FailedProjectsOrSolutions.Any())
160+
{
161+
this.AddDiagnostic(new(
162+
this.Options.Language,
163+
"dotnet-build-failure",
164+
"Some projects or solutions failed to build using .NET Core",
165+
markdownMessage: $"""
166+
CodeQL was unable to build the following projects using .NET Core:
167+
168+
{autoBuildRule.DotNetRule.FailedProjectsOrSolutions.Select(p => this.MakeRelative(p.FullPath)).ToMarkdownList(MarkdownUtil.CodeFormatter, 10)}
169+
170+
Set up a [manual build command]({buildCommandDocsUrl}).
171+
"""
172+
));
173+
}
174+
175+
// report any projects that failed to build with MSBuild
176+
if (autoBuildRule.MsBuildRule.FailedProjectsOrSolutions.Any())
177+
{
178+
this.AddDiagnostic(new(
179+
this.Options.Language,
180+
"msbuild-build-failure",
181+
"Some projects or solutions failed to build using MSBuild",
182+
markdownMessage: $"""
183+
CodeQL was unable to build the following projects using MSBuild:
184+
185+
{autoBuildRule.MsBuildRule.FailedProjectsOrSolutions.Select(p => this.MakeRelative(p.FullPath)).ToMarkdownList(MarkdownUtil.CodeFormatter, 10)}
186+
187+
Set up a [manual build command]({buildCommandDocsUrl}).
188+
"""
189+
));
190+
}
191+
}
192+
117193
/// <summary>
118194
/// Gets the build strategy that the autobuilder should apply, based on the
119195
/// options in the `lgtm.yml` file.

0 commit comments

Comments
 (0)