C#: Recognize .proj files in autobuilder

When determining the target of `msbuild` or `dotnet build`, first look for `.proj`
files, then `.sln` files, and finally `.csproj`/`.vcxproj` files. In all three cases,
choose the project/solution file closest to the root.
This commit is contained in:
Tom Hvitved
2018-11-20 13:19:52 +01:00
parent b95d7e5302
commit 836daaf07b
11 changed files with 447 additions and 211 deletions

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System;
using System.Linq;
using Microsoft.Build.Construction;
using System.Xml;
namespace Semmle.Extraction.Tests
{
@@ -129,12 +130,24 @@ namespace Semmle.Extraction.Tests
string IBuildActions.PathCombine(params string[] parts)
{
return string.Join('\\', parts);
return string.Join('\\', parts.Where(p => !string.IsNullOrWhiteSpace(p)));
}
string IBuildActions.GetFullPath(string path) => path;
string IBuildActions.GetDirectoryName(string path) => System.IO.Path.GetDirectoryName(path);
void IBuildActions.WriteAllText(string filename, string contents)
{
}
public IDictionary<string, XmlDocument> LoadXml = new Dictionary<string, XmlDocument>();
XmlDocument IBuildActions.LoadXml(string filename)
{
if (LoadXml.TryGetValue(filename, out var xml))
return xml;
throw new ArgumentException("Missing LoadXml " + filename);
}
}
/// <summary>
@@ -142,23 +155,21 @@ namespace Semmle.Extraction.Tests
/// </summary>
class TestSolution : ISolution
{
public IEnumerable<Project> Projects => throw new NotImplementedException();
public IEnumerable<SolutionConfigurationInSolution> Configurations => throw new NotImplementedException();
public string DefaultConfigurationName => "Release";
public string DefaultPlatformName => "x86";
public string Path { get; set; }
public int ProjectCount => throw new NotImplementedException();
public string FullPath { get; set; }
public Version ToolsVersion => new Version("14.0");
public IEnumerable<IProjectOrSolution> IncludedProjects => throw new NotImplementedException();
public TestSolution(string path)
{
Path = path;
FullPath = path;
}
}
@@ -318,11 +329,11 @@ namespace Semmle.Extraction.Tests
}
Autobuilder CreateAutoBuilder(string lgtmLanguage, bool isWindows,
string buildless=null, string solution=null, string buildCommand=null, string ignoreErrors=null,
string msBuildArguments=null, string msBuildPlatform=null, string msBuildConfiguration=null, string msBuildTarget=null,
string dotnetArguments=null, string dotnetVersion=null, string vsToolsVersion=null,
string nugetRestore=null, string allSolutions=null,
string cwd=@"C:\Project")
string buildless = null, string solution = null, string buildCommand = null, string ignoreErrors = null,
string msBuildArguments = null, string msBuildPlatform = null, string msBuildConfiguration = null, string msBuildTarget = null,
string dotnetArguments = null, string dotnetVersion = null, string vsToolsVersion = null,
string nugetRestore = null, string allSolutions = null,
string cwd = @"C:\Project")
{
Actions.GetEnvironmentVariable["SEMMLE_DIST"] = @"C:\odasa";
Actions.GetEnvironmentVariable["SEMMLE_JAVA_HOME"] = @"C:\odasa\tools\java";
@@ -354,16 +365,26 @@ namespace Semmle.Extraction.Tests
public void TestDefaultCSharpAutoBuilder()
{
Actions.RunProcess["cmd.exe /C dotnet --info"] = 0;
Actions.RunProcess["cmd.exe /C dotnet clean"] = 0;
Actions.RunProcess["cmd.exe /C dotnet restore"] = 0;
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto dotnet build --no-incremental /p:UseSharedCompilation=false"] = 0;
Actions.RunProcess["cmd.exe /C dotnet clean test.csproj"] = 0;
Actions.RunProcess["cmd.exe /C dotnet restore test.csproj"] = 0;
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto dotnet build --no-incremental /p:UseSharedCompilation=false test.csproj"] = 0;
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --xml --extensions config csproj props xml"] = 0;
Actions.FileExists["csharp.log"] = true;
Actions.FileExists["test.csproj"] = true;
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbar.cs";
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbar.cs\ntest.csproj";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var xml = new XmlDocument();
xml.LoadXml(@"<Project Sdk=""Microsoft.NET.Sdk"">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
</Project>");
Actions.LoadXml["test.csproj"] = xml;
var autobuilder = CreateAutoBuilder("csharp", true);
TestAutobuilderScript(autobuilder, 0, 6);
@@ -373,16 +394,26 @@ namespace Semmle.Extraction.Tests
public void TestLinuxCSharpAutoBuilder()
{
Actions.RunProcess["dotnet --info"] = 0;
Actions.RunProcess["dotnet clean"] = 0;
Actions.RunProcess["dotnet restore"] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto dotnet build --no-incremental /p:UseSharedCompilation=false"] = 0;
Actions.RunProcess["dotnet clean test.csproj"] = 0;
Actions.RunProcess["dotnet restore test.csproj"] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto dotnet build --no-incremental /p:UseSharedCompilation=false test.csproj"] = 0;
Actions.RunProcess[@"C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --xml --extensions config csproj props xml"] = 0;
Actions.FileExists["csharp.log"] = true;
Actions.FileExists["test.csproj"] = true;
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs";
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs\ntest.csproj";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var xml = new XmlDocument();
xml.LoadXml(@"<Project Sdk=""Microsoft.NET.Sdk"">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
</Project>");
Actions.LoadXml["test.csproj"] = xml;
var autobuilder = CreateAutoBuilder("csharp", false);
TestAutobuilderScript(autobuilder, 0, 6);
@@ -391,10 +422,6 @@ namespace Semmle.Extraction.Tests
[Fact]
public void TestLinuxCSharpAutoBuilderExtractorFailed()
{
Actions.RunProcess["dotnet --info"] = 0;
Actions.RunProcess["dotnet clean"] = 0;
Actions.RunProcess["dotnet restore"] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto dotnet build --no-incremental /p:UseSharedCompilation=false"] = 0;
Actions.FileExists["csharp.log"] = false;
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
@@ -402,7 +429,7 @@ namespace Semmle.Extraction.Tests
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", false);
TestAutobuilderScript(autobuilder, 1, 4);
TestAutobuilderScript(autobuilder, 1, 0);
}
@@ -438,7 +465,7 @@ namespace Semmle.Extraction.Tests
var autobuilder = CreateAutoBuilder("cpp", true);
var solution = new TestSolution(@"C:\Project\test.sln");
autobuilder.SolutionsToBuild.Add(solution);
autobuilder.ProjectsOrSolutionsToBuild.Add(solution);
TestAutobuilderScript(autobuilder, 0, 2);
}
@@ -495,7 +522,7 @@ namespace Semmle.Extraction.Tests
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", false, buildless:"true");
var autobuilder = CreateAutoBuilder("csharp", false, buildless: "true");
TestAutobuilderScript(autobuilder, 0, 3);
}
@@ -550,7 +577,7 @@ namespace Semmle.Extraction.Tests
Assert.Equal(commandsRun, EndCallbackReturn.Count);
var action = Actions.RunProcess.GetEnumerator();
for(int cmd=0; cmd<commandsRun; ++cmd)
for (int cmd = 0; cmd < commandsRun; ++cmd)
{
Assert.True(action.MoveNext());
@@ -573,7 +600,7 @@ namespace Semmle.Extraction.Tests
SkipVsWhere();
var autobuilder = CreateAutoBuilder("csharp", false, buildCommand:"./build.sh --skip-tests");
var autobuilder = CreateAutoBuilder("csharp", false, buildCommand: "./build.sh --skip-tests");
TestAutobuilderScript(autobuilder, 0, 3);
}
@@ -584,7 +611,6 @@ namespace Semmle.Extraction.Tests
Actions.EnumerateDirectories[@"C:\Project"] = "";
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.RunProcess["dotnet --info"] = 1;
Actions.RunProcess["/bin/chmod u+x build/build.sh"] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto build/build.sh"] = 0;
Actions.RunProcessWorkingDirectory[@"C:\odasa\tools\odasa index --auto build/build.sh"] = "build";
@@ -593,7 +619,7 @@ namespace Semmle.Extraction.Tests
Actions.FileExists["csharp.log"] = true;
var autobuilder = CreateAutoBuilder("csharp", false);
TestAutobuilderScript(autobuilder, 0, 5);
TestAutobuilderScript(autobuilder, 0, 4);
}
[Fact]
@@ -604,14 +630,13 @@ namespace Semmle.Extraction.Tests
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.RunProcess["dotnet --info"] = 1;
Actions.RunProcess["/bin/chmod u+x build.sh"] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto build.sh"] = 0;
Actions.RunProcessWorkingDirectory[@"C:\odasa\tools\odasa index --auto build.sh"] = "";
Actions.FileExists["csharp.log"] = false;
var autobuilder = CreateAutoBuilder("csharp", false);
TestAutobuilderScript(autobuilder, 1, 3);
TestAutobuilderScript(autobuilder, 1, 2);
}
[Fact]
@@ -622,14 +647,13 @@ namespace Semmle.Extraction.Tests
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.RunProcess["dotnet --info"] = 1;
Actions.RunProcess["/bin/chmod u+x build.sh"] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto build.sh"] = 5;
Actions.RunProcessWorkingDirectory[@"C:\odasa\tools\odasa index --auto build.sh"] = "";
Actions.FileExists["csharp.log"] = true;
var autobuilder = CreateAutoBuilder("csharp", false);
TestAutobuilderScript(autobuilder, 1, 3);
TestAutobuilderScript(autobuilder, 1, 2);
}
[Fact]
@@ -639,7 +663,6 @@ namespace Semmle.Extraction.Tests
Actions.EnumerateDirectories[@"C:\Project"] = "";
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.RunProcess["cmd.exe /C dotnet --info"] = 1;
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto build.bat"] = 0;
Actions.RunProcessWorkingDirectory[@"cmd.exe /C C:\odasa\tools\odasa index --auto build.bat"] = "";
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
@@ -647,7 +670,7 @@ namespace Semmle.Extraction.Tests
Actions.FileExists["csharp.log"] = true;
var autobuilder = CreateAutoBuilder("csharp", true);
TestAutobuilderScript(autobuilder, 0, 4);
TestAutobuilderScript(autobuilder, 0, 3);
}
[Fact]
@@ -657,15 +680,14 @@ namespace Semmle.Extraction.Tests
Actions.EnumerateDirectories[@"C:\Project"] = "";
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.RunProcess["cmd.exe /C dotnet --info"] = 1;
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto build.bat"] = 1;
Actions.RunProcessWorkingDirectory[@"cmd.exe /C C:\odasa\tools\odasa index --auto build.bat"] = "";
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --xml --extensions config"] = 0;
Actions.FileExists["csharp.log"] = true;
var autobuilder = CreateAutoBuilder("csharp", true, ignoreErrors:"true");
TestAutobuilderScript(autobuilder, 1, 2);
var autobuilder = CreateAutoBuilder("csharp", true, ignoreErrors: "true");
TestAutobuilderScript(autobuilder, 1, 1);
}
[Fact]
@@ -707,12 +729,12 @@ namespace Semmle.Extraction.Tests
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest1.cs\ntest2.cs";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", true, msBuildArguments:"/P:Fu=Bar", msBuildTarget:"Windows", msBuildPlatform:"x86", msBuildConfiguration:"Debug",
vsToolsVersion:"12", allSolutions:"true");
var autobuilder = CreateAutoBuilder("csharp", true, msBuildArguments: "/P:Fu=Bar", msBuildTarget: "Windows", msBuildPlatform: "x86", msBuildConfiguration: "Debug",
vsToolsVersion: "12", allSolutions: "true");
var testSolution1 = new TestSolution(@"C:\Project\test1.sln");
var testSolution2 = new TestSolution(@"C:\Project\test2.sln");
autobuilder.SolutionsToBuild.Add(testSolution1);
autobuilder.SolutionsToBuild.Add(testSolution2);
autobuilder.ProjectsOrSolutionsToBuild.Add(testSolution1);
autobuilder.ProjectsOrSolutionsToBuild.Add(testSolution2);
TestAutobuilderScript(autobuilder, 0, 6);
}
@@ -737,8 +759,8 @@ namespace Semmle.Extraction.Tests
vsToolsVersion: "12", allSolutions: "true");
var testSolution1 = new TestSolution(@"C:\Project\test1.sln");
var testSolution2 = new TestSolution(@"C:\Project\test2.sln");
autobuilder.SolutionsToBuild.Add(testSolution1);
autobuilder.SolutionsToBuild.Add(testSolution2);
autobuilder.ProjectsOrSolutionsToBuild.Add(testSolution1);
autobuilder.ProjectsOrSolutionsToBuild.Add(testSolution2);
TestAutobuilderScript(autobuilder, 1, 2);
}
@@ -764,11 +786,11 @@ namespace Semmle.Extraction.Tests
var autobuilder = CreateAutoBuilder("csharp", true, msBuildArguments: "/P:Fu=Bar", msBuildTarget: "Windows",
msBuildPlatform: "x86", msBuildConfiguration: "Debug", vsToolsVersion: "12",
allSolutions: "true", nugetRestore:"false");
allSolutions: "true", nugetRestore: "false");
var testSolution1 = new TestSolution(@"C:\Project\test1.sln");
var testSolution2 = new TestSolution(@"C:\Project\test2.sln");
autobuilder.SolutionsToBuild.Add(testSolution1);
autobuilder.SolutionsToBuild.Add(testSolution2);
autobuilder.ProjectsOrSolutionsToBuild.Add(testSolution1);
autobuilder.ProjectsOrSolutionsToBuild.Add(testSolution2);
TestAutobuilderScript(autobuilder, 0, 4);
}
@@ -785,7 +807,7 @@ namespace Semmle.Extraction.Tests
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", false, buildless: "true", solution: "foo.sln", nugetRestore:"false");
var autobuilder = CreateAutoBuilder("csharp", false, buildless: "true", solution: "foo.sln", nugetRestore: "false");
TestAutobuilderScript(autobuilder, 0, 3);
}
@@ -794,18 +816,28 @@ namespace Semmle.Extraction.Tests
public void TestSkipNugetDotnet()
{
Actions.RunProcess["dotnet --info"] = 0;
Actions.RunProcess["dotnet clean"] = 0;
Actions.RunProcess["dotnet restore"] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto dotnet build --no-incremental /p:UseSharedCompilation=false --no-restore"] = 0;
Actions.RunProcess["dotnet clean test.csproj"] = 0;
Actions.RunProcess["dotnet restore test.csproj"] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto dotnet build --no-incremental /p:UseSharedCompilation=false --no-restore test.csproj"] = 0;
Actions.RunProcess[@"C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --xml --extensions config csproj props xml"] = 0;
Actions.FileExists["csharp.log"] = true;
Actions.FileExists["test.csproj"] = true;
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs";
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs\ntest.csproj";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var xml = new XmlDocument();
xml.LoadXml(@"<Project Sdk=""Microsoft.NET.Sdk"">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
var autobuilder = CreateAutoBuilder("csharp", false, dotnetArguments:"--no-restore"); // nugetRestore=false does not work for now.
</Project>");
Actions.LoadXml["test.csproj"] = xml;
var autobuilder = CreateAutoBuilder("csharp", false, dotnetArguments: "--no-restore"); // nugetRestore=false does not work for now.
TestAutobuilderScript(autobuilder, 0, 6);
}
@@ -818,19 +850,29 @@ namespace Semmle.Extraction.Tests
Actions.RunProcess[@"chmod u+x dotnet-install.sh"] = 0;
Actions.RunProcess[@"./dotnet-install.sh --channel release --version 2.1.3 --install-dir C:\Project\.dotnet"] = 0;
Actions.RunProcess[@"C:\Project\.dotnet\dotnet --info"] = 0;
Actions.RunProcess[@"C:\Project\.dotnet\dotnet clean"] = 0;
Actions.RunProcess[@"C:\Project\.dotnet\dotnet restore"] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto C:\Project\.dotnet\dotnet build --no-incremental /p:UseSharedCompilation=false"] = 0;
Actions.RunProcess[@"C:\Project\.dotnet\dotnet clean test.csproj"] = 0;
Actions.RunProcess[@"C:\Project\.dotnet\dotnet restore test.csproj"] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto C:\Project\.dotnet\dotnet build --no-incremental /p:UseSharedCompilation=false test.csproj"] = 0;
Actions.RunProcess[@"C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --xml --extensions config csproj props xml"] = 0;
Actions.FileExists["csharp.log"] = true;
Actions.FileExists["test.csproj"] = true;
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.GetEnvironmentVariable["PATH"] = "/bin:/usr/bin";
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs";
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs\ntest.csproj";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var xml = new XmlDocument();
xml.LoadXml(@"<Project Sdk=""Microsoft.NET.Sdk"">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
var autobuilder = CreateAutoBuilder("csharp", false, dotnetVersion:"2.1.3");
</Project>");
Actions.LoadXml["test.csproj"] = xml;
var autobuilder = CreateAutoBuilder("csharp", false, dotnetVersion: "2.1.3");
TestAutobuilderScript(autobuilder, 0, 10);
}
@@ -843,17 +885,27 @@ namespace Semmle.Extraction.Tests
Actions.RunProcess[@"chmod u+x dotnet-install.sh"] = 0;
Actions.RunProcess[@"./dotnet-install.sh --channel release --version 2.1.3 --install-dir C:\Project\.dotnet"] = 0;
Actions.RunProcess[@"C:\Project\.dotnet\dotnet --info"] = 0;
Actions.RunProcess[@"C:\Project\.dotnet\dotnet clean"] = 0;
Actions.RunProcess[@"C:\Project\.dotnet\dotnet restore"] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto C:\Project\.dotnet\dotnet build --no-incremental /p:UseSharedCompilation=false"] = 0;
Actions.RunProcess[@"C:\Project\.dotnet\dotnet clean test.csproj"] = 0;
Actions.RunProcess[@"C:\Project\.dotnet\dotnet restore test.csproj"] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto C:\Project\.dotnet\dotnet build --no-incremental /p:UseSharedCompilation=false test.csproj"] = 0;
Actions.RunProcess[@"C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --xml --extensions config csproj props xml"] = 0;
Actions.FileExists["csharp.log"] = true;
Actions.FileExists["test.csproj"] = true;
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.GetEnvironmentVariable["PATH"] = "/bin:/usr/bin";
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbar.cs";
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbar.cs\ntest.csproj";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var xml = new XmlDocument();
xml.LoadXml(@"<Project Sdk=""Microsoft.NET.Sdk"">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
</Project>");
Actions.LoadXml["test.csproj"] = xml;
var autobuilder = CreateAutoBuilder("csharp", false, dotnetVersion: "2.1.3");
TestAutobuilderScript(autobuilder, 0, 10);
@@ -866,20 +918,109 @@ namespace Semmle.Extraction.Tests
Actions.RunProcessOut["cmd.exe /C dotnet --list-sdks"] = "2.1.3 [C:\\Program Files\\dotnet\\sdks]\n2.1.4 [C:\\Program Files\\dotnet\\sdks]";
Actions.RunProcess[@"cmd.exe /C powershell -NoProfile -ExecutionPolicy unrestricted -file C:\Project\install-dotnet.ps1 -Version 2.1.3 -InstallDir C:\Project\.dotnet"] = 0;
Actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet --info"] = 0;
Actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet clean"] = 0;
Actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet restore"] = 0;
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto C:\Project\.dotnet\dotnet build --no-incremental /p:UseSharedCompilation=false"] = 0;
Actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet clean test.csproj"] = 0;
Actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet restore test.csproj"] = 0;
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto C:\Project\.dotnet\dotnet build --no-incremental /p:UseSharedCompilation=false test.csproj"] = 0;
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --xml --extensions config csproj props xml"] = 0;
Actions.FileExists["csharp.log"] = true;
Actions.FileExists["test.csproj"] = true;
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.GetEnvironmentVariable["PATH"] = "/bin:/usr/bin";
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs";
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs\ntest.csproj";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var xml = new XmlDocument();
xml.LoadXml(@"<Project Sdk=""Microsoft.NET.Sdk"">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
</Project>");
Actions.LoadXml["test.csproj"] = xml;
var autobuilder = CreateAutoBuilder("csharp", true, dotnetVersion: "2.1.3");
TestAutobuilderScript(autobuilder, 0, 8);
}
[Fact]
public void TestDirsProjWindows()
{
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\csharp\nuget\nuget.exe restore dirs.proj"] = 1;
Actions.RunProcess["cmd.exe /C CALL ^\"C:\\Program Files ^(x86^)\\Microsoft Visual Studio 12.0\\VC\\vcvarsall.bat^\" && C:\\odasa\\tools\\odasa index --auto msbuild dirs.proj /p:UseSharedCompilation=false /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /p:MvcBuildViews=true /P:Fu=Bar"] = 0;
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --xml --extensions config csproj props xml"] = 0;
Actions.FileExists["csharp.log"] = true;
Actions.FileExists[@"a\test.csproj"] = true;
Actions.FileExists["dirs.proj"] = true;
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = false;
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"] = false;
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat"] = true;
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat"] = false;
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"] = true;
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.EnumerateFiles[@"C:\Project"] = "a\\test.cs\na\\test.csproj\ndirs.proj";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var csproj = new XmlDocument();
csproj.LoadXml(@"<?xml version=""1.0"" encoding=""utf - 8""?>
<Project ToolsVersion=""15.0"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<ItemGroup>
<Compile Include=""test.cs"" />
</ItemGroup>
</Project>");
Actions.LoadXml["a\\test.csproj"] = csproj;
var dirsproj = new XmlDocument();
dirsproj.LoadXml(@"<Project DefaultTargets=""Build"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"" ToolsVersion=""3.5"">
<ItemGroup>
<ProjectFiles Include=""a\test.csproj"" />
</ItemGroup>
</Project>");
Actions.LoadXml["dirs.proj"] = dirsproj;
var autobuilder = CreateAutoBuilder("csharp", true, msBuildArguments: "/P:Fu=Bar", msBuildTarget: "Windows", msBuildPlatform: "x86", msBuildConfiguration: "Debug",
vsToolsVersion: "12", allSolutions: "true");
TestAutobuilderScript(autobuilder, 0, 4);
}
[Fact]
public void TestDirsProjLinux()
{
Actions.RunProcess[@"mono C:\odasa\tools\csharp\nuget\nuget.exe restore dirs.proj"] = 1;
Actions.RunProcess[@"C:\odasa\tools\odasa index --auto msbuild dirs.proj /p:UseSharedCompilation=false /t:rebuild /p:MvcBuildViews=true"] = 0;
Actions.RunProcess[@"C:\odasa\tools\java\bin\java -jar C:\odasa\tools\extractor-asp.jar ."] = 0;
Actions.RunProcess[@"C:\odasa\tools\odasa index --xml --extensions config csproj props xml"] = 0;
Actions.FileExists["csharp.log"] = true;
Actions.FileExists["a/test.csproj"] = true;
Actions.FileExists["dirs.proj"] = true;
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.EnumerateFiles[@"C:\Project"] = "a/test.cs\na/test.csproj\ndirs.proj";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var csproj = new XmlDocument();
csproj.LoadXml(@"<?xml version=""1.0"" encoding=""utf - 8""?>
<Project ToolsVersion=""15.0"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<ItemGroup>
<Compile Include=""test.cs"" />
</ItemGroup>
</Project>");
Actions.LoadXml["a/test.csproj"] = csproj;
var dirsproj = new XmlDocument();
dirsproj.LoadXml(@"<Project DefaultTargets=""Build"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"" ToolsVersion=""3.5"">
<ItemGroup>
<ProjectFiles Include=""a\test.csproj"" />
</ItemGroup>
</Project>");
Actions.LoadXml["dirs.proj"] = dirsproj;
var autobuilder = CreateAutoBuilder("csharp", false);
TestAutobuilderScript(autobuilder, 0, 4);
}
}
}

View File

@@ -29,26 +29,32 @@ namespace Semmle.Autobuild
public class Autobuilder
{
/// <summary>
/// Full file paths of files found in the project directory.
/// Full file paths of files found in the project directory, as well
/// their distance from the project root folder. The list is sorted
/// by distance in ascending order.
/// </summary>
public IEnumerable<string> Paths => pathsLazy.Value;
readonly Lazy<IEnumerable<string>> pathsLazy;
public IEnumerable<(string, int)> Paths => pathsLazy.Value;
readonly Lazy<IEnumerable<(string, int)>> pathsLazy;
/// <summary>
/// Gets a list of paths matching a set of extensions
/// (including the ".").
/// (including the "."), as well their distance from the project root folder.
/// The list is sorted by distance in ascending order.
/// </summary>
/// <param name="extensions">The extensions to find.</param>
/// <returns>The files matching the extension.</returns>
public IEnumerable<string> GetExtensions(params string[] extensions) =>
Paths.Where(p => extensions.Contains(Path.GetExtension(p)));
public IEnumerable<(string, int)> GetExtensions(params string[] extensions) =>
Paths.Where(p => extensions.Contains(Path.GetExtension(p.Item1)));
/// <summary>
/// Gets all paths matching a particular filename.
/// Gets all paths matching a particular filename, as well
/// their distance from the project root folder. The list is sorted
/// by distance in ascending order.
/// </summary>
/// <param name="name">The filename to find.</param>
/// <returns>Possibly empty sequence of paths with the given filename.</returns>
public IEnumerable<string> GetFilename(string name) => Paths.Where(p => Path.GetFileName(p) == name);
public IEnumerable<(string, int)> GetFilename(string name) =>
Paths.Where(p => Path.GetFileName(p.Item1) == name);
/// <summary>
/// Holds if a given path, relative to the root of the source directory
@@ -59,30 +65,30 @@ namespace Semmle.Autobuild
public bool HasRelativePath(string path) => HasPath(Actions.PathCombine(RootDirectory, path));
/// <summary>
/// List of solution files to build.
/// List of project/solution files to build.
/// </summary>
public IList<ISolution> SolutionsToBuild => solutionsToBuildLazy.Value;
readonly Lazy<IList<ISolution>> solutionsToBuildLazy;
public IList<IProjectOrSolution> ProjectsOrSolutionsToBuild => projectsOrSolutionsToBuildLazy.Value;
readonly Lazy<IList<IProjectOrSolution>> projectsOrSolutionsToBuildLazy;
/// <summary>
/// Holds if a given path was found.
/// </summary>
/// <param name="path">The path of the file.</param>
/// <returns>True iff the path was found.</returns>
public bool HasPath(string path) => Paths.Any(p => path == p);
public bool HasPath(string path) => Paths.Any(p => path == p.Item1);
void FindFiles(string dir, int depth, IList<string> results)
void FindFiles(string dir, int depth, int maxDepth, IList<(string, int)> results)
{
foreach (var f in Actions.EnumerateFiles(dir))
{
results.Add(f);
results.Add((f, depth));
}
if (depth > 1)
if (depth < maxDepth)
{
foreach (var d in Actions.EnumerateDirectories(dir))
{
FindFiles(d, depth - 1, results);
FindFiles(d, depth + 1, maxDepth, results);
}
}
}
@@ -113,46 +119,75 @@ namespace Semmle.Autobuild
Actions = actions;
Options = options;
pathsLazy = new Lazy<IEnumerable<string>>(() =>
pathsLazy = new Lazy<IEnumerable<(string, int)>>(() =>
{
var files = new List<string>();
FindFiles(options.RootDirectory, options.SearchDepth, files);
return files.
OrderBy(s => s.Count(c => c == Path.DirectorySeparatorChar)).
ThenBy(s => Path.GetFileName(s).Length).
ToArray();
var files = new List<(string, int)>();
FindFiles(options.RootDirectory, 0, options.SearchDepth, files);
return files.OrderBy(f => f.Item2).ToArray();
});
solutionsToBuildLazy = new Lazy<IList<ISolution>>(() =>
projectsOrSolutionsToBuildLazy = new Lazy<IList<IProjectOrSolution>>(() =>
{
if (options.Solution.Any())
{
var ret = new List<ISolution>();
var ret = new List<IProjectOrSolution>();
foreach (var solution in options.Solution)
{
if (actions.FileExists(solution))
ret.Add(new Solution(this, solution));
else
Log(Severity.Error, "The specified solution file {0} was not found", solution);
Log(Severity.Error, $"The specified solution file {solution} was not found");
}
return ret;
}
var solutions = GetExtensions(".sln").
Select(s => new Solution(this, s)).
Where(s => s.ProjectCount > 0).
OrderByDescending(s => s.ProjectCount).
ThenBy(s => s.Path.Length).
bool FindFiles(string extension, Func<string, ProjectOrSolution> create, out IEnumerable<IProjectOrSolution> files)
{
var allFiles = GetExtensions(extension).
Select(p => (ProjectOrSolution: create(p.Item1), DistanceFromRoot: p.Item2)).
Where(p => p.ProjectOrSolution.HasLanguage(this.Options.Language)).
ToArray();
foreach (var sln in solutions)
{
Log(Severity.Info, $"Found {sln.Path} with {sln.ProjectCount} {this.Options.Language} projects, version {sln.ToolsVersion}, config {string.Join(" ", sln.Configurations.Select(c => c.FullName))}");
if (allFiles.Length == 0)
{
files = null;
return false;
}
if (options.AllSolutions)
{
files = allFiles.Select(p => p.ProjectOrSolution);
return true;
}
var firstIsClosest = allFiles.Length > 1 && allFiles[0].DistanceFromRoot < allFiles[1].DistanceFromRoot;
if (allFiles.Length == 1 || firstIsClosest)
{
files = allFiles.Select(p => p.ProjectOrSolution).Take(1);
return true;
}
var candidates = allFiles.
Where(f => f.DistanceFromRoot == allFiles[0].DistanceFromRoot).
Select(f => f.ProjectOrSolution);
Log(Severity.Info, $"Found multiple '{extension}' files, giving up: {string.Join(", ", candidates)}.");
files = new IProjectOrSolution[0];
return true;
}
return new List<ISolution>(options.AllSolutions ?
solutions :
solutions.Take(1));
// First look for `.proj` files
if (FindFiles(".proj", f => new Project(this, f), out var ret1))
return ret1.ToList();
// Then look for `.sln` files
if (FindFiles(".sln", f => new Solution(this, f), out var ret2))
return ret2.ToList();
// Finally look for language specific project files, e.g. `.csproj` files
if (FindFiles(this.Options.Language.ProjectExtension, f => new Project(this, f), out var ret3))
return ret3.ToList();
return new List<IProjectOrSolution>();
});
SemmleDist = Actions.GetEnvironmentVariable("SEMMLE_DIST");

View File

@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml;
namespace Semmle.Autobuild
{
@@ -96,12 +97,27 @@ namespace Semmle.Autobuild
/// <returns>The combined path.</returns>
string PathCombine(params string[] parts);
/// <summary>
/// Gets the full path for <paramref name="path"/>, Path.GetFullPath().
/// </summary>
string GetFullPath(string path);
/// <summary>
/// Gets the directory of <paramref name="path"/>, Path.GetDirectoryName().
/// </summary>
string GetDirectoryName(string path);
/// <summary>
/// Writes contents to file, File.WriteAllText().
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="contents">The text.</param>
void WriteAllText(string filename, string contents);
/// <summary>
/// Loads the XML document from <paramref name="filename"/>.
/// </summary>
XmlDocument LoadXml(string filename);
}
/// <summary>
@@ -167,10 +183,17 @@ namespace Semmle.Autobuild
void IBuildActions.WriteAllText(string filename, string contents) => File.WriteAllText(filename, contents);
private SystemBuildActions()
XmlDocument IBuildActions.LoadXml(string filename)
{
var ret = new XmlDocument();
ret.Load(filename);
return ret;
}
string IBuildActions.GetFullPath(string path) => Path.GetFullPath(path);
string IBuildActions.GetDirectoryName(string path) => Path.GetDirectoryName(path);
public static readonly IBuildActions Instance = new SystemBuildActions();
}
}

View File

@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Semmle.Util;
using Semmle.Util.Logging;
namespace Semmle.Autobuild
@@ -32,7 +31,7 @@ namespace Semmle.Autobuild
var extensions = builder.Actions.IsWindows() ? winExtensions : linuxExtensions;
var scripts = buildScripts.SelectMany(s => extensions.Select(e => s + e));
var scriptPath = builder.Paths.Where(p => scripts.Any(p.ToLower().EndsWith)).OrderBy(p => p.Length).FirstOrDefault();
var scriptPath = builder.Paths.Where(p => scripts.Any(p.Item1.ToLower().EndsWith)).OrderBy(p => p.Item2).Select(p => p.Item1).FirstOrDefault();
if (scriptPath == null)
return BuildScript.Failure;
@@ -41,12 +40,12 @@ namespace Semmle.Autobuild
chmod.RunCommand("/bin/chmod", $"u+x {scriptPath}");
var chmodScript = builder.Actions.IsWindows() ? BuildScript.Success : BuildScript.Try(chmod.Script);
var path = Path.GetDirectoryName(scriptPath);
var dir = builder.Actions.GetDirectoryName(scriptPath);
// A specific .NET Core version may be required
return chmodScript & DotNetRule.WithDotNet(builder, dotNet =>
{
var command = new CommandBuilder(builder.Actions, path, dotNet?.Environment);
var command = new CommandBuilder(builder.Actions, dir, dotNet?.Environment);
// A specific Visual Studio version may be required
var vsTools = MsBuildRule.GetVcVarsBatFile(builder);

View File

@@ -4,6 +4,7 @@ using System.Linq;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.IO;
using Semmle.Util;
namespace Semmle.Autobuild
{
@@ -15,46 +16,36 @@ namespace Semmle.Autobuild
{
public BuildScript Analyse(Autobuilder builder)
{
builder.Log(Severity.Info, "Attempting to build using .NET Core");
if (!builder.ProjectsOrSolutionsToBuild.Any())
return BuildScript.Failure;
var projects = builder.SolutionsToBuild.Any()
? builder.SolutionsToBuild.SelectMany(s => s.Projects).ToArray()
: builder.GetExtensions(Language.CSharp.ProjectExtension).Select(p => new Project(builder, p)).ToArray();
var notDotNetProject = projects.FirstOrDefault(p => !p.DotNetProject);
var notDotNetProject = builder.ProjectsOrSolutionsToBuild.
SelectMany(p => Enumerators.Singleton(p).Concat(p.IncludedProjects)).
OfType<Project>().
FirstOrDefault(p => !p.DotNetProject);
if (notDotNetProject != null)
{
builder.Log(Severity.Info, "Not using .NET Core because of incompatible project {0}", notDotNetProject);
return BuildScript.Failure;
}
if (!builder.SolutionsToBuild.Any())
// Attempt dotnet build in root folder
return WithDotNet(builder, dotNet =>
{
var info = GetInfoCommand(builder.Actions, dotNet);
var clean = GetCleanCommand(builder.Actions, dotNet).Script;
var restore = GetRestoreCommand(builder.Actions, dotNet).Script;
var build = GetBuildCommand(builder, dotNet).Script;
return info & clean & BuildScript.Try(restore) & build;
});
builder.Log(Severity.Info, "Attempting to build using .NET Core");
// Attempt dotnet build on each solution
return WithDotNet(builder, dotNet =>
{
var ret = GetInfoCommand(builder.Actions, dotNet);
foreach (var solution in builder.SolutionsToBuild)
foreach (var projectOrSolution in builder.ProjectsOrSolutionsToBuild)
{
var cleanCommand = GetCleanCommand(builder.Actions, dotNet);
cleanCommand.QuoteArgument(solution.Path);
cleanCommand.QuoteArgument(projectOrSolution.FullPath);
var clean = cleanCommand.Script;
var restoreCommand = GetRestoreCommand(builder.Actions, dotNet);
restoreCommand.QuoteArgument(solution.Path);
restoreCommand.QuoteArgument(projectOrSolution.FullPath);
var restore = restoreCommand.Script;
var buildCommand = GetBuildCommand(builder, dotNet);
buildCommand.QuoteArgument(solution.Path);
buildCommand.QuoteArgument(projectOrSolution.FullPath);
var build = buildCommand.Script;
ret &= clean & BuildScript.Try(restore) & build;
@@ -110,7 +101,7 @@ namespace Semmle.Autobuild
// See https://docs.microsoft.com/en-us/dotnet/core/tools/global-json
var installScript = BuildScript.Success;
var validGlobalJson = false;
foreach (var path in builder.Paths.Where(p => p.EndsWith("global.json", StringComparison.Ordinal)))
foreach (var path in builder.Paths.Select(p => p.Item1).Where(p => p.EndsWith("global.json", StringComparison.Ordinal)))
{
string version;
try

View File

@@ -8,9 +8,6 @@
public bool ProjectFileHasThisLanguage(string path) =>
System.IO.Path.GetExtension(path) == ProjectExtension;
public static bool IsProjectFileForAnySupportedLanguage(string path) =>
Cpp.ProjectFileHasThisLanguage(path) || CSharp.ProjectFileHasThisLanguage(path);
public readonly string ProjectExtension;
private Language(string extension)

View File

@@ -16,19 +16,19 @@ namespace Semmle.Autobuild
public BuildScript Analyse(Autobuilder builder)
{
builder.Log(Severity.Info, "Attempting to build using MSBuild");
if (!builder.SolutionsToBuild.Any())
{
builder.Log(Severity.Info, "Could not find a suitable solution file to build");
if (!builder.ProjectsOrSolutionsToBuild.Any())
return BuildScript.Failure;
}
builder.Log(Severity.Info, "Attempting to build using MSBuild");
var vsTools = GetVcVarsBatFile(builder);
if (vsTools == null && builder.SolutionsToBuild.Any())
if (vsTools == null && builder.ProjectsOrSolutionsToBuild.Any())
{
vsTools = BuildTools.FindCompatibleVcVars(builder.Actions, builder.SolutionsToBuild.First());
var firstSolution = builder.ProjectsOrSolutionsToBuild.OfType<ISolution>().FirstOrDefault();
vsTools = firstSolution != null
? BuildTools.FindCompatibleVcVars(builder.Actions, firstSolution)
: BuildTools.VcVarsAllBatFiles(builder.Actions).OrderByDescending(b => b.ToolsVersion).FirstOrDefault();
}
if (vsTools == null && builder.Actions.IsWindows())
@@ -40,36 +40,42 @@ namespace Semmle.Autobuild
var ret = BuildScript.Success;
foreach (var solution in builder.SolutionsToBuild)
foreach (var projectOrSolution in builder.ProjectsOrSolutionsToBuild)
{
if (builder.Options.NugetRestore)
{
var nugetCommand = new CommandBuilder(builder.Actions).
RunCommand(nuget).
Argument("restore").
QuoteArgument(solution.Path);
QuoteArgument(projectOrSolution.FullPath);
ret &= BuildScript.Try(nugetCommand.Script);
}
var command = new CommandBuilder(builder.Actions);
if (vsTools != null)
{
command.CallBatFile(vsTools.Path);
}
command.IndexCommand(builder.Odasa, MsBuild);
command.QuoteArgument(solution.Path);
command.QuoteArgument(projectOrSolution.FullPath);
command.Argument("/p:UseSharedCompilation=false");
string target = builder.Options.MsBuildTarget != null ? builder.Options.MsBuildTarget : "rebuild";
string platform = builder.Options.MsBuildPlatform != null ? builder.Options.MsBuildPlatform : solution.DefaultPlatformName;
string configuration = builder.Options.MsBuildConfiguration != null ? builder.Options.MsBuildConfiguration : solution.DefaultConfigurationName;
string target = builder.Options.MsBuildTarget != null
? builder.Options.MsBuildTarget
: "rebuild";
string platform = builder.Options.MsBuildPlatform != null
? builder.Options.MsBuildPlatform
: projectOrSolution is ISolution s1 ? s1.DefaultPlatformName : null;
string configuration = builder.Options.MsBuildConfiguration != null
? builder.Options.MsBuildConfiguration
: projectOrSolution is ISolution s2 ? s2.DefaultConfigurationName : null;
command.Argument("/t:" + target);
command.Argument(string.Format("/p:Platform=\"{0}\"", platform));
command.Argument(string.Format("/p:Configuration=\"{0}\"", configuration));
if (platform != null)
command.Argument(string.Format("/p:Platform=\"{0}\"", platform));
if (configuration != null)
command.Argument(string.Format("/p:Configuration=\"{0}\"", configuration));
command.Argument("/p:MvcBuildViews=true");
command.Argument(builder.Options.MsBuildArguments);

View File

@@ -1,16 +1,18 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using Semmle.Util.Logging;
namespace Semmle.Autobuild
{
/// <summary>
/// Representation of a .csproj (C#) or .vcxproj (C++) file.
/// Representation of a .proj file, a .csproj file (C#), or a .vcxproj file (C++).
/// C# project files come in 2 flavours, .Net core and msbuild, but they
/// have the same file extension.
/// </summary>
public class Project
public class Project : ProjectOrSolution
{
/// <summary>
/// Holds if this project is for .Net core.
@@ -21,18 +23,28 @@ namespace Semmle.Autobuild
public Version ToolsVersion { get; private set; }
readonly string filename;
readonly List<Project> includedProjects = new List<Project>();
public override IEnumerable<IProjectOrSolution> IncludedProjects =>
includedProjects.Concat(includedProjects.SelectMany(s => s.IncludedProjects));
public Project(Autobuilder builder, string filename)
public Project(Autobuilder builder, string path) : base(builder, path)
{
this.filename = filename;
ToolsVersion = new Version();
if (!File.Exists(filename))
if (!builder.Actions.FileExists(FullPath))
return;
var projFile = new XmlDocument();
projFile.Load(filename);
XmlDocument projFile;
try
{
projFile = builder.Actions.LoadXml(FullPath);
}
catch (XmlException)
{
builder.Log(Severity.Info, $"Skipping project file {path} as it is not a valid XML document.");
return;
}
var root = projFile.DocumentElement;
if (root.Name == "Project")
@@ -40,30 +52,35 @@ namespace Semmle.Autobuild
if (root.HasAttribute("Sdk"))
{
DotNetProject = true;
return;
}
else
var toolsVersion = root.GetAttribute("ToolsVersion");
if (!string.IsNullOrEmpty(toolsVersion))
{
var toolsVersion = root.GetAttribute("ToolsVersion");
if (string.IsNullOrEmpty(toolsVersion))
try
{
builder.Log(Severity.Warning, "Project {0} is missing a tools version", filename);
ToolsVersion = new Version(toolsVersion);
ValidToolsVersion = true;
}
else
catch // Generic catch clause - Version constructor throws about 5 different exceptions.
{
try
{
ToolsVersion = new Version(toolsVersion);
ValidToolsVersion = true;
}
catch // Generic catch clause - Version constructor throws about 5 different exceptions.
{
builder.Log(Severity.Warning, "Project {0} has invalid tools version {1}", filename, toolsVersion);
}
builder.Log(Severity.Warning, "Project {0} has invalid tools version {1}", path, toolsVersion);
}
}
// The documentation on `.proj` files is very limited, but it appears that both
// `<ProjectFile Include="X"/>` and `<ProjectFiles Include="X"/>` is valid
var mgr = new XmlNamespaceManager(projFile.NameTable);
mgr.AddNamespace("msbuild", "http://schemas.microsoft.com/developer/msbuild/2003");
var projectFileIncludes = root.SelectNodes("//msbuild:Project/msbuild:ItemGroup/msbuild:ProjectFile/@Include", mgr).OfType<XmlNode>();
var projectFilesIncludes = root.SelectNodes("//msbuild:Project/msbuild:ItemGroup/msbuild:ProjectFiles/@Include", mgr).OfType<XmlNode>();
foreach (var include in projectFileIncludes.Concat(projectFilesIncludes))
{
var includePath = builder.Actions.IsWindows() ? include.Value : include.Value.Replace("\\", "/");
includedProjects.Add(new Project(builder, builder.Actions.PathCombine(builder.Actions.GetDirectoryName(this.FullPath), includePath)));
}
}
}
public override string ToString() => filename;
}
}

View File

@@ -0,0 +1,47 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Semmle.Autobuild
{
/// <summary>
/// A file that can be the target in an invocation of `msbuild` or `dotnet build`.
/// Either a solution file or a project file (`.proj`, `.csproj`, or `.vcxproj`).
/// </summary>
public interface IProjectOrSolution
{
/// <summary>
/// Gets the full path of this file.
/// </summary>
string FullPath { get; }
/// <summary>
/// Gets a list of other projects included by this file.
/// </summary>
IEnumerable<IProjectOrSolution> IncludedProjects { get; }
}
public abstract class ProjectOrSolution : IProjectOrSolution
{
public string FullPath { get; private set; }
protected ProjectOrSolution(Autobuilder builder, string path)
{
FullPath = builder.Actions.GetFullPath(path);
}
public abstract IEnumerable<IProjectOrSolution> IncludedProjects { get; }
public override string ToString() => FullPath;
}
public static class IProjectOrSolutionExtensions
{
/// <summary>
/// Holds if this file includes a project with code from language <paramref name="l"/>.
/// </summary>
public static bool HasLanguage(this IProjectOrSolution p, Language l) =>
l.ProjectFileHasThisLanguage(p.FullPath) ||
p.IncludedProjects.Any(p0 => l.ProjectFileHasThisLanguage(p0.FullPath));
}
}

View File

@@ -10,15 +10,8 @@ namespace Semmle.Autobuild
/// <summary>
/// A solution file, extension .sln.
/// </summary>
public interface ISolution
public interface ISolution : IProjectOrSolution
{
/// <summary>
/// List of C# or C++ projects contained in the solution.
/// (There could be other project types as well - these are ignored.)
/// </summary>
IEnumerable<Project> Projects { get; }
/// <summary>
/// Solution configurations.
/// </summary>
@@ -34,16 +27,6 @@ namespace Semmle.Autobuild
/// </summary>
string DefaultPlatformName { get; }
/// <summary>
/// The path of the solution file.
/// </summary>
string Path { get; }
/// <summary>
/// The number of C# or C++ projects.
/// </summary>
int ProjectCount { get; }
/// <summary>
/// Gets the "best" tools version for this solution.
/// If there are several versions, because the project files
@@ -56,11 +39,12 @@ namespace Semmle.Autobuild
/// <summary>
/// A solution file on the filesystem, read using Microsoft.Build.
/// </summary>
class Solution : ISolution
class Solution : ProjectOrSolution, ISolution
{
readonly SolutionFile solution;
public IEnumerable<Project> Projects { get; private set; }
readonly IEnumerable<Project> includedProjects;
public override IEnumerable<IProjectOrSolution> IncludedProjects => includedProjects;
public IEnumerable<SolutionConfigurationInSolution> Configurations =>
solution == null ? Enumerable.Empty<SolutionConfigurationInSolution>() : solution.SolutionConfigurations;
@@ -71,18 +55,16 @@ namespace Semmle.Autobuild
public string DefaultPlatformName =>
solution == null ? "" : solution.GetDefaultPlatformName();
public Solution(Autobuilder builder, string path)
public Solution(Autobuilder builder, string path) : base(builder, path)
{
Path = System.IO.Path.GetFullPath(path);
try
{
solution = SolutionFile.Parse(Path);
solution = SolutionFile.Parse(FullPath);
Projects =
includedProjects =
solution.ProjectsInOrder.
Where(p => p.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat).
Select(p => System.IO.Path.GetFullPath(FileUtils.ConvertToNative(p.AbsolutePath))).
Where(p => builder.Options.Language.ProjectFileHasThisLanguage(p)).
Select(p => builder.Actions.GetFullPath(FileUtils.ConvertToNative(p.AbsolutePath))).
Select(p => new Project(builder, p)).
ToArray();
}
@@ -90,17 +72,11 @@ namespace Semmle.Autobuild
{
// We allow specifying projects as solutions in lgtm.yml, so model
// that scenario as a solution with just that one project
Projects = Language.IsProjectFileForAnySupportedLanguage(Path)
? new[] { new Project(builder, Path) }
: new Project[0];
includedProjects = new[] { new Project(builder, path) };
}
}
public string Path { get; private set; }
public int ProjectCount => Projects.Count();
IEnumerable<Version> ToolsVersions => Projects.Where(p => p.ValidToolsVersion).Select(p => p.ToolsVersion);
IEnumerable<Version> ToolsVersions => includedProjects.Where(p => p.ValidToolsVersion).Select(p => p.ToolsVersion);
public Version ToolsVersion => ToolsVersions.Any() ? ToolsVersions.Max() : new Version();
}