using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; namespace Semmle.Extraction.CSharp { /// /// Identifies the compiler and framework from the command line arguments. /// --compiler specifies the compiler /// --framework specifies the .net framework /// public class CompilerVersion { private const string csc_rsp = "csc.rsp"; private readonly string? specifiedFramework = null; /// /// The value specified by --compiler, or null. /// public string? SpecifiedCompiler { get; private set; } /// /// Why was the candidate exe rejected as a compiler? /// public string? SkipReason { get; private set; } private static readonly Dictionary knownCompilerNames = new Dictionary { { "csc.exe", "Microsoft" }, { "csc2.exe", "Microsoft" }, { "csc.dll", "Microsoft" }, { "mcs.exe", "Novell" } }; /// /// Probes the compiler (if specified). /// /// The command line arguments. public CompilerVersion(Options options) { SpecifiedCompiler = options.CompilerName; specifiedFramework = options.Framework; if (SpecifiedCompiler is not null) { if (!File.Exists(SpecifiedCompiler)) { SkipExtractionBecause("the specified file does not exist"); return; } // Reads the file details from the .exe var compilerDir = Path.GetDirectoryName(SpecifiedCompiler); if (compilerDir is null) { SkipExtractionBecause("the compiler directory could not be retrieved"); return; } var mscorlibExists = File.Exists(Path.Combine(compilerDir, "mscorlib.dll")); if (specifiedFramework is null && mscorlibExists) { specifiedFramework = compilerDir; } var versionInfo = FileVersionInfo.GetVersionInfo(SpecifiedCompiler); if (!knownCompilerNames.TryGetValue(versionInfo.OriginalFilename ?? string.Empty, out var vendor)) { SkipExtractionBecause("the compiler name is not recognised"); return; } if (versionInfo.LegalCopyright is null || !versionInfo.LegalCopyright.Contains(vendor)) { SkipExtractionBecause($"the compiler isn't copyright {vendor}, but instead {versionInfo.LegalCopyright ?? ""}"); return; } } ArgsWithResponse = AddDefaultResponse(CscRsp, options.CompilerArguments).ToArray(); } private void SkipExtractionBecause(string reason) { SkipExtraction = true; SkipReason = reason; } /// /// The directory containing the .Net Framework. /// public string FrameworkPath => specifiedFramework ?? RuntimeEnvironment.GetRuntimeDirectory(); /// /// The file csc.rsp. /// private string CscRsp => Path.Combine(FrameworkPath, csc_rsp); /// /// Should we skip extraction? /// Only if csc.exe was specified but it wasn't a compiler. /// public bool SkipExtraction { get; private set; } /// /// Gets additional reference directories - the compiler directory. /// public string? AdditionalReferenceDirectories => SpecifiedCompiler is not null ? Path.GetDirectoryName(SpecifiedCompiler) : null; /// /// Adds @csc.rsp to the argument list to mimic csc.exe. /// /// The full pathname of csc.rsp. /// The other command line arguments. /// Modified list of arguments. private static IEnumerable AddDefaultResponse(string responseFile, IEnumerable args) { var ret = SuppressDefaultResponseFile(args) || !File.Exists(responseFile) ? args : new[] { "@" + responseFile }.Concat(args); // make sure to never treat warnings as errors in the extractor: // our version of Roslyn may report warnings that the actual build // doesn't return ret.Concat(new[] { "/warnaserror-" }); } private static bool SuppressDefaultResponseFile(IEnumerable args) { return args.Any(arg => new[] { "/noconfig", "-noconfig" }.Contains(arg.ToLowerInvariant())); } public IEnumerable ArgsWithResponse { get; } = Enumerable.Empty(); } }