C#: Move autobuilder into separate folder

This commit is contained in:
Tom Hvitved
2018-11-19 13:38:03 +01:00
parent 918fc90515
commit b95d7e5302
24 changed files with 13 additions and 13 deletions

View File

@@ -0,0 +1,885 @@
using Xunit;
using Semmle.Autobuild;
using System.Collections.Generic;
using System;
using System.Linq;
using Microsoft.Build.Construction;
namespace Semmle.Extraction.Tests
{
/// <summary>
/// Test class to script Autobuilder scenarios.
/// For most methods, it uses two fields:
/// - an IList to capture the the arguments passed to it
/// - an IDictionary of possible return values.
/// </summary>
class TestActions : IBuildActions
{
/// <summary>
/// List of strings passed to FileDelete.
/// </summary>
public IList<string> FileDeleteIn = new List<string>();
void IBuildActions.FileDelete(string file)
{
FileDeleteIn.Add(file);
}
public IList<string> FileExistsIn = new List<string>();
public IDictionary<string, bool> FileExists = new Dictionary<string, bool>();
bool IBuildActions.FileExists(string file)
{
FileExistsIn.Add(file);
if (FileExists.TryGetValue(file, out var ret))
return ret;
if (FileExists.TryGetValue(System.IO.Path.GetFileName(file), out ret))
return ret;
throw new ArgumentException("Missing FileExists " + file);
}
public IList<string> RunProcessIn = new List<string>();
public IDictionary<string, int> RunProcess = new Dictionary<string, int>();
public IDictionary<string, string> RunProcessOut = new Dictionary<string, string>();
public IDictionary<string, string> RunProcessWorkingDirectory = new Dictionary<string, string>();
int IBuildActions.RunProcess(string cmd, string args, string workingDirectory, IDictionary<string, string> env, out IList<string> stdOut)
{
var pattern = cmd + " " + args;
RunProcessIn.Add(pattern);
if (RunProcessOut.TryGetValue(pattern, out var str))
stdOut = str.Split("\n");
else
throw new ArgumentException("Missing RunProcessOut " + pattern);
RunProcessWorkingDirectory.TryGetValue(pattern, out var wd);
if (wd != workingDirectory)
throw new ArgumentException("Missing RunProcessWorkingDirectory " + pattern);
if (RunProcess.TryGetValue(pattern, out var ret))
return ret;
throw new ArgumentException("Missing RunProcess " + pattern);
}
int IBuildActions.RunProcess(string cmd, string args, string workingDirectory, IDictionary<string, string> env)
{
var pattern = cmd + " " + args;
RunProcessIn.Add(pattern);
RunProcessWorkingDirectory.TryGetValue(pattern, out var wd);
if (wd != workingDirectory)
throw new ArgumentException("Missing RunProcessWorkingDirectory " + pattern);
if (RunProcess.TryGetValue(pattern, out var ret))
return ret;
throw new ArgumentException("Missing RunProcess " + pattern);
}
public IList<string> DirectoryDeleteIn = new List<string>();
void IBuildActions.DirectoryDelete(string dir, bool recursive)
{
DirectoryDeleteIn.Add(dir);
}
public IDictionary<string, bool> DirectoryExists = new Dictionary<string, bool>();
public IList<string> DirectoryExistsIn = new List<string>();
bool IBuildActions.DirectoryExists(string dir)
{
DirectoryExistsIn.Add(dir);
if (DirectoryExists.TryGetValue(dir, out var ret))
return ret;
throw new ArgumentException("Missing DirectoryExists " + dir);
}
public IDictionary<string, string> GetEnvironmentVariable = new Dictionary<string, string>();
string IBuildActions.GetEnvironmentVariable(string name)
{
if (GetEnvironmentVariable.TryGetValue(name, out var ret))
return ret;
throw new ArgumentException("Missing GetEnvironmentVariable " + name);
}
public string GetCurrentDirectory;
string IBuildActions.GetCurrentDirectory()
{
return GetCurrentDirectory;
}
public IDictionary<string, string> EnumerateFiles = new Dictionary<string, string>();
IEnumerable<string> IBuildActions.EnumerateFiles(string dir)
{
if (EnumerateFiles.TryGetValue(dir, out var str))
return str.Split("\n");
throw new ArgumentException("Missing EnumerateFiles " + dir);
}
public IDictionary<string, string> EnumerateDirectories = new Dictionary<string, string>();
IEnumerable<string> IBuildActions.EnumerateDirectories(string dir)
{
if (EnumerateDirectories.TryGetValue(dir, out var str))
return string.IsNullOrEmpty(str) ? Enumerable.Empty<string>() : str.Split("\n");
throw new ArgumentException("Missing EnumerateDirectories " + dir);
}
public bool IsWindows;
bool IBuildActions.IsWindows() => IsWindows;
string IBuildActions.PathCombine(params string[] parts)
{
return string.Join('\\', parts);
}
void IBuildActions.WriteAllText(string filename, string contents)
{
}
}
/// <summary>
/// A fake solution to build.
/// </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 Version ToolsVersion => new Version("14.0");
public TestSolution(string path)
{
Path = path;
}
}
public class BuildScriptTests
{
TestActions Actions = new TestActions();
// Records the arguments passed to StartCallback.
IList<string> StartCallbackIn = new List<string>();
void StartCallback(string s)
{
StartCallbackIn.Add(s);
}
// Records the arguments passed to EndCallback
IList<string> EndCallbackIn = new List<string>();
IList<int> EndCallbackReturn = new List<int>();
void EndCallback(int ret, string s)
{
EndCallbackReturn.Add(ret);
EndCallbackIn.Add(s);
}
[Fact]
public void TestBuildCommand()
{
var cmd = BuildScript.Create("abc", "def ghi", null, null);
Actions.RunProcess["abc def ghi"] = 1;
cmd.Run(Actions, StartCallback, EndCallback);
Assert.Equal("abc def ghi", Actions.RunProcessIn[0]);
Assert.Equal("abc def ghi", StartCallbackIn[0]);
Assert.Equal("", EndCallbackIn[0]);
Assert.Equal(1, EndCallbackReturn[0]);
}
[Fact]
public void TestAnd1()
{
var cmd = BuildScript.Create("abc", "def ghi", null, null) & BuildScript.Create("odasa", null, null, null);
Actions.RunProcess["abc def ghi"] = 1;
cmd.Run(Actions, StartCallback, EndCallback);
Assert.Equal("abc def ghi", Actions.RunProcessIn[0]);
Assert.Equal("abc def ghi", StartCallbackIn[0]);
Assert.Equal("", EndCallbackIn[0]);
Assert.Equal(1, EndCallbackReturn[0]);
}
[Fact]
public void TestAnd2()
{
var cmd = BuildScript.Create("odasa", null, null, null) & BuildScript.Create("abc", "def ghi", null, null);
Actions.RunProcess["abc def ghi"] = 1;
Actions.RunProcess["odasa "] = 0;
cmd.Run(Actions, StartCallback, EndCallback);
Assert.Equal("odasa ", Actions.RunProcessIn[0]);
Assert.Equal("odasa ", StartCallbackIn[0]);
Assert.Equal("", EndCallbackIn[0]);
Assert.Equal(0, EndCallbackReturn[0]);
Assert.Equal("abc def ghi", Actions.RunProcessIn[1]);
Assert.Equal("abc def ghi", StartCallbackIn[1]);
Assert.Equal("", EndCallbackIn[1]);
Assert.Equal(1, EndCallbackReturn[1]);
}
[Fact]
public void TestOr1()
{
var cmd = BuildScript.Create("odasa", null, null, null) | BuildScript.Create("abc", "def ghi", null, null);
Actions.RunProcess["abc def ghi"] = 1;
Actions.RunProcess["odasa "] = 0;
cmd.Run(Actions, StartCallback, EndCallback);
Assert.Equal("odasa ", Actions.RunProcessIn[0]);
Assert.Equal("odasa ", StartCallbackIn[0]);
Assert.Equal("", EndCallbackIn[0]);
Assert.Equal(0, EndCallbackReturn[0]);
Assert.Equal(1, EndCallbackReturn.Count);
}
[Fact]
public void TestOr2()
{
var cmd = BuildScript.Create("abc", "def ghi", null, null) | BuildScript.Create("odasa", null, null, null);
Actions.RunProcess["abc def ghi"] = 1;
Actions.RunProcess["odasa "] = 0;
cmd.Run(Actions, StartCallback, EndCallback);
Assert.Equal("abc def ghi", Actions.RunProcessIn[0]);
Assert.Equal("abc def ghi", StartCallbackIn[0]);
Assert.Equal("", EndCallbackIn[0]);
Assert.Equal(1, EndCallbackReturn[0]);
Assert.Equal("odasa ", Actions.RunProcessIn[1]);
Assert.Equal("odasa ", StartCallbackIn[1]);
Assert.Equal("", EndCallbackIn[1]);
Assert.Equal(0, EndCallbackReturn[1]);
}
[Fact]
public void TestSuccess()
{
Assert.Equal(0, BuildScript.Success.Run(Actions, StartCallback, EndCallback));
}
[Fact]
public void TestFailure()
{
Assert.NotEqual(0, BuildScript.Failure.Run(Actions, StartCallback, EndCallback));
}
[Fact]
public void TestDeleteDirectorySuccess()
{
Actions.DirectoryExists["trap"] = true;
Assert.Equal(0, BuildScript.DeleteDirectory("trap").Run(Actions, StartCallback, EndCallback));
Assert.Equal("trap", Actions.DirectoryDeleteIn[0]);
}
[Fact]
public void TestDeleteDirectoryFailure()
{
Actions.DirectoryExists["trap"] = false;
Assert.NotEqual(0, BuildScript.DeleteDirectory("trap").Run(Actions, StartCallback, EndCallback));
}
[Fact]
public void TestDeleteFileSuccess()
{
Actions.FileExists["csharp.log"] = true;
Assert.Equal(0, BuildScript.DeleteFile("csharp.log").Run(Actions, StartCallback, EndCallback));
Assert.Equal("csharp.log", Actions.FileExistsIn[0]);
Assert.Equal("csharp.log", Actions.FileDeleteIn[0]);
}
[Fact]
public void TestDeleteFileFailure()
{
Actions.FileExists["csharp.log"] = false;
Assert.NotEqual(0, BuildScript.DeleteFile("csharp.log").Run(Actions, StartCallback, EndCallback));
Assert.Equal("csharp.log", Actions.FileExistsIn[0]);
}
[Fact]
public void TestTry()
{
Assert.Equal(0, BuildScript.Try(BuildScript.Failure).Run(Actions, StartCallback, EndCallback));
}
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")
{
Actions.GetEnvironmentVariable["SEMMLE_DIST"] = @"C:\odasa";
Actions.GetEnvironmentVariable["SEMMLE_JAVA_HOME"] = @"C:\odasa\tools\java";
Actions.GetEnvironmentVariable["LGTM_PROJECT_LANGUAGE"] = lgtmLanguage;
Actions.GetEnvironmentVariable["SEMMLE_PLATFORM_TOOLS"] = @"C:\odasa\tools";
Actions.GetEnvironmentVariable["LGTM_INDEX_VSTOOLS_VERSION"] = vsToolsVersion;
Actions.GetEnvironmentVariable["LGTM_INDEX_MSBUILD_ARGUMENTS"] = msBuildArguments;
Actions.GetEnvironmentVariable["LGTM_INDEX_MSBUILD_PLATFORM"] = msBuildPlatform;
Actions.GetEnvironmentVariable["LGTM_INDEX_MSBUILD_CONFIGURATION"] = msBuildConfiguration;
Actions.GetEnvironmentVariable["LGTM_INDEX_MSBUILD_TARGET"] = msBuildTarget;
Actions.GetEnvironmentVariable["LGTM_INDEX_DOTNET_ARGUMENTS"] = dotnetArguments;
Actions.GetEnvironmentVariable["LGTM_INDEX_DOTNET_VERSION"] = dotnetVersion;
Actions.GetEnvironmentVariable["LGTM_INDEX_BUILD_COMMAND"] = buildCommand;
Actions.GetEnvironmentVariable["LGTM_INDEX_SOLUTION"] = solution;
Actions.GetEnvironmentVariable["LGTM_INDEX_IGNORE_ERRORS"] = ignoreErrors;
Actions.GetEnvironmentVariable["LGTM_INDEX_BUILDLESS"] = buildless;
Actions.GetEnvironmentVariable["LGTM_INDEX_ALL_SOLUTIONS"] = allSolutions;
Actions.GetEnvironmentVariable["LGTM_INDEX_NUGET_RESTORE"] = nugetRestore;
Actions.GetEnvironmentVariable["ProgramFiles(x86)"] = isWindows ? @"C:\Program Files (x86)" : null;
Actions.GetCurrentDirectory = cwd;
Actions.IsWindows = isWindows;
var options = new AutobuildOptions();
options.ReadEnvironment(Actions);
return new Autobuilder(Actions, options);
}
[Fact]
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 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.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbar.cs";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", true);
TestAutobuilderScript(autobuilder, 0, 6);
}
[Fact]
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[@"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.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", false);
TestAutobuilderScript(autobuilder, 0, 6);
}
[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;
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", false);
TestAutobuilderScript(autobuilder, 1, 4);
}
[Fact]
public void TestDefaultCppAutobuilder()
{
Actions.EnumerateFiles[@"C:\Project"] = "";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("cpp", true);
var script = autobuilder.GetBuildScript();
// Fails due to no solutions present.
Assert.NotEqual(0, script.Run(Actions, StartCallback, EndCallback));
}
[Fact]
public void TestCppAutobuilderSuccess()
{
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\csharp\nuget\nuget.exe restore C:\Project\test.sln"] = 1;
Actions.RunProcess[@"cmd.exe /C CALL ^""C:\Program Files ^(x86^)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat^"" && C:\odasa\tools\odasa index --auto msbuild C:\Project\test.sln /p:UseSharedCompilation=false /t:rebuild /p:Platform=""x86"" /p:Configuration=""Release"" /p:MvcBuildViews=true"] = 0;
Actions.RunProcessOut[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationPath"] = "";
Actions.RunProcess[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationPath"] = 1;
Actions.RunProcess[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationVersion"] = 0;
Actions.RunProcessOut[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationVersion"] = "";
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"] = true;
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"] = true;
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"] = true;
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = true;
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.slx";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("cpp", true);
var solution = new TestSolution(@"C:\Project\test.sln");
autobuilder.SolutionsToBuild.Add(solution);
TestAutobuilderScript(autobuilder, 0, 2);
}
[Fact]
public void TestVsWhereSucceeded()
{
Actions.GetEnvironmentVariable["ProgramFiles(x86)"] = @"C:\Program Files (x86)";
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = true;
Actions.RunProcess[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationPath"] = 0;
Actions.RunProcessOut[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationPath"] = "C:\\VS1\nC:\\VS2";
Actions.RunProcessOut[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationVersion"] = "10.0\n11.0";
Actions.RunProcess[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationVersion"] = 0;
var candidates = BuildTools.GetCandidateVcVarsFiles(Actions).ToArray();
Assert.Equal("C:\\VS1\\VC\\vcvarsall.bat", candidates[0].Path);
Assert.Equal(10, candidates[0].ToolsVersion);
Assert.Equal("C:\\VS2\\VC\\vcvarsall.bat", candidates[1].Path);
Assert.Equal(11, candidates[1].ToolsVersion);
}
[Fact]
public void TestVsWhereNotExist()
{
Actions.GetEnvironmentVariable["ProgramFiles(x86)"] = @"C:\Program Files (x86)";
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = false;
var candidates = BuildTools.GetCandidateVcVarsFiles(Actions).ToArray();
Assert.Equal(4, candidates.Length);
}
[Fact]
public void TestVcVarsAllBatFiles()
{
Actions.GetEnvironmentVariable["ProgramFiles(x86)"] = @"C:\Program Files (x86)";
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"] = true;
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat"] = false;
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat"] = true;
Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"] = false;
var vcvarsfiles = BuildTools.VcVarsAllBatFiles(Actions).ToArray();
Assert.Equal(2, vcvarsfiles.Length);
}
[Fact]
public void TestLinuxBuildlessExtractionSuccess()
{
Actions.RunProcess[@"C:\odasa\tools\csharp\Semmle.Extraction.CSharp.Standalone --references:."] = 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.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", false, buildless:"true");
TestAutobuilderScript(autobuilder, 0, 3);
}
[Fact]
public void TestLinuxBuildlessExtractionFailed()
{
Actions.RunProcess[@"C:\odasa\tools\csharp\Semmle.Extraction.CSharp.Standalone --references:."] = 10;
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"] = 0;
Actions.FileExists["csharp.log"] = true;
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", false, buildless: "true");
TestAutobuilderScript(autobuilder, 10, 1);
}
[Fact]
public void TestLinuxBuildlessExtractionSolution()
{
Actions.RunProcess[@"C:\odasa\tools\csharp\Semmle.Extraction.CSharp.Standalone foo.sln --references:."] = 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.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", false, buildless: "true", solution: "foo.sln");
TestAutobuilderScript(autobuilder, 0, 3);
}
void SkipVsWhere()
{
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"] = false;
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"] = false;
}
void TestAutobuilderScript(Autobuilder autobuilder, int expectedOutput, int commandsRun)
{
Assert.Equal(expectedOutput, autobuilder.GetBuildScript().Run(Actions, StartCallback, EndCallback));
// Check expected commands actually ran
Assert.Equal(commandsRun, StartCallbackIn.Count);
Assert.Equal(commandsRun, EndCallbackIn.Count);
Assert.Equal(commandsRun, EndCallbackReturn.Count);
var action = Actions.RunProcess.GetEnumerator();
for(int cmd=0; cmd<commandsRun; ++cmd)
{
Assert.True(action.MoveNext());
Assert.Equal(action.Current.Key, StartCallbackIn[cmd]);
Assert.Equal(action.Current.Value, EndCallbackReturn[cmd]);
}
}
[Fact]
public void TestLinuxBuildCommand()
{
Actions.RunProcess["C:\\odasa\\tools\\odasa index --auto \"./build.sh --skip-tests\""] = 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.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
Actions.EnumerateDirectories[@"C:\Project"] = "";
SkipVsWhere();
var autobuilder = CreateAutoBuilder("csharp", false, buildCommand:"./build.sh --skip-tests");
TestAutobuilderScript(autobuilder, 0, 3);
}
[Fact]
public void TestLinuxBuildSh()
{
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbuild/build.sh";
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";
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;
var autobuilder = CreateAutoBuilder("csharp", false);
TestAutobuilderScript(autobuilder, 0, 5);
}
[Fact]
public void TestLinuxBuildShCSharpLogMissing()
{
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbuild.sh";
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.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);
}
[Fact]
public void TestLinuxBuildShFailed()
{
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbuild.sh";
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.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);
}
[Fact]
public void TestWindowsBuildBat()
{
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbuild.bat";
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;
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --xml --extensions config csproj props xml"] = 0;
Actions.FileExists["csharp.log"] = true;
var autobuilder = CreateAutoBuilder("csharp", true);
TestAutobuilderScript(autobuilder, 0, 4);
}
[Fact]
public void TestWindowsBuildBatIgnoreErrors()
{
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbuild.bat";
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);
}
[Fact]
public void TestWindowsCmdIgnoreErrors()
{
Actions.RunProcess["cmd.exe /C C:\\odasa\\tools\\odasa index --auto ^\"build.cmd --skip-tests^\""] = 3;
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;
SkipVsWhere();
Actions.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", true, buildCommand: "build.cmd --skip-tests", ignoreErrors: "true");
TestAutobuilderScript(autobuilder, 3, 1);
}
[Fact]
public void TestWindowCSharpMsBuild()
{
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\csharp\nuget\nuget.exe restore C:\Project\test1.sln"] = 0;
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 C:\\Project\\test1.sln /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\csharp\nuget\nuget.exe restore C:\Project\test2.sln"] = 0;
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 C:\\Project\\test2.sln /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[@"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"] = "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 testSolution1 = new TestSolution(@"C:\Project\test1.sln");
var testSolution2 = new TestSolution(@"C:\Project\test2.sln");
autobuilder.SolutionsToBuild.Add(testSolution1);
autobuilder.SolutionsToBuild.Add(testSolution2);
TestAutobuilderScript(autobuilder, 0, 6);
}
[Fact]
public void TestWindowCSharpMsBuildFailed()
{
Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\csharp\nuget\nuget.exe restore C:\Project\test1.sln"] = 0;
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 C:\\Project\\test1.sln /p:UseSharedCompilation=false /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /p:MvcBuildViews=true /P:Fu=Bar"] = 1;
Actions.FileExists["csharp.log"] = 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"] = "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 testSolution1 = new TestSolution(@"C:\Project\test1.sln");
var testSolution2 = new TestSolution(@"C:\Project\test2.sln");
autobuilder.SolutionsToBuild.Add(testSolution1);
autobuilder.SolutionsToBuild.Add(testSolution2);
TestAutobuilderScript(autobuilder, 1, 2);
}
[Fact]
public void TestSkipNugetMsBuild()
{
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 C:\\Project\\test1.sln /p:UseSharedCompilation=false /t:Windows /p:Platform=\"x86\" /p:Configuration=\"Debug\" /p:MvcBuildViews=true /P:Fu=Bar"] = 0;
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 C:\\Project\\test2.sln /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[@"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"] = "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", 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);
TestAutobuilderScript(autobuilder, 0, 4);
}
[Fact]
public void TestSkipNugetBuildless()
{
Actions.RunProcess[@"C:\odasa\tools\csharp\Semmle.Extraction.CSharp.Standalone foo.sln --references:. --skip-nuget"] = 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.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", false, buildless: "true", solution: "foo.sln", nugetRestore:"false");
TestAutobuilderScript(autobuilder, 0, 3);
}
[Fact]
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[@"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.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", false, dotnetArguments:"--no-restore"); // nugetRestore=false does not work for now.
TestAutobuilderScript(autobuilder, 0, 6);
}
[Fact]
public void TestDotnetVersionNotInstalled()
{
Actions.RunProcess["dotnet --list-sdks"] = 0;
Actions.RunProcessOut["dotnet --list-sdks"] = "2.1.2 [C:\\Program Files\\dotnet\\sdks]\n2.1.4 [C:\\Program Files\\dotnet\\sdks]";
Actions.RunProcess[@"curl -sO https://dot.net/v1/dotnet-install.sh"] = 0;
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:\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.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.GetEnvironmentVariable["PATH"] = "/bin:/usr/bin";
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", false, dotnetVersion:"2.1.3");
TestAutobuilderScript(autobuilder, 0, 10);
}
[Fact]
public void TestDotnetVersionAlreadyInstalled()
{
Actions.RunProcess["dotnet --list-sdks"] = 0;
Actions.RunProcessOut["dotnet --list-sdks"] = "2.1.3 [C:\\Program Files\\dotnet\\sdks]\n2.1.4 [C:\\Program Files\\dotnet\\sdks]";
Actions.RunProcess[@"curl -sO https://dot.net/v1/dotnet-install.sh"] = 0;
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:\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.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.GetEnvironmentVariable["PATH"] = "/bin:/usr/bin";
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbar.cs";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", false, dotnetVersion: "2.1.3");
TestAutobuilderScript(autobuilder, 0, 10);
}
[Fact]
public void TestDotnetVersionWindows()
{
Actions.RunProcess["cmd.exe /C dotnet --list-sdks"] = 0;
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:\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.GetEnvironmentVariable["TRAP_FOLDER"] = null;
Actions.GetEnvironmentVariable["SOURCE_ARCHIVE"] = null;
Actions.GetEnvironmentVariable["PATH"] = "/bin:/usr/bin";
Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs";
Actions.EnumerateDirectories[@"C:\Project"] = "";
var autobuilder = CreateAutoBuilder("csharp", true, dotnetVersion: "2.1.3");
TestAutobuilderScript(autobuilder, 0, 8);
}
}
}

View File

@@ -0,0 +1,32 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Semmle.Autobuild.Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Semmle.Extraction.Tests")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Semmle.Autobuild\Semmle.Autobuild.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,20 @@
using System.IO;
namespace Semmle.Autobuild
{
/// <summary>
/// ASP extraction.
/// </summary>
class AspBuildRule : IBuildRule
{
public BuildScript Analyse(Autobuilder builder)
{
var command = new CommandBuilder(builder.Actions).
RunCommand(builder.Actions.PathCombine(builder.SemmleJavaHome, "bin", "java")).
Argument("-jar").
QuoteArgument(builder.Actions.PathCombine(builder.SemmleDist, "tools", "extractor-asp.jar")).
Argument(".");
return command.Script;
}
}
}

View File

@@ -0,0 +1,103 @@
using System;
using System.Linq;
namespace Semmle.Autobuild
{
/// <summary>
/// Encapsulates build options.
/// </summary>
public class AutobuildOptions
{
public readonly int SearchDepth = 3;
public string RootDirectory = null;
static readonly string prefix = "LGTM_INDEX_";
public string VsToolsVersion;
public string MsBuildArguments;
public string MsBuildPlatform;
public string MsBuildConfiguration;
public string MsBuildTarget;
public string DotNetArguments;
public string DotNetVersion;
public string BuildCommand;
public string[] Solution;
public bool IgnoreErrors;
public bool Buildless;
public bool AllSolutions;
public bool NugetRestore;
public Language Language;
/// <summary>
/// Reads options from environment variables.
/// Throws ArgumentOutOfRangeException for invalid arguments.
/// </summary>
public void ReadEnvironment(IBuildActions actions)
{
RootDirectory = actions.GetCurrentDirectory();
VsToolsVersion = actions.GetEnvironmentVariable(prefix + "VSTOOLS_VERSION");
MsBuildArguments = actions.GetEnvironmentVariable(prefix + "MSBUILD_ARGUMENTS");
MsBuildPlatform = actions.GetEnvironmentVariable(prefix + "MSBUILD_PLATFORM");
MsBuildConfiguration = actions.GetEnvironmentVariable(prefix + "MSBUILD_CONFIGURATION");
MsBuildTarget = actions.GetEnvironmentVariable(prefix + "MSBUILD_TARGET");
DotNetArguments = actions.GetEnvironmentVariable(prefix + "DOTNET_ARGUMENTS");
DotNetVersion = actions.GetEnvironmentVariable(prefix + "DOTNET_VERSION");
BuildCommand = actions.GetEnvironmentVariable(prefix + "BUILD_COMMAND");
Solution = actions.GetEnvironmentVariable(prefix + "SOLUTION").AsList(new string[0]);
IgnoreErrors = actions.GetEnvironmentVariable(prefix + "IGNORE_ERRORS").AsBool("ignore_errors", false);
Buildless = actions.GetEnvironmentVariable(prefix + "BUILDLESS").AsBool("buildless", false);
AllSolutions = actions.GetEnvironmentVariable(prefix + "ALL_SOLUTIONS").AsBool("all_solutions", false);
NugetRestore = actions.GetEnvironmentVariable(prefix + "NUGET_RESTORE").AsBool("nuget_restore", true);
Language = actions.GetEnvironmentVariable("LGTM_PROJECT_LANGUAGE").AsLanguage();
}
}
public static class OptionsExtensions
{
public static bool AsBool(this string value, string param, bool defaultValue)
{
if (value == null) return defaultValue;
switch (value.ToLower())
{
case "on":
case "yes":
case "true":
case "enabled":
return true;
case "off":
case "no":
case "false":
case "disabled":
return false;
default:
throw new ArgumentOutOfRangeException(param, value, "The Boolean value is invalid.");
}
}
public static Language AsLanguage(this string key)
{
switch (key)
{
case null:
throw new ArgumentException("Environment variable required: LGTM_PROJECT_LANGUAGE");
case "csharp":
return Language.CSharp;
case "cpp":
return Language.Cpp;
default:
throw new ArgumentException("Language key not understood: '" + key + "'");
}
}
public static string[] AsList(this string value, string[] defaultValue)
{
if (value == null)
return defaultValue;
return value.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).ToArray();
}
}
}

View File

@@ -0,0 +1,352 @@
using Semmle.Extraction.CSharp;
using Semmle.Util.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Semmle.Autobuild
{
/// <summary>
/// A build rule analyses the files in "builder" and outputs a build script.
/// </summary>
interface IBuildRule
{
/// <summary>
/// Analyse the files and produce a build script.
/// </summary>
/// <param name="builder">The files and options relating to the build.</param>
BuildScript Analyse(Autobuilder builder);
}
/// <summary>
/// Main application logic, containing all data
/// gathered from the project and filesystem.
///
/// The overall design is intended to be extensible so that in theory,
/// it should be possible to add new build rules without touching this code.
/// </summary>
public class Autobuilder
{
/// <summary>
/// Full file paths of files found in the project directory.
/// </summary>
public IEnumerable<string> Paths => pathsLazy.Value;
readonly Lazy<IEnumerable<string>> pathsLazy;
/// <summary>
/// Gets a list of paths matching a set of extensions
/// (including the ".").
/// </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)));
/// <summary>
/// Gets all paths matching a particular filename.
/// </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);
/// <summary>
/// Holds if a given path, relative to the root of the source directory
/// was found.
/// </summary>
/// <param name="path">The relative path.</param>
/// <returns>True iff the path was found.</returns>
public bool HasRelativePath(string path) => HasPath(Actions.PathCombine(RootDirectory, path));
/// <summary>
/// List of solution files to build.
/// </summary>
public IList<ISolution> SolutionsToBuild => solutionsToBuildLazy.Value;
readonly Lazy<IList<ISolution>> solutionsToBuildLazy;
/// <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);
void FindFiles(string dir, int depth, IList<string> results)
{
foreach (var f in Actions.EnumerateFiles(dir))
{
results.Add(f);
}
if (depth > 1)
{
foreach (var d in Actions.EnumerateDirectories(dir))
{
FindFiles(d, depth - 1, results);
}
}
}
/// <summary>
/// The root of the source directory.
/// </summary>
string RootDirectory => Options.RootDirectory;
/// <summary>
/// Gets the supplied build configuration.
/// </summary>
public AutobuildOptions Options { get; }
/// <summary>
/// The set of build actions used during the autobuilder.
/// Could be real system operations, or a stub for testing.
/// </summary>
public IBuildActions Actions { get; }
/// <summary>
/// Find all the relevant files and picks the best
/// solution file and tools.
/// </summary>
/// <param name="options">The command line options.</param>
public Autobuilder(IBuildActions actions, AutobuildOptions options)
{
Actions = actions;
Options = options;
pathsLazy = new Lazy<IEnumerable<string>>(() =>
{
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();
});
solutionsToBuildLazy = new Lazy<IList<ISolution>>(() =>
{
if (options.Solution.Any())
{
var ret = new List<ISolution>();
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);
}
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).
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))}");
}
return new List<ISolution>(options.AllSolutions ?
solutions :
solutions.Take(1));
});
SemmleDist = Actions.GetEnvironmentVariable("SEMMLE_DIST");
SemmleJavaHome = Actions.GetEnvironmentVariable("SEMMLE_JAVA_HOME");
SemmlePlatformTools = Actions.GetEnvironmentVariable("SEMMLE_PLATFORM_TOOLS");
if (SemmleDist == null)
Log(Severity.Error, "The environment variable SEMMLE_DIST has not been set.");
}
readonly ILogger logger = new ConsoleLogger(Verbosity.Info);
/// <summary>
/// Log a given build event to the console.
/// </summary>
/// <param name="format">The format string.</param>
/// <param name="args">Inserts to the format string.</param>
public void Log(Severity severity, string format, params object[] args)
{
logger.Log(severity, format, args);
}
/// <summary>
/// Attempt to build this project.
/// </summary>
/// <returns>The exit code, 0 for success and non-zero for failures.</returns>
public int AttemptBuild()
{
Log(Severity.Info, $"Working directory: {Options.RootDirectory}");
var script = GetBuildScript();
if (Options.IgnoreErrors)
script |= BuildScript.Success;
void startCallback(string s) => Log(Severity.Info, $"\nRunning {s}");
void exitCallback(int ret, string msg) => Log(Severity.Info, $"Exit code {ret}{(string.IsNullOrEmpty(msg) ? "" : $": {msg}")}");
return script.Run(Actions, startCallback, exitCallback);
}
/// <summary>
/// Returns the build script to use for this project.
/// </summary>
public BuildScript GetBuildScript()
{
var isCSharp = Options.Language == Language.CSharp;
return isCSharp ? GetCSharpBuildScript() : GetCppBuildScript();
}
BuildScript GetCSharpBuildScript()
{
/// <summary>
/// A script that checks that the C# extractor has been executed.
/// </summary>
BuildScript CheckExtractorRun(bool warnOnFailure) =>
BuildScript.Create(actions =>
{
if (actions.FileExists(Extractor.GetCSharpLogPath()))
return 0;
if (warnOnFailure)
Log(Severity.Error, "No C# code detected during build.");
return 1;
});
var attempt = BuildScript.Failure;
switch (GetCSharpBuildStrategy())
{
case CSharpBuildStrategy.CustomBuildCommand:
attempt = new BuildCommandRule().Analyse(this) & CheckExtractorRun(true);
break;
case CSharpBuildStrategy.Buildless:
// No need to check that the extractor has been executed in buildless mode
attempt = new StandaloneBuildRule().Analyse(this);
break;
case CSharpBuildStrategy.MSBuild:
attempt = new MsBuildRule().Analyse(this) & CheckExtractorRun(true);
break;
case CSharpBuildStrategy.DotNet:
attempt = new DotNetRule().Analyse(this) & CheckExtractorRun(true);
break;
case CSharpBuildStrategy.Auto:
var cleanTrapFolder =
BuildScript.DeleteDirectory(Actions.GetEnvironmentVariable("TRAP_FOLDER"));
var cleanSourceArchive =
BuildScript.DeleteDirectory(Actions.GetEnvironmentVariable("SOURCE_ARCHIVE"));
var cleanExtractorLog =
BuildScript.DeleteFile(Extractor.GetCSharpLogPath());
var attemptExtractorCleanup =
BuildScript.Try(cleanTrapFolder) &
BuildScript.Try(cleanSourceArchive) &
BuildScript.Try(cleanExtractorLog);
/// <summary>
/// Execute script `s` and check that the C# extractor has been executed.
/// If either fails, attempt to cleanup any artifacts produced by the extractor,
/// and exit with code 1, in order to proceed to the next attempt.
/// </summary>
BuildScript IntermediateAttempt(BuildScript s) =>
(s & CheckExtractorRun(false)) |
(attemptExtractorCleanup & BuildScript.Failure);
attempt =
// First try .NET Core
IntermediateAttempt(new DotNetRule().Analyse(this)) |
// Then MSBuild
(() => IntermediateAttempt(new MsBuildRule().Analyse(this))) |
// And finally look for a script that might be a build script
(() => new BuildCommandAutoRule().Analyse(this) & CheckExtractorRun(true)) |
// All attempts failed: print message
AutobuildFailure();
break;
}
return
attempt &
(() => new AspBuildRule().Analyse(this)) &
(() => new XmlBuildRule().Analyse(this));
}
/// <summary>
/// Gets the build strategy that the autobuilder should apply, based on the
/// options in the `lgtm.yml` file.
/// </summary>
CSharpBuildStrategy GetCSharpBuildStrategy()
{
if (Options.BuildCommand != null)
return CSharpBuildStrategy.CustomBuildCommand;
if (Options.Buildless)
return CSharpBuildStrategy.Buildless;
if (Options.MsBuildArguments != null
|| Options.MsBuildConfiguration != null
|| Options.MsBuildPlatform != null
|| Options.MsBuildTarget != null)
return CSharpBuildStrategy.MSBuild;
if (Options.DotNetArguments != null || Options.DotNetVersion != null)
return CSharpBuildStrategy.DotNet;
return CSharpBuildStrategy.Auto;
}
enum CSharpBuildStrategy
{
CustomBuildCommand,
Buildless,
MSBuild,
DotNet,
Auto
}
BuildScript GetCppBuildScript()
{
if (Options.BuildCommand != null)
return new BuildCommandRule().Analyse(this);
return
// First try MSBuild
new MsBuildRule().Analyse(this) |
// Then look for a script that might be a build script
(() => new BuildCommandAutoRule().Analyse(this)) |
// All attempts failed: print message
AutobuildFailure();
}
BuildScript AutobuildFailure() =>
BuildScript.Create(actions =>
{
Log(Severity.Error, "Could not auto-detect a suitable build method");
return 1;
});
/// <summary>
/// Value of SEMMLE_DIST environment variable.
/// </summary>
public string SemmleDist { get; private set; }
/// <summary>
/// Value of SEMMLE_JAVA_HOME environment variable.
/// </summary>
public string SemmleJavaHome { get; private set; }
/// <summary>
/// Value of SEMMLE_PLATFORM_TOOLS environment variable.
/// </summary>
public string SemmlePlatformTools { get; private set; }
/// <summary>
/// The absolute path of the odasa executable.
/// </summary>
public string Odasa => SemmleDist == null ? null : Actions.PathCombine(SemmleDist, "tools", "odasa");
}
}

View File

@@ -0,0 +1,176 @@
using Semmle.Util;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
namespace Semmle.Autobuild
{
/// <summary>
/// Wrapper around system calls so that the build scripts can be unit-tested.
/// </summary>
public interface IBuildActions
{
/// <summary>
/// Runs a process and captures its output.
/// </summary>
/// <param name="exe">The exe to run.</param>
/// <param name="args">The other command line arguments.</param>
/// <param name="workingDirectory">The working directory (<code>null</code> for current directory).</param>
/// <param name="env">Additional environment variables.</param>
/// <param name="stdOut">The lines of stdout.</param>
/// <returns>The process exit code.</returns>
int RunProcess(string exe, string args, string workingDirectory, IDictionary<string, string> env, out IList<string> stdOut);
/// <summary>
/// Runs a process but does not capture its output.
/// </summary>
/// <param name="exe">The exe to run.</param>
/// <param name="args">The other command line arguments.</param>
/// <param name="workingDirectory">The working directory (<code>null</code> for current directory).</param>
/// <param name="env">Additional environment variables.</param>
/// <returns>The process exit code.</returns>
int RunProcess(string exe, string args, string workingDirectory, IDictionary<string, string> env);
/// <summary>
/// Tests whether a file exists, File.Exists().
/// </summary>
/// <param name="file">The filename.</param>
/// <returns>True iff the file exists.</returns>
bool FileExists(string file);
/// <summary>
/// Tests whether a directory exists, Directory.Exists().
/// </summary>
/// <param name="dir">The directory name.</param>
/// <returns>True iff the directory exists.</returns>
bool DirectoryExists(string dir);
/// <summary>
/// Deletes a file, File.Delete().
/// </summary>
/// <param name="file">The filename.</param>
void FileDelete(string file);
/// <summary>
/// Deletes a directory, Directory.Delete().
/// </summary>
void DirectoryDelete(string dir, bool recursive);
/// <summary>
/// Gets an environment variable, Environment.GetEnvironmentVariable().
/// </summary>
/// <param name="name">The name of the variable.</param>
/// <returns>The string value, or null if the variable is not defined.</returns>
string GetEnvironmentVariable(string name);
/// <summary>
/// Gets the current directory, Directory.GetCurrentDirectory().
/// </summary>
/// <returns>The current directory.</returns>
string GetCurrentDirectory();
/// <summary>
/// Enumerates files in a directory, Directory.EnumerateFiles().
/// </summary>
/// <param name="dir">The directory to enumerate.</param>
/// <returns>A list of filenames, or an empty list.</returns>
IEnumerable<string> EnumerateFiles(string dir);
/// <summary>
/// Enumerates the directories in a directory, Directory.EnumerateDirectories().
/// </summary>
/// <param name="dir">The directory to enumerate.</param>
/// <returns>List of subdirectories, or empty list.</returns>
IEnumerable<string> EnumerateDirectories(string dir);
/// <summary>
/// True if we are running on Windows.
/// </summary>
bool IsWindows();
/// <summary>
/// Combine path segments, Path.Combine().
/// </summary>
/// <param name="parts">The parts of the path.</param>
/// <returns>The combined path.</returns>
string PathCombine(params string[] parts);
/// <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>
/// An implementation of IBuildActions that actually performs the requested operations.
/// </summary>
class SystemBuildActions : IBuildActions
{
void IBuildActions.FileDelete(string file) => File.Delete(file);
bool IBuildActions.FileExists(string file) => File.Exists(file);
ProcessStartInfo GetProcessStartInfo(string exe, string arguments, string workingDirectory, IDictionary<string, string> environment, bool redirectStandardOutput)
{
var pi = new ProcessStartInfo(exe, arguments)
{
UseShellExecute = false,
RedirectStandardOutput = redirectStandardOutput
};
if (workingDirectory != null)
pi.WorkingDirectory = workingDirectory;
// Environment variables can only be used when not redirecting stdout
if (!redirectStandardOutput)
{
pi.Environment["UseSharedCompilation"] = "false";
if (environment != null)
environment.ForEach(kvp => pi.Environment[kvp.Key] = kvp.Value);
}
return pi;
}
int IBuildActions.RunProcess(string cmd, string args, string workingDirectory, IDictionary<string, string> environment)
{
var pi = GetProcessStartInfo(cmd, args, workingDirectory, environment, false);
using (var p = Process.Start(pi))
{
p.WaitForExit();
return p.ExitCode;
}
}
int IBuildActions.RunProcess(string cmd, string args, string workingDirectory, IDictionary<string, string> environment, out IList<string> stdOut)
{
var pi = GetProcessStartInfo(cmd, args, workingDirectory, environment, true);
return pi.ReadOutput(out stdOut);
}
void IBuildActions.DirectoryDelete(string dir, bool recursive) => Directory.Delete(dir, recursive);
bool IBuildActions.DirectoryExists(string dir) => Directory.Exists(dir);
string IBuildActions.GetEnvironmentVariable(string name) => Environment.GetEnvironmentVariable(name);
string IBuildActions.GetCurrentDirectory() => Directory.GetCurrentDirectory();
IEnumerable<string> IBuildActions.EnumerateFiles(string dir) => Directory.EnumerateFiles(dir);
IEnumerable<string> IBuildActions.EnumerateDirectories(string dir) => Directory.EnumerateDirectories(dir);
bool IBuildActions.IsWindows() => Win32.IsWindows();
string IBuildActions.PathCombine(params string[] parts) => Path.Combine(parts);
void IBuildActions.WriteAllText(string filename, string contents) => File.WriteAllText(filename, contents);
private SystemBuildActions()
{
}
public static readonly IBuildActions Instance = new SystemBuildActions();
}
}

View File

@@ -0,0 +1,61 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Semmle.Util;
using Semmle.Util.Logging;
namespace Semmle.Autobuild
{
/// <summary>
/// Auto-detection of build scripts.
/// </summary>
class BuildCommandAutoRule : IBuildRule
{
readonly IEnumerable<string> winExtensions = new List<string> {
".bat",
".cmd",
".exe"
};
readonly IEnumerable<string> linuxExtensions = new List<string> {
"",
".sh"
};
readonly IEnumerable<string> buildScripts = new List<string> {
"build"
};
public BuildScript Analyse(Autobuilder builder)
{
builder.Log(Severity.Info, "Attempting to locate build script");
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();
if (scriptPath == null)
return BuildScript.Failure;
var chmod = new CommandBuilder(builder.Actions);
chmod.RunCommand("/bin/chmod", $"u+x {scriptPath}");
var chmodScript = builder.Actions.IsWindows() ? BuildScript.Success : BuildScript.Try(chmod.Script);
var path = Path.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);
// A specific Visual Studio version may be required
var vsTools = MsBuildRule.GetVcVarsBatFile(builder);
if (vsTools != null)
command.CallBatFile(vsTools.Path);
command.IndexCommand(builder.Odasa, scriptPath);
return command.Script;
});
}
}
}

View File

@@ -0,0 +1,28 @@
namespace Semmle.Autobuild
{
/// <summary>
/// Execute the build_command rule.
/// </summary>
class BuildCommandRule : IBuildRule
{
public BuildScript Analyse(Autobuilder builder)
{
if (builder.Options.BuildCommand == null)
return BuildScript.Failure;
// Custom build commands may require a specific .NET Core version
return DotNetRule.WithDotNet(builder, dotNet =>
{
var command = new CommandBuilder(builder.Actions, null, dotNet?.Environment);
// Custom build commands may require a specific Visual Studio version
var vsTools = MsBuildRule.GetVcVarsBatFile(builder);
if (vsTools != null)
command.CallBatFile(vsTools.Path);
command.IndexCommand(builder.Odasa, builder.Options.BuildCommand);
return command.Script;
});
}
}
}

View File

@@ -0,0 +1,274 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using Semmle.Util;
namespace Semmle.Autobuild
{
/// <summary>
/// A build script.
/// </summary>
public abstract class BuildScript
{
/// <summary>
/// Run this build script.
/// </summary>
/// <param name="actions">
/// The interface used to implement the build actions.
/// </param>
/// <param name="startCallback">
/// A call back that is called every time a new process is started. The
/// argument to the call back is a textual representation of the process.
/// </param>
/// <param name="exitCallBack">
/// A call back that is called every time a new process exits. The first
/// argument to the call back is the exit code, and the second argument is
/// an exit message.
/// </param>
/// <returns>The exit code from this build script.</returns>
public abstract int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack);
/// <summary>
/// Run this build command.
/// </summary>
/// <param name="actions">
/// The interface used to implement the build actions.
/// </param>
/// <param name="startCallback">
/// A call back that is called every time a new process is started. The
/// argument to the call back is a textual representation of the process.
/// </param>
/// <param name="exitCallBack">
/// A call back that is called every time a new process exits. The first
/// argument to the call back is the exit code, and the second argument is
/// an exit message.
/// </param>
/// <param name="stdout">Contents of standard out.</param>
/// <returns>The exit code from this build script.</returns>
public abstract int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack, out IList<string> stdout);
class BuildCommand : BuildScript
{
readonly string exe, arguments, workingDirectory;
readonly IDictionary<string, string> environment;
/// <summary>
/// Create a simple build command.
/// </summary>
/// <param name="exe">The executable to run.</param>
/// <param name="argumentsOpt">The arguments to the executable, or null.</param>
/// <param name="workingDirectory">The working directory (<code>null</code> for current directory).</param>
/// <param name="environment">Additional environment variables.</param>
public BuildCommand(string exe, string argumentsOpt, string workingDirectory = null, IDictionary<string, string> environment = null)
{
this.exe = exe;
this.arguments = argumentsOpt ?? "";
this.workingDirectory = workingDirectory;
this.environment = environment;
}
public override string ToString() => exe + " " + arguments;
public override int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack)
{
startCallback(this.ToString());
var ret = 1;
var retMessage = "";
try
{
ret = actions.RunProcess(exe, arguments, workingDirectory, environment);
}
catch (Exception ex)
when (ex is System.ComponentModel.Win32Exception || ex is FileNotFoundException)
{
retMessage = ex.Message;
}
exitCallBack(ret, retMessage);
return ret;
}
public override int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack, out IList<string> stdout)
{
startCallback(this.ToString());
var ret = 1;
var retMessage = "";
try
{
ret = actions.RunProcess(exe, arguments, workingDirectory, environment, out stdout);
}
catch (Exception ex)
when (ex is System.ComponentModel.Win32Exception || ex is FileNotFoundException)
{
retMessage = ex.Message;
stdout = new string[0];
}
exitCallBack(ret, retMessage);
return ret;
}
}
class ReturnBuildCommand : BuildScript
{
readonly Func<IBuildActions, int> func;
public ReturnBuildCommand(Func<IBuildActions, int> func)
{
this.func = func;
}
public override int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack) => func(actions);
public override int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack, out IList<string> stdout)
{
stdout = new string[0];
return func(actions);
}
}
class BindBuildScript : BuildScript
{
readonly BuildScript s1;
readonly Func<IList<string>, int, BuildScript> s2a;
readonly Func<int, BuildScript> s2b;
public BindBuildScript(BuildScript s1, Func<IList<string>, int, BuildScript> s2)
{
this.s1 = s1;
this.s2a = s2;
}
public BindBuildScript(BuildScript s1, Func<int, BuildScript> s2)
{
this.s1 = s1;
this.s2b = s2;
}
public override int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack)
{
int ret1;
if (s2a != null)
{
ret1 = s1.Run(actions, startCallback, exitCallBack, out var stdout1);
return s2a(stdout1, ret1).Run(actions, startCallback, exitCallBack);
}
ret1 = s1.Run(actions, startCallback, exitCallBack);
return s2b(ret1).Run(actions, startCallback, exitCallBack);
}
public override int Run(IBuildActions actions, Action<string> startCallback, Action<int, string> exitCallBack, out IList<string> stdout)
{
var ret1 = s1.Run(actions, startCallback, exitCallBack, out var stdout1);
var ret2 = (s2a != null ? s2a(stdout1, ret1) : s2b(ret1)).Run(actions, startCallback, exitCallBack, out var stdout2);
var @out = new List<string>();
@out.AddRange(stdout1);
@out.AddRange(stdout2);
stdout = @out;
return ret2;
}
}
/// <summary>
/// Creates a simple build script that runs the specified exe.
/// </summary>
/// <param name="argumentsOpt">The arguments to the executable, or null.</param>
/// <param name="workingDirectory">The working directory (<code>null</code> for current directory).</param>
/// <param name="environment">Additional environment variables.</param>
public static BuildScript Create(string exe, string argumentsOpt, string workingDirectory, IDictionary<string, string> environment) =>
new BuildCommand(exe, argumentsOpt, workingDirectory, environment);
/// <summary>
/// Creates a simple build script that runs the specified function.
/// </summary>
public static BuildScript Create(Func<IBuildActions, int> func) =>
new ReturnBuildCommand(func);
/// <summary>
/// Creates a build script that runs <paramref name="s1"/>, followed by running the script
/// produced by <paramref name="s2"/> on the exit code from <paramref name="s1"/>.
/// </summary>
public static BuildScript Bind(BuildScript s1, Func<int, BuildScript> s2) =>
new BindBuildScript(s1, s2);
/// <summary>
/// Creates a build script that runs <paramref name="s1"/>, followed by running the script
/// produced by <paramref name="s2"/> on the exit code and standard output from
/// <paramref name="s1"/>.
/// </summary>
public static BuildScript Bind(BuildScript s1, Func<IList<string>, int, BuildScript> s2) =>
new BindBuildScript(s1, s2);
const int SuccessCode = 0;
/// <summary>
/// The empty build script that always returns exit code 0.
/// </summary>
public static readonly BuildScript Success = Create(actions => SuccessCode);
const int FailureCode = 1;
/// <summary>
/// The empty build script that always returns exit code 1.
/// </summary>
public static readonly BuildScript Failure = Create(actions => FailureCode);
static bool Succeeded(int i) => i == SuccessCode;
public static BuildScript operator &(BuildScript s1, BuildScript s2) =>
new BindBuildScript(s1, ret1 => Succeeded(ret1) ? s2 : Create(actions => ret1));
public static BuildScript operator &(BuildScript s1, Func<BuildScript> s2) =>
new BindBuildScript(s1, ret1 => Succeeded(ret1) ? s2() : Create(actions => ret1));
public static BuildScript operator |(BuildScript s1, BuildScript s2) =>
new BindBuildScript(s1, ret1 => Succeeded(ret1) ? Success : s2);
public static BuildScript operator |(BuildScript s1, Func<BuildScript> s2) =>
new BindBuildScript(s1, ret1 => Succeeded(ret1) ? Success : s2());
/// <summary>
/// Creates a build script that attempts to run the build script <paramref name="s"/>,
/// always returning a successful exit code.
/// </summary>
public static BuildScript Try(BuildScript s) => s | Success;
/// <summary>
/// Creates a build script that deletes the given directory.
/// </summary>
public static BuildScript DeleteDirectory(string dir) =>
Create(actions =>
{
if (string.IsNullOrEmpty(dir) || !actions.DirectoryExists(dir))
return FailureCode;
try
{
actions.DirectoryDelete(dir, true);
}
catch
{
return FailureCode;
}
return SuccessCode;
});
/// <summary>
/// Creates a build script that deletes the given file.
/// </summary>
public static BuildScript DeleteFile(string file) =>
Create(actions =>
{
if (string.IsNullOrEmpty(file) || !actions.FileExists(file))
return FailureCode;
try
{
actions.FileDelete(file);
}
catch
{
return FailureCode;
}
return SuccessCode;
});
}
}

View File

@@ -0,0 +1,99 @@
using System.Collections.Generic;
using System.Linq;
namespace Semmle.Autobuild
{
/// <summary>
/// A BAT file used to initialise the appropriate
/// Visual Studio version/platform.
/// </summary>
public class VcVarsBatFile
{
public readonly int ToolsVersion;
public readonly string Path;
public readonly string[] Platform;
public VcVarsBatFile(string path, int version, params string[] platform)
{
Path = path;
ToolsVersion = version;
Platform = platform;
}
};
/// <summary>
/// Collection of available Visual Studio build tools.
/// </summary>
public static class BuildTools
{
public static IEnumerable<VcVarsBatFile> GetCandidateVcVarsFiles(IBuildActions actions)
{
var programFilesx86 = actions.GetEnvironmentVariable("ProgramFiles(x86)");
if (programFilesx86 == null)
yield break;
// Attempt to use vswhere to find installations of Visual Studio
string vswhere = actions.PathCombine(programFilesx86, "Microsoft Visual Studio", "Installer", "vswhere.exe");
if (actions.FileExists(vswhere))
{
int exitCode1 = actions.RunProcess(vswhere, "-prerelease -legacy -property installationPath", null, null, out var installationList);
int exitCode2 = actions.RunProcess(vswhere, "-prerelease -legacy -property installationVersion", null, null, out var versionList);
if (exitCode1 == 0 && exitCode2 == 0 && versionList.Count == installationList.Count)
{
// vswhere ran successfully and produced the expected output
foreach (var vsInstallation in versionList.Zip(installationList, (v, i) => (Version: v, InstallationPath: i)))
{
var dot = vsInstallation.Version.IndexOf('.');
var majorVersionString = dot == -1 ? vsInstallation.Version : vsInstallation.Version.Substring(0, dot);
if (int.TryParse(majorVersionString, out int majorVersion))
{
if (majorVersion < 15)
{
yield return new VcVarsBatFile(actions.PathCombine(vsInstallation.InstallationPath, @"VC\vcvarsall.bat"), majorVersion, "x86");
}
else
{
yield return new VcVarsBatFile(actions.PathCombine(vsInstallation.InstallationPath, @"VC\Auxiliary\Build\vcvars32.bat"), majorVersion, "x86");
yield return new VcVarsBatFile(actions.PathCombine(vsInstallation.InstallationPath, @"VC\Auxiliary\Build\vcvars64.bat"), majorVersion, "x64");
}
}
// else: Skip installation without a version
}
yield break;
}
}
// vswhere not installed or didn't run correctly - return legacy Visual Studio versions
yield return new VcVarsBatFile(actions.PathCombine(programFilesx86, @"Microsoft Visual Studio 14.0\VC\vcvarsall.bat"), 14, "x86");
yield return new VcVarsBatFile(actions.PathCombine(programFilesx86, @"Microsoft Visual Studio 12.0\VC\vcvarsall.bat"), 12, "x86");
yield return new VcVarsBatFile(actions.PathCombine(programFilesx86, @"Microsoft Visual Studio 11.0\VC\vcvarsall.bat"), 11, "x86");
yield return new VcVarsBatFile(actions.PathCombine(programFilesx86, @"Microsoft Visual Studio 10.0\VC\vcvarsall.bat"), 10, "x86");
}
/// <summary>
/// Enumerates all available tools.
/// </summary>
public static IEnumerable<VcVarsBatFile> VcVarsAllBatFiles(IBuildActions actions) =>
GetCandidateVcVarsFiles(actions).Where(v => actions.FileExists(v.Path));
/// <summary>
/// Finds a VcVars file that provides a compatible environment for the given solution.
/// </summary>
/// <param name="sln">The solution file.</param>
/// <returns>A compatible file, or throws an exception.</returns>
public static VcVarsBatFile FindCompatibleVcVars(IBuildActions actions, ISolution sln) =>
FindCompatibleVcVars(actions, sln.ToolsVersion.Major);
/// <summary>
/// Finds a VcVars that provides a compatible environment for the given tools version.
/// </summary>
/// <param name="targetVersion">The tools version.</param>
/// <returns>A compatible file, or null.</returns>
public static VcVarsBatFile FindCompatibleVcVars(IBuildActions actions, int targetVersion) =>
targetVersion < 10 ?
VcVarsAllBatFiles(actions).OrderByDescending(b => b.ToolsVersion).FirstOrDefault() :
VcVarsAllBatFiles(actions).Where(b => b.ToolsVersion >= targetVersion).OrderBy(b => b.ToolsVersion).FirstOrDefault();
}
}

View File

@@ -0,0 +1,195 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Semmle.Autobuild
{
/// <summary>
/// Utility to construct a build command.
/// </summary>
class CommandBuilder
{
enum EscapeMode { Process, Cmd };
readonly StringBuilder arguments;
bool firstCommand;
string executable;
readonly EscapeMode escapingMode;
readonly string workingDirectory;
readonly IDictionary<string, string> environment;
/// <summary>
/// Initializes a new instance of the <see cref="T:Semmle.Autobuild.CommandBuilder"/> class.
/// </summary>
/// <param name="workingDirectory">The working directory (<code>null</code> for current directory).</param>
/// <param name="environment">Additional environment variables.</param>
public CommandBuilder(IBuildActions actions, string workingDirectory = null, IDictionary<string, string> environment = null)
{
arguments = new StringBuilder();
if (actions.IsWindows())
{
executable = "cmd.exe";
arguments.Append("/C");
escapingMode = EscapeMode.Cmd;
}
else
{
escapingMode = EscapeMode.Process;
}
firstCommand = true;
this.workingDirectory = workingDirectory;
this.environment = environment;
}
void OdasaIndex(string odasa)
{
RunCommand(odasa, "index --auto");
}
public CommandBuilder CallBatFile(string batFile, string argumentsOpt = null)
{
NextCommand();
arguments.Append(" CALL");
QuoteArgument(batFile);
Argument(argumentsOpt);
return this;
}
/// <summary>
/// Perform odasa index on a given command or BAT file.
/// </summary>
/// <param name="odasa">The odasa executable.</param>
/// <param name="command">The command to run.</param>
/// <param name="argumentsOpt">Additional arguments.</param>
/// <returns>this for chaining calls.</returns>
public CommandBuilder IndexCommand(string odasa, string command, string argumentsOpt = null)
{
OdasaIndex(odasa);
QuoteArgument(command);
Argument(argumentsOpt);
return this;
}
static readonly char[] specialChars = { ' ', '\t', '\n', '\v', '\"' };
static readonly char[] cmdMetacharacter = { '(', ')', '%', '!', '^', '\"', '<', '>', '&', '|' };
/// <summary>
/// Appends the given argument to the command line.
/// </summary>
/// <param name="argument">The argument to append.</param>
/// <param name="force">Whether to always quote the argument.</param>
/// <param name="cmd">Whether to escape for cmd.exe</param>
///
/// <remarks>
/// This implementation is copied from
/// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
/// </remarks>
void ArgvQuote(string argument, bool force)
{
bool cmd = escapingMode == EscapeMode.Cmd;
if (!force &&
!string.IsNullOrEmpty(argument) &&
argument.IndexOfAny(specialChars) == -1)
{
arguments.Append(argument);
}
else
{
if (cmd) arguments.Append('^');
arguments.Append('\"');
for (int it = 0; ; ++it)
{
var numBackslashes = 0;
while (it != argument.Length && argument[it] == '\\')
{
++it;
++numBackslashes;
}
if (it == argument.Length)
{
arguments.Append('\\', numBackslashes * 2);
break;
}
else if (argument[it] == '\"')
{
arguments.Append('\\', numBackslashes * 2 + 1);
if (cmd) arguments.Append('^');
arguments.Append(arguments[it]);
}
else
{
arguments.Append('\\', numBackslashes);
if (cmd && cmdMetacharacter.Any(c => c == argument[it]))
arguments.Append('^');
arguments.Append(argument[it]);
}
}
if (cmd) arguments.Append('^');
arguments.Append('\"');
}
}
public CommandBuilder QuoteArgument(string argumentsOpt)
{
if (argumentsOpt != null)
{
NextArgument();
ArgvQuote(argumentsOpt, false);
}
return this;
}
void NextArgument()
{
if (arguments.Length > 0)
arguments.Append(' ');
}
public CommandBuilder Argument(string argumentsOpt)
{
if (argumentsOpt != null)
{
NextArgument();
arguments.Append(argumentsOpt);
}
return this;
}
void NextCommand()
{
if (firstCommand)
firstCommand = false;
else
arguments.Append(" &&");
}
public CommandBuilder RunCommand(string exe, string argumentsOpt = null)
{
var (exe0, arg0) =
escapingMode == EscapeMode.Process && exe.EndsWith(".exe", System.StringComparison.Ordinal)
? ("mono", exe) // Linux
: (exe, null);
NextCommand();
if (executable == null)
{
executable = exe0;
}
else
{
QuoteArgument(exe0);
}
Argument(arg0);
Argument(argumentsOpt);
return this;
}
/// <summary>
/// Returns a build script that contains just this command.
/// </summary>
public BuildScript Script => BuildScript.Create(executable, arguments.ToString(), workingDirectory, environment);
}
}

View File

@@ -0,0 +1,268 @@
using System;
using Semmle.Util.Logging;
using System.Linq;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.IO;
namespace Semmle.Autobuild
{
/// <summary>
/// A build rule where the build command is of the form "dotnet build".
/// Currently unused because the tracer does not work with dotnet.
/// </summary>
class DotNetRule : IBuildRule
{
public BuildScript Analyse(Autobuilder builder)
{
builder.Log(Severity.Info, "Attempting to build using .NET Core");
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);
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;
});
// Attempt dotnet build on each solution
return WithDotNet(builder, dotNet =>
{
var ret = GetInfoCommand(builder.Actions, dotNet);
foreach (var solution in builder.SolutionsToBuild)
{
var cleanCommand = GetCleanCommand(builder.Actions, dotNet);
cleanCommand.QuoteArgument(solution.Path);
var clean = cleanCommand.Script;
var restoreCommand = GetRestoreCommand(builder.Actions, dotNet);
restoreCommand.QuoteArgument(solution.Path);
var restore = restoreCommand.Script;
var buildCommand = GetBuildCommand(builder, dotNet);
buildCommand.QuoteArgument(solution.Path);
var build = buildCommand.Script;
ret &= clean & BuildScript.Try(restore) & build;
}
return ret;
});
}
/// <summary>
/// Returns a script that attempts to download relevant version(s) of the
/// .NET Core SDK, followed by running the script generated by <paramref name="f"/>.
///
/// The first element <code>DotNetPath</code> of the argument to <paramref name="f"/>
/// is the path where .NET Core was installed, and the second element <code>Environment</code>
/// is any additional required environment variables. The tuple argument is <code>null</code>
/// when the installation failed.
/// </summary>
public static BuildScript WithDotNet(Autobuilder builder, Func<(string DotNetPath, IDictionary<string, string> Environment)?, BuildScript> f)
{
var installDir = builder.Actions.PathCombine(builder.Options.RootDirectory, ".dotnet");
var installScript = DownloadDotNet(builder, installDir);
return BuildScript.Bind(installScript, installed =>
{
if (installed == 0)
{
// The installation succeeded, so use the newly installed .NET Core
var path = builder.Actions.GetEnvironmentVariable("PATH");
var delim = builder.Actions.IsWindows() ? ";" : ":";
var env = new Dictionary<string, string>{
{ "DOTNET_MULTILEVEL_LOOKUP", "false" }, // prevent look up of other .NET Core SDKs
{ "DOTNET_SKIP_FIRST_TIME_EXPERIENCE", "true" },
{ "PATH", installDir + delim + path }
};
return f((installDir, env));
}
return f(null);
});
}
/// <summary>
/// Returns a script for downloading relevant versions of the
/// .NET Core SDK. The SDK(s) will be installed at <code>installDir</code>
/// (provided that the script succeeds).
/// </summary>
static BuildScript DownloadDotNet(Autobuilder builder, string installDir)
{
if (!string.IsNullOrEmpty(builder.Options.DotNetVersion))
// Specific version supplied in configuration: always use that
return DownloadDotNetVersion(builder, installDir, builder.Options.DotNetVersion);
// Download versions mentioned in `global.json` files
// 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)))
{
string version;
try
{
var o = JObject.Parse(File.ReadAllText(path));
version = (string)o["sdk"]["version"];
}
catch
{
// not a valid global.json file
continue;
}
installScript &= DownloadDotNetVersion(builder, installDir, version);
validGlobalJson = true;
}
return validGlobalJson ? installScript : BuildScript.Failure;
}
/// <summary>
/// Returns a script for downloading a specific .NET Core SDK version, if the
/// version is not already installed.
///
/// See https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script.
/// </summary>
static BuildScript DownloadDotNetVersion(Autobuilder builder, string path, string version)
{
return BuildScript.Bind(GetInstalledSdksScript(builder.Actions), (sdks, sdksRet) =>
{
if (sdksRet == 0 && sdks.Count() == 1 && sdks[0].StartsWith(version + " ", StringComparison.Ordinal))
// The requested SDK is already installed (and no other SDKs are installed), so
// no need to reinstall
return BuildScript.Failure;
builder.Log(Severity.Info, "Attempting to download .NET Core {0}", version);
if (builder.Actions.IsWindows())
{
var psScript = @"param([string]$Version, [string]$InstallDir)
add-type @""
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy
{
public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem)
{
return true;
}
}
""@
$AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'
[System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
$Script = Invoke-WebRequest -useb 'https://dot.net/v1/dotnet-install.ps1'
$arguments = @{
Channel = 'release'
Version = $Version
InstallDir = $InstallDir
}
$ScriptBlock = [scriptblock]::create("".{$($Script)} $(&{$args} @arguments)"")
Invoke-Command -ScriptBlock $ScriptBlock";
var psScriptFile = builder.Actions.PathCombine(builder.Options.RootDirectory, "install-dotnet.ps1");
builder.Actions.WriteAllText(psScriptFile, psScript);
var install = new CommandBuilder(builder.Actions).
RunCommand("powershell").
Argument("-NoProfile").
Argument("-ExecutionPolicy").
Argument("unrestricted").
Argument("-file").
Argument(psScriptFile).
Argument("-Version").
Argument(version).
Argument("-InstallDir").
Argument(path);
return install.Script;
}
else
{
var curl = new CommandBuilder(builder.Actions).
RunCommand("curl").
Argument("-sO").
Argument("https://dot.net/v1/dotnet-install.sh");
var chmod = new CommandBuilder(builder.Actions).
RunCommand("chmod").
Argument("u+x").
Argument("dotnet-install.sh");
var install = new CommandBuilder(builder.Actions).
RunCommand("./dotnet-install.sh").
Argument("--channel").
Argument("release").
Argument("--version").
Argument(version).
Argument("--install-dir").
Argument(path);
return curl.Script & chmod.Script & install.Script;
}
});
}
static BuildScript GetInstalledSdksScript(IBuildActions actions)
{
var listSdks = new CommandBuilder(actions).
RunCommand("dotnet").
Argument("--list-sdks");
return listSdks.Script;
}
static string DotNetCommand(IBuildActions actions, string dotNetPath) =>
dotNetPath != null ? actions.PathCombine(dotNetPath, "dotnet") : "dotnet";
BuildScript GetInfoCommand(IBuildActions actions, (string DotNetPath, IDictionary<string, string> Environment)? arg)
{
var info = new CommandBuilder(actions, null, arg?.Environment).
RunCommand(DotNetCommand(actions, arg?.DotNetPath)).
Argument("--info");
return info.Script;
}
CommandBuilder GetCleanCommand(IBuildActions actions, (string DotNetPath, IDictionary<string, string> Environment)? arg)
{
var clean = new CommandBuilder(actions, null, arg?.Environment).
RunCommand(DotNetCommand(actions, arg?.DotNetPath)).
Argument("clean");
return clean;
}
CommandBuilder GetRestoreCommand(IBuildActions actions, (string DotNetPath, IDictionary<string, string> Environment)? arg)
{
var restore = new CommandBuilder(actions, null, arg?.Environment).
RunCommand(DotNetCommand(actions, arg?.DotNetPath)).
Argument("restore");
return restore;
}
CommandBuilder GetBuildCommand(Autobuilder builder, (string DotNetPath, IDictionary<string, string> Environment)? arg)
{
var build = new CommandBuilder(builder.Actions, null, arg?.Environment).
IndexCommand(builder.Odasa, DotNetCommand(builder.Actions, arg?.DotNetPath)).
Argument("build").
Argument("--no-incremental").
Argument("/p:UseSharedCompilation=false").
Argument(builder.Options.DotNetArguments);
return build;
}
}
}

View File

@@ -0,0 +1,24 @@
namespace Semmle.Autobuild
{
public sealed class Language
{
public static readonly Language Cpp = new Language(".vcxproj");
public static readonly Language CSharp = new Language(".csproj");
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)
{
ProjectExtension = extension;
}
public override string ToString() =>
ProjectExtension == Cpp.ProjectExtension ? "C/C++" : "C#";
}
}

View File

@@ -0,0 +1,118 @@
using Semmle.Util.Logging;
using System.IO;
using System.Linq;
namespace Semmle.Autobuild
{
/// <summary>
/// A build rule using msbuild.
/// </summary>
class MsBuildRule : IBuildRule
{
/// <summary>
/// The name of the msbuild command.
/// </summary>
const string MsBuild = "msbuild";
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");
return BuildScript.Failure;
}
var vsTools = GetVcVarsBatFile(builder);
if (vsTools == null && builder.SolutionsToBuild.Any())
{
vsTools = BuildTools.FindCompatibleVcVars(builder.Actions, builder.SolutionsToBuild.First());
}
if (vsTools == null && builder.Actions.IsWindows())
{
builder.Log(Severity.Warning, "Could not find a suitable version of vcvarsall.bat");
}
var nuget = builder.Actions.PathCombine(builder.SemmlePlatformTools, "csharp", "nuget", "nuget.exe");
var ret = BuildScript.Success;
foreach (var solution in builder.SolutionsToBuild)
{
if (builder.Options.NugetRestore)
{
var nugetCommand = new CommandBuilder(builder.Actions).
RunCommand(nuget).
Argument("restore").
QuoteArgument(solution.Path);
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.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;
command.Argument("/t:" + target);
command.Argument(string.Format("/p:Platform=\"{0}\"", platform));
command.Argument(string.Format("/p:Configuration=\"{0}\"", configuration));
command.Argument("/p:MvcBuildViews=true");
command.Argument(builder.Options.MsBuildArguments);
ret &= command.Script;
}
return ret;
}
/// <summary>
/// Gets the BAT file used to initialize the appropriate Visual Studio
/// version/platform, as specified by the `vstools_version` property in
/// lgtm.yml.
///
/// Returns <code>null</code> when no version is specified.
/// </summary>
public static VcVarsBatFile GetVcVarsBatFile(Autobuilder builder)
{
VcVarsBatFile vsTools = null;
if (builder.Options.VsToolsVersion != null)
{
if (int.TryParse(builder.Options.VsToolsVersion, out var msToolsVersion))
{
foreach (var b in BuildTools.VcVarsAllBatFiles(builder.Actions))
{
builder.Log(Severity.Info, "Found {0} version {1}", b.Path, b.ToolsVersion);
}
vsTools = BuildTools.FindCompatibleVcVars(builder.Actions, msToolsVersion);
if (vsTools == null)
builder.Log(Severity.Warning, "Could not find build tools matching version {0}", msToolsVersion);
else
builder.Log(Severity.Info, "Setting Visual Studio tools to {0}", vsTools.Path);
}
else
{
builder.Log(Severity.Error, "The format of vstools_version is incorrect. Please specify an integer.");
}
}
return vsTools;
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
namespace Semmle.Autobuild
{
class Program
{
static int Main()
{
var options = new AutobuildOptions();
var actions = SystemBuildActions.Instance;
try
{
options.ReadEnvironment(actions);
}
catch (ArgumentOutOfRangeException ex)
{
Console.WriteLine("The value \"{0}\" for parameter \"{1}\" is invalid", ex.ActualValue, ex.ParamName);
}
var builder = new Autobuilder(actions, options);
Console.WriteLine($"Semmle autobuilder for {options.Language}");
return builder.AttemptBuild();
}
}
}

View File

@@ -0,0 +1,69 @@
using System;
using System.IO;
using System.Xml;
using Semmle.Util.Logging;
namespace Semmle.Autobuild
{
/// <summary>
/// Representation of a .csproj (C#) or .vcxproj (C++) file.
/// C# project files come in 2 flavours, .Net core and msbuild, but they
/// have the same file extension.
/// </summary>
public class Project
{
/// <summary>
/// Holds if this project is for .Net core.
/// </summary>
public bool DotNetProject { get; private set; }
public bool ValidToolsVersion { get; private set; }
public Version ToolsVersion { get; private set; }
readonly string filename;
public Project(Autobuilder builder, string filename)
{
this.filename = filename;
ToolsVersion = new Version();
if (!File.Exists(filename))
return;
var projFile = new XmlDocument();
projFile.Load(filename);
var root = projFile.DocumentElement;
if (root.Name == "Project")
{
if (root.HasAttribute("Sdk"))
{
DotNetProject = true;
}
else
{
var toolsVersion = root.GetAttribute("ToolsVersion");
if (string.IsNullOrEmpty(toolsVersion))
{
builder.Log(Severity.Warning, "Project {0} is missing a tools version", filename);
}
else
{
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);
}
}
}
}
}
public override string ToString() => filename;
}
}

View File

@@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Semmle.Autobuild")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Semmle")]
[assembly: AssemblyProduct("Semmle Visual Studio Autobuild")]
[assembly: AssemblyCopyright("Copyright © Semmle 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("1d9920ad-7b00-4df1-8b01-9ff5b687828e")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<AssemblyName>Semmle.Autobuild</AssemblyName>
<RootNamespace>Semmle.Autobuild</RootNamespace>
<ApplicationIcon />
<OutputType>Exe</OutputType>
<StartupObject />
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build" Version="15.8.166" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\extractor\Semmle.Util\Semmle.Util.csproj" />
<ProjectReference Include="..\..\extractor\Semmle.Extraction.CSharp\Semmle.Extraction.CSharp.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,107 @@
using Microsoft.Build.Construction;
using Microsoft.Build.Exceptions;
using System;
using System.Collections.Generic;
using System.Linq;
using Semmle.Util;
namespace Semmle.Autobuild
{
/// <summary>
/// A solution file, extension .sln.
/// </summary>
public interface ISolution
{
/// <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>
IEnumerable<SolutionConfigurationInSolution> Configurations { get; }
/// <summary>
/// The default configuration name, e.g. "Release"
/// </summary>
string DefaultConfigurationName { get; }
/// <summary>
/// The default platform name, e.g. "x86"
/// </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
/// are inconsistent, then pick the highest/latest version.
/// If no tools versions are present, return 0.0.0.0.
/// </summary>
Version ToolsVersion { get; }
}
/// <summary>
/// A solution file on the filesystem, read using Microsoft.Build.
/// </summary>
class Solution : ISolution
{
readonly SolutionFile solution;
public IEnumerable<Project> Projects { get; private set; }
public IEnumerable<SolutionConfigurationInSolution> Configurations =>
solution == null ? Enumerable.Empty<SolutionConfigurationInSolution>() : solution.SolutionConfigurations;
public string DefaultConfigurationName =>
solution == null ? "" : solution.GetDefaultConfigurationName();
public string DefaultPlatformName =>
solution == null ? "" : solution.GetDefaultPlatformName();
public Solution(Autobuilder builder, string path)
{
Path = System.IO.Path.GetFullPath(path);
try
{
solution = SolutionFile.Parse(Path);
Projects =
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 => new Project(builder, p)).
ToArray();
}
catch (InvalidProjectFileException)
{
// 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];
}
}
public string Path { get; private set; }
public int ProjectCount => Projects.Count();
IEnumerable<Version> ToolsVersions => Projects.Where(p => p.ValidToolsVersion).Select(p => p.ToolsVersion);
public Version ToolsVersion => ToolsVersions.Any() ? ToolsVersions.Max() : new Version();
}
}

View File

@@ -0,0 +1,46 @@
using System.IO;
namespace Semmle.Autobuild
{
/// <summary>
/// Build using standalone extraction.
/// </summary>
class StandaloneBuildRule : IBuildRule
{
public BuildScript Analyse(Autobuilder builder)
{
BuildScript GetCommand(string solution)
{
var standalone = builder.Actions.PathCombine(builder.SemmlePlatformTools, "csharp", "Semmle.Extraction.CSharp.Standalone");
var cmd = new CommandBuilder(builder.Actions);
cmd.RunCommand(standalone);
if (solution != null)
cmd.QuoteArgument(solution);
cmd.Argument("--references:.");
if (!builder.Options.NugetRestore)
{
cmd.Argument("--skip-nuget");
}
return cmd.Script;
}
if (!builder.Options.Buildless)
return BuildScript.Failure;
var solutions = builder.Options.Solution.Length;
if (solutions == 0)
return GetCommand(null);
var script = BuildScript.Success;
foreach (var solution in builder.Options.Solution)
script &= GetCommand(solution);
return script;
}
}
}

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace Semmle.Autobuild
{
/// <summary>
/// XML extraction.
/// </summary>
class XmlBuildRule : IBuildRule
{
public BuildScript Analyse(Autobuilder builder)
{
var command = new CommandBuilder(builder.Actions).
RunCommand(builder.Odasa).
Argument("index --xml --extensions config csproj props xml");
return command.Script;
}
}
}