using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Xml;
using Semmle.Util.Logging;
namespace Semmle.Util
{
public delegate void BuildOutputHandler(string? data);
///
/// Wrapper around system calls so that the build scripts can be unit-tested.
///
public interface IBuildActions
{
///
/// Runs a process, captures its output, and provides it asynchronously.
///
/// The exe to run.
/// The other command line arguments.
/// The working directory (null for current directory).
/// Additional environment variables.
/// A handler for stdout output.
/// A handler for stderr output.
/// The process exit code.
int RunProcess(string exe, string args, string? workingDirectory, IDictionary? env, BuildOutputHandler onOutput, BuildOutputHandler onError);
///
/// Runs a process and captures its output.
///
/// The exe to run.
/// The other command line arguments.
/// The working directory (null for current directory).
/// Additional environment variables.
/// The lines of stdout.
/// The process exit code.
int RunProcess(string exe, string args, string? workingDirectory, IDictionary? env, out IList stdOut);
///
/// Runs a process but does not capture its output.
///
/// The exe to run.
/// The other command line arguments.
/// The working directory (null for current directory).
/// Additional environment variables.
/// The process exit code.
int RunProcess(string exe, string args, string? workingDirectory, IDictionary? env);
///
/// Tests whether a file exists, File.Exists().
///
/// The filename.
/// True iff the file exists.
bool FileExists(string file);
///
/// Tests whether a directory exists, Directory.Exists().
///
/// The directory name.
/// True iff the directory exists.
bool DirectoryExists(string dir);
///
/// Deletes a file, File.Delete().
///
/// The filename.
void FileDelete(string file);
///
/// Deletes a directory, Directory.Delete().
///
void DirectoryDelete(string dir, bool recursive);
///
/// Creates all directories and subdirectories in the specified path unless they already exist.
///
void CreateDirectory(string path);
///
/// Gets an environment variable, Environment.GetEnvironmentVariable().
///
/// The name of the variable.
/// The string value, or null if the variable is not defined.
string? GetEnvironmentVariable(string name);
///
/// Gets the current directory, Directory.GetCurrentDirectory().
///
/// The current directory.
string GetCurrentDirectory();
///
/// Enumerates files in a directory, Directory.EnumerateFiles().
///
/// The directory to enumerate.
/// A list of filenames, or an empty list.
IEnumerable EnumerateFiles(string dir);
///
/// Enumerates the directories in a directory, Directory.EnumerateDirectories().
///
/// The directory to enumerate.
/// List of subdirectories, or empty list.
IEnumerable EnumerateDirectories(string dir);
///
/// True if we are running on Windows.
///
bool IsWindows();
///
/// Gets a value indicating whether we are running on macOS.
///
/// True if we are running on macOS.
bool IsMacOs();
///
/// Gets a value indicating whether we are running on Apple Silicon.
///
/// True if we are running on Apple Silicon.
bool IsRunningOnAppleSilicon();
///
/// Combine path segments, Path.Combine().
///
/// The parts of the path.
/// The combined path.
string PathCombine(params string[] parts);
///
/// Gets the full path for , Path.GetFullPath().
///
string GetFullPath(string path);
///
/// Returns the file name and extension of the specified path string.
///
[return: NotNullIfNotNull(nameof(path))]
string? GetFileName(string? path);
///
/// Returns the directory information for the specified path string.
///
string? GetDirectoryName(string? path);
///
/// Writes contents to file, File.WriteAllText().
///
/// The filename.
/// The text.
void WriteAllText(string filename, string contents);
///
/// Loads the XML document from .
///
XmlDocument LoadXml(string filename);
string EnvironmentExpandEnvironmentVariables(string s);
///
/// Downloads the resource with the specified URI to a local file.
///
void DownloadFile(string address, string fileName, ILogger logger);
///
/// Creates an for the given .
///
///
/// The path suggesting where the diagnostics should be written to.
///
///
/// A to which diagnostic entries can be added.
///
IDiagnosticsWriter CreateDiagnosticsWriter(string filename);
}
///
/// An implementation of IBuildActions that actually performs the requested operations.
///
public class SystemBuildActions : IBuildActions
{
void IBuildActions.FileDelete(string file) => File.Delete(file);
bool IBuildActions.FileExists(string file) => File.Exists(file);
private static ProcessStartInfo GetProcessStartInfo(string exe, string arguments, string? workingDirectory, IDictionary? environment)
{
var pi = new ProcessStartInfo(exe, arguments)
{
UseShellExecute = false,
RedirectStandardOutput = true
};
if (workingDirectory is not null)
pi.WorkingDirectory = workingDirectory;
environment?.ForEach(kvp => pi.Environment[kvp.Key] = kvp.Value);
return pi;
}
int IBuildActions.RunProcess(string exe, string args, string? workingDirectory, System.Collections.Generic.IDictionary? env, BuildOutputHandler onOutput, BuildOutputHandler onError)
{
var pi = GetProcessStartInfo(exe, args, workingDirectory, env);
pi.RedirectStandardError = true;
return pi.ReadOutput(out _, onOut: s => onOutput(s), onError: s => onError(s));
}
int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory, IDictionary? environment)
{
var pi = GetProcessStartInfo(cmd, args, workingDirectory, environment);
return pi.ReadOutput(out _, onOut: Console.WriteLine, onError: null);
}
int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory, IDictionary? environment, out IList stdOut)
{
var pi = GetProcessStartInfo(cmd, args, workingDirectory, environment);
return pi.ReadOutput(out stdOut, onOut: null, onError: null);
}
void IBuildActions.DirectoryDelete(string dir, bool recursive) => Directory.Delete(dir, recursive);
bool IBuildActions.DirectoryExists(string dir) => Directory.Exists(dir);
void IBuildActions.CreateDirectory(string path) => Directory.CreateDirectory(path);
string? IBuildActions.GetEnvironmentVariable(string name) => Environment.GetEnvironmentVariable(name);
string IBuildActions.GetCurrentDirectory() => Directory.GetCurrentDirectory();
IEnumerable IBuildActions.EnumerateFiles(string dir) => Directory.EnumerateFiles(dir);
IEnumerable IBuildActions.EnumerateDirectories(string dir) => Directory.EnumerateDirectories(dir);
bool IBuildActions.IsWindows() => Win32.IsWindows();
bool IBuildActions.IsMacOs() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
bool IBuildActions.IsRunningOnAppleSilicon()
{
var thisBuildActions = (IBuildActions)this;
if (!thisBuildActions.IsMacOs())
{
return false;
}
try
{
thisBuildActions.RunProcess("sysctl", "machdep.cpu.brand_string", workingDirectory: null, env: null, out var stdOut);
return stdOut?.Any(s => s?.ToLowerInvariant().Contains("apple") == true) ?? false;
}
catch (Exception)
{
return false;
}
}
string IBuildActions.PathCombine(params string[] parts) => Path.Combine(parts);
void IBuildActions.WriteAllText(string filename, string contents) => File.WriteAllText(filename, contents);
XmlDocument IBuildActions.LoadXml(string filename)
{
var ret = new XmlDocument();
ret.Load(filename);
return ret;
}
string IBuildActions.GetFullPath(string path) => Path.GetFullPath(path);
string? IBuildActions.GetFileName(string? path) => Path.GetFileName(path);
string? IBuildActions.GetDirectoryName(string? path) => Path.GetDirectoryName(path);
public string EnvironmentExpandEnvironmentVariables(string s) => Environment.ExpandEnvironmentVariables(s);
public void DownloadFile(string address, string fileName, ILogger logger) =>
FileUtils.DownloadFile(address, fileName, logger);
public IDiagnosticsWriter CreateDiagnosticsWriter(string filename) => new DiagnosticsStream(filename);
public static IBuildActions Instance { get; } = new SystemBuildActions();
}
public static class BuildActionExtensions
{
private static void FindFiles(this IBuildActions actions, string dir, int depth, int? maxDepth, IList<(string, int)> results)
{
foreach (var f in actions.EnumerateFiles(dir))
{
results.Add((f, depth));
}
if (depth < maxDepth)
{
foreach (var d in actions.EnumerateDirectories(dir))
{
actions.FindFiles(d, depth + 1, maxDepth, results);
}
}
}
public static (string path, int depth)[] FindFiles(this IBuildActions actions, string dir, int? maxDepth)
{
var results = new List<(string, int)>();
actions.FindFiles(dir, 0, maxDepth, results);
return results.OrderBy(f => f.Item2).ToArray();
}
}
}