using System;
using System.Collections.Generic;
using System.Linq;
using Semmle.Util;
using Semmle.Autobuild.Shared;
using Semmle.Extraction.CSharp.DependencyFetching;
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 List FailedProjectsOrSolutions { get; } = [];
///
/// A list of projects which are incompatible with DotNet.
///
public IEnumerable> NotDotNetProjects { get; private set; }
public DotNetRule() => NotDotNetProjects = [];
public BuildScript Analyse(IAutobuilder builder, bool auto)
{
if (!builder.ProjectsOrSolutionsToBuild.Any())
return BuildScript.Failure;
if (auto)
{
NotDotNetProjects = builder.ProjectsOrSolutionsToBuild
.SelectMany(p => new[] { p }.Concat(p.IncludedProjects))
.OfType>()
.Where(p => !p.DotNetProject);
var notDotNetProject = NotDotNetProjects.FirstOrDefault();
if (notDotNetProject is not null)
{
builder.Logger.LogInfo($"Not using .NET Core because of incompatible project {notDotNetProject}");
return BuildScript.Failure;
}
builder.Logger.LogInfo("Attempting to build using .NET Core");
}
return WithDotNet(builder, ensureDotNetAvailable: false, (dotNetPath, environment) =>
{
// When a custom .NET CLI has been installed, `dotnet --info` has already been executed
// to verify the installation.
var ret = dotNetPath is null ? DotNet.InfoScript(builder.Actions, DotNetCommand(builder.Actions, dotNetPath), environment, builder.Logger) : BuildScript.Success;
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) & BuildScript.OnFailure(build, ret =>
{
FailedProjectsOrSolutions.Add(projectOrSolution);
});
}
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 arguments to are the path to the directory in which the
/// .NET Core SDK(s) were installed and any additional required environment
/// variables needed by the installed .NET Core (null when no variables
/// are needed).
///
public static BuildScript WithDotNet(IAutobuilder builder, bool ensureDotNetAvailable, Func?, BuildScript> f)
{
var temp = FileUtils.GetTemporaryWorkingDirectory(builder.Actions.GetEnvironmentVariable, builder.Options.Language.UpperCaseName, out var shouldCleanUp);
return DotNet.WithDotNet(builder.Actions, builder.Logger, builder.Paths.Select(x => x.Item1), temp, shouldCleanUp, ensureDotNetAvailable, builder.Options.DotNetVersion, installDir =>
{
var env = DotNet.MinimalEnvironment.ToDictionary();
if (installDir is not null)
{
// The installation succeeded, so use the newly installed .NET
var path = builder.Actions.GetEnvironmentVariable("PATH");
var delim = builder.Actions.IsWindows() ? ";" : ":";
env.Add("DOTNET_MULTILEVEL_LOOKUP", "false"); // prevent look up of other .NET SDKs
env.Add("PATH", installDir + delim + path);
}
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(IAutobuilder builder, Func?, BuildScript> f)
=> WithDotNet(builder, ensureDotNetAvailable: false, (_, env) => f(env));
private static string DotNetCommand(IBuildActions actions, string? dotNetPath) =>
dotNetPath is not null ? actions.PathCombine(dotNetPath, "dotnet") : "dotnet";
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(IAutobuilder builder, string? dotNetPath, IDictionary? environment, string projOrSln)
{
var build = new CommandBuilder(builder.Actions, null, environment);
var script = build.RunCommand(DotNetCommand(builder.Actions, dotNetPath)).
Argument("build").
Argument("--no-incremental");
return
script.QuoteArgument(projOrSln).
Script;
}
}
}