using System; using Semmle.Util.Logging; using System.Linq; using Newtonsoft.Json.Linq; using System.Collections.Generic; using System.IO; using Semmle.Util; namespace Semmle.Autobuild { /// /// A build rule where the build command is of the form "dotnet build". /// Currently unused because the tracer does not work with dotnet. /// class DotNetRule : IBuildRule { public BuildScript Analyse(Autobuilder builder, bool auto) { if (!builder.ProjectsOrSolutionsToBuild.Any()) return BuildScript.Failure; if (auto) { var notDotNetProject = builder.ProjectsOrSolutionsToBuild. SelectMany(p => Enumerators.Singleton(p).Concat(p.IncludedProjects)). OfType(). FirstOrDefault(p => !p.DotNetProject); if (notDotNetProject != null) { builder.Log(Severity.Info, "Not using .NET Core because of incompatible project {0}", notDotNetProject); return BuildScript.Failure; } builder.Log(Severity.Info, "Attempting to build using .NET Core"); } return WithDotNet(builder, dotNet => { var ret = GetInfoCommand(builder.Actions, dotNet); foreach (var projectOrSolution in builder.ProjectsOrSolutionsToBuild) { var cleanCommand = GetCleanCommand(builder.Actions, dotNet); cleanCommand.QuoteArgument(projectOrSolution.FullPath); var clean = cleanCommand.Script; var restoreCommand = GetRestoreCommand(builder.Actions, dotNet); restoreCommand.QuoteArgument(projectOrSolution.FullPath); var restore = restoreCommand.Script; var buildCommand = GetBuildCommand(builder, dotNet); buildCommand.QuoteArgument(projectOrSolution.FullPath); var build = buildCommand.Script; ret &= clean & BuildScript.Try(restore) & build; } return ret; }); } /// /// Returns a script that attempts to download relevant version(s) of the /// .NET Core SDK, followed by running the script generated by . /// /// The first element DotNetPath of the argument to /// is the path where .NET Core was installed, and the second element Environment /// is any additional required environment variables. The tuple argument is null /// when the installation failed. /// public static BuildScript WithDotNet(Autobuilder builder, Func<(string DotNetPath, IDictionary 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{ { "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); }); } /// /// Returns a script for downloading relevant versions of the /// .NET Core SDK. The SDK(s) will be installed at installDir /// (provided that the script succeeds). /// 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.Select(p => p.Item1).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; } /// /// 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. /// 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 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 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 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 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; } } }