using System; using Semmle.Util.Logging; using System.Linq; using Newtonsoft.Json.Linq; using System.Collections.Generic; using System.IO; using Semmle.Util; using System.Text.RegularExpressions; using Semmle.Autobuild.Shared; namespace Semmle.Autobuild.CSharp { /// /// A build rule where the build command is of the form "dotnet build". /// Currently unused because the tracer does not work with dotnet. /// internal 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 is not 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, (dotNetPath, environment) => { var ret = GetInfoCommand(builder.Actions, dotNetPath, environment); foreach (var projectOrSolution in builder.ProjectsOrSolutionsToBuild) { var cleanCommand = GetCleanCommand(builder.Actions, dotNetPath, environment); cleanCommand.QuoteArgument(projectOrSolution.FullPath); var clean = cleanCommand.Script; var restoreCommand = GetRestoreCommand(builder.Actions, dotNetPath, environment); restoreCommand.QuoteArgument(projectOrSolution.FullPath); var restore = restoreCommand.Script; var build = GetBuildScript(builder, dotNetPath, environment, projectOrSolution.FullPath); ret &= BuildScript.Try(clean) & BuildScript.Try(restore) & build; } return ret; }); } private static BuildScript WithDotNet(Autobuilder builder, Func?, BuildScript> f) { var installDir = builder.Actions.PathCombine(builder.Options.RootDirectory, ".dotnet"); var installScript = DownloadDotNet(builder, installDir); return BuildScript.Bind(installScript, installed => { Dictionary? env; if (installed == 0) { // The installation succeeded, so use the newly installed .NET Core var path = builder.Actions.GetEnvironmentVariable("PATH"); var delim = builder.Actions.IsWindows() ? ";" : ":"; 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 } }; } else { installDir = null; env = null; } if (env is null) env = new Dictionary(); env.Add("UseSharedCompilation", "false"); return f(installDir, env); }); } /// /// Returns a script that attempts to download relevant version(s) of the /// .NET Core SDK, followed by running the script generated by . /// /// The argument to is any additional required environment /// variables needed by the installed .NET Core (null when no variables /// are needed). /// public static BuildScript WithDotNet(Autobuilder builder, Func?, BuildScript> f) => WithDotNet(builder, (_1, env) => f(env)); /// /// 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). /// private 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 // lgtm[cs/catch-of-all-exceptions] { // 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. /// private 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 psCommand = $"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version {version} -InstallDir {path}"; BuildScript GetInstall(string pwsh) => new CommandBuilder(builder.Actions). RunCommand(pwsh). Argument("-NoProfile"). Argument("-ExecutionPolicy"). Argument("unrestricted"). Argument("-Command"). Argument("\"" + psCommand + "\""). Script; return GetInstall("pwsh") | GetInstall("powershell"); } else { var downloadDotNetInstallSh = BuildScript.DownloadFile( "https://dot.net/v1/dotnet-install.sh", "dotnet-install.sh", e => builder.Log(Severity.Warning, $"Failed to download 'dotnet-install.sh': {e.Message}")); 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); var removeScript = new CommandBuilder(builder.Actions). RunCommand("rm"). Argument("dotnet-install.sh"); return downloadDotNetInstallSh & chmod.Script & install.Script & BuildScript.Try(removeScript.Script); } }); } private static BuildScript GetInstalledSdksScript(IBuildActions actions) { var listSdks = new CommandBuilder(actions, silent: true). RunCommand("dotnet"). Argument("--list-sdks"); return listSdks.Script; } private static string DotNetCommand(IBuildActions actions, string? dotNetPath) => dotNetPath is not null ? actions.PathCombine(dotNetPath, "dotnet") : "dotnet"; private static BuildScript GetInfoCommand(IBuildActions actions, string? dotNetPath, IDictionary? environment) { var info = new CommandBuilder(actions, null, environment). RunCommand(DotNetCommand(actions, dotNetPath)). Argument("--info"); return info.Script; } private static CommandBuilder GetCleanCommand(IBuildActions actions, string? dotNetPath, IDictionary? environment) { var clean = new CommandBuilder(actions, null, environment). RunCommand(DotNetCommand(actions, dotNetPath)). Argument("clean"); return clean; } private static CommandBuilder GetRestoreCommand(IBuildActions actions, string? dotNetPath, IDictionary? environment) { var restore = new CommandBuilder(actions, null, environment). RunCommand(DotNetCommand(actions, dotNetPath)). Argument("restore"); return restore; } /// /// Gets the `dotnet build` script. /// private static BuildScript GetBuildScript(Autobuilder builder, string? dotNetPath, IDictionary? environment, string projOrSln) { var build = new CommandBuilder(builder.Actions, null, environment); var script = builder.MaybeIndex(build, DotNetCommand(builder.Actions, dotNetPath)). Argument("build"). Argument("--no-incremental"); return script.Argument("/p:UseSharedCompilation=false"). Argument(builder.Options.DotNetArguments). QuoteArgument(projOrSln). Script; } } }