static int HandleCommand(string assembliesPath, string sourcePath, string[]?referencesPaths, bool verbose, bool quiet, bool debug, string?debugPath) { // If user provided a debug path then assume we should write debug outputs. debug |= debugPath is object; debugPath ??= Path.Combine(Path.GetTempPath(), $"BuildValidator"); referencesPaths ??= Array.Empty <string>(); var options = new Options(assembliesPath, referencesPaths, sourcePath, verbose, quiet, debug, debugPath); // TODO: remove the DemoLoggerProvider, update this dependency, // and move to the built in logger. var loggerFactory = new LoggerFactory( new[] { new ConsoleLoggerProvider(new ConsoleLoggerSettings()) }, new LoggerFilterOptions() { MinLevel = options.Verbose ? LogLevel.Trace : LogLevel.Information }); if (!options.Quiet) { loggerFactory.AddProvider(new DemoLoggerProvider()); } var logger = loggerFactory.CreateLogger <Program>(); try { var fullDebugPath = Path.GetFullPath(debugPath); logger.LogInformation($@"Using debug folder: ""{fullDebugPath}"""); Directory.Delete(debugPath, recursive: true); logger.LogInformation($@"Cleaned debug folder: ""{fullDebugPath}"""); } catch (IOException) { // no-op } try { var sourceResolver = new LocalSourceResolver(options, loggerFactory); var referenceResolver = new LocalReferenceResolver(options, loggerFactory); var buildConstructor = new BuildConstructor(referenceResolver, sourceResolver, logger); var artifactsDir = new DirectoryInfo(options.AssembliesPath); var filesToValidate = artifactsDir.EnumerateFiles("*.exe", SearchOption.AllDirectories) .Concat(artifactsDir.EnumerateFiles("*.dll", SearchOption.AllDirectories)) .Distinct(FileNameEqualityComparer.Instance); var success = ValidateFiles(filesToValidate, buildConstructor, logger, options); Console.Out.Flush(); return(success ? ExitSuccess : ExitFailure); } catch (Exception ex) { logger.LogError(ex, ex.Message); throw; } }
private static void ValidateFiles(IEnumerable <FileInfo> files, BuildConstructor buildConstructor, string?thisCompilerVersion) { var assembliesCompiled = new List <CompilationDiff>(); foreach (var file in files) { var compilationDiff = ValidateFile(file, buildConstructor, thisCompilerVersion); if (compilationDiff is null) { continue; } assembliesCompiled.Add(compilationDiff); } var sb = new StringBuilder(); sb.AppendLine("===================="); sb.AppendLine("Summary:"); sb.AppendLine(); sb.AppendLine("Successful Tests:"); foreach (var diff in assembliesCompiled.Where(a => a.AreEqual == true)) { sb.AppendLine($"\t{diff.OriginalPath}"); } sb.AppendLine(); sb.AppendLine("Failed Tests:"); foreach (var diff in assembliesCompiled.Where(a => a.AreEqual == false)) { sb.AppendLine($"\t{diff.OriginalPath}"); } sb.AppendLine(); sb.AppendLine("Error Cases:"); foreach (var diff in assembliesCompiled.Where(a => !a.AreEqual.HasValue)) { sb.AppendLine($"\t{diff.OriginalPath}"); if (diff.Exception != null) { sb.AppendLine($"\tException: {diff.Exception.Message}"); } } sb.AppendLine("===================="); s_logger.LogInformation(sb.ToString()); }
// TODO: it feels like "logger" and "options" should be instance variables of something private static bool ValidateFiles(IEnumerable <FileInfo> originalBinaries, BuildConstructor buildConstructor, ILogger logger, Options options) { var assembliesCompiled = new List <CompilationDiff>(); foreach (var file in originalBinaries) { var compilationDiff = ValidateFile(file, buildConstructor, logger, options); if (compilationDiff is null) { logger.LogInformation($"Ignoring {file.FullName}"); continue; } assembliesCompiled.Add(compilationDiff); } bool success = true; using var summary = logger.BeginScope("Summary"); using (logger.BeginScope("Successful rebuilds")) { foreach (var diff in assembliesCompiled.Where(a => a.AreEqual == true)) { logger.LogInformation($"\t{diff.OriginalPath}"); } } using (logger.BeginScope("Rebuilds with output differences")) { foreach (var diff in assembliesCompiled.Where(a => a.AreEqual == false)) { // TODO: can we include the path to any diff artifacts? logger.LogWarning($"\t{diff.OriginalPath}"); success = false; } } using (logger.BeginScope("Rebuilds with compilation errors")) { foreach (var diff in assembliesCompiled.Where(a => a.AreEqual == null)) { logger.LogError($"{diff.OriginalPath} had {diff.Diagnostics.Length} diagnostics."); success = false; } } return(success); }
private static CompilationDiff?ValidateFile(FileInfo file, BuildConstructor buildConstructor, string?thisCompilerVersion) { if (s_ignorePatterns.Any(r => r.IsMatch(file.FullName))) { s_logger.LogTrace($"Ignoring {file.FullName}"); return(null); } MetadataReaderProvider?pdbReaderProvider = null; try { // Find the embedded pdb using var fileStream = file.OpenRead(); using var peReader = new PEReader(fileStream); var pdbOpened = peReader.TryOpenAssociatedPortablePdb( peImagePath: file.FullName, filePath => File.Exists(filePath) ? File.OpenRead(filePath) : null, out pdbReaderProvider, out var pdbPath); if (!pdbOpened || pdbReaderProvider is null) { s_logger.LogError($"Could not find pdb for {file.FullName}"); return(null); } s_logger.LogInformation($"Compiling {file.FullName} with pdb {pdbPath ?? "[embedded]"}"); var reader = pdbReaderProvider.GetMetadataReader(); // TODO: Check compilation version using the PEReader var compilation = buildConstructor.CreateCompilation(reader, file.Name); return(CompilationDiff.Create(file, compilation)); } catch (Exception e) { s_logger.LogError(e, file.FullName); return(CompilationDiff.Create(file, e)); } finally { pdbReaderProvider?.Dispose(); } }
static void Main(string[] args) { Options options; try { options = Options.Create(args); } catch (InvalidDataException) { PrintHelp(); return; } var loggerFactory = new LoggerFactory(Enumerable.Empty <ILoggerProvider>(), new LoggerFilterOptions() { MinLevel = options.Verbose ? LogLevel.Trace : LogLevel.Information }); if (options.ConsoleOutput) { loggerFactory.AddConsole(); } s_logger = loggerFactory.CreateLogger <Program>(); var sourceResolver = new LocalSourceResolver(loggerFactory); var referenceResolver = new LocalReferenceResolver(loggerFactory); var buildConstructor = new BuildConstructor(referenceResolver, sourceResolver); var artifactsDir = LocalReferenceResolver.GetArtifactsDirectory(); var thisCompilerVersion = options.IgnoreCompilerVersion ? null : typeof(Compilation).Assembly.GetCustomAttribute <AssemblyInformationalVersionAttribute>()?.InformationalVersion; var filesToValidate = artifactsDir.EnumerateFiles("*.exe", SearchOption.AllDirectories) .Concat(artifactsDir.EnumerateFiles("*.dll", SearchOption.AllDirectories)) .Distinct(FileNameEqualityComparer.Instance); ValidateFiles(filesToValidate, buildConstructor, thisCompilerVersion); }
private static CompilationDiff?ValidateFile(FileInfo originalBinary, BuildConstructor buildConstructor, ILogger logger, Options options) { if (s_ignorePatterns.Any(r => r.IsMatch(originalBinary.FullName))) { logger.LogTrace($"Ignoring {originalBinary.FullName}"); return(null); } MetadataReaderProvider?pdbReaderProvider = null; try { // Find the embedded pdb using var originalBinaryStream = originalBinary.OpenRead(); using var originalPeReader = new PEReader(originalBinaryStream); var pdbOpened = originalPeReader.TryOpenAssociatedPortablePdb( peImagePath: originalBinary.FullName, filePath => File.Exists(filePath) ? File.OpenRead(filePath) : null, out pdbReaderProvider, out var pdbPath); if (!pdbOpened || pdbReaderProvider is null) { logger.LogError($"Could not find pdb for {originalBinary.FullName}"); return(null); } using var _ = logger.BeginScope($"Verifying {originalBinary.FullName} with pdb {pdbPath ?? "[embedded]"}"); var pdbReader = pdbReaderProvider.GetMetadataReader(); var optionsReader = new CompilationOptionsReader(logger, pdbReader, originalPeReader); var compilation = buildConstructor.CreateCompilation( optionsReader, Path.GetFileNameWithoutExtension(originalBinary.Name)); var compilationDiff = CompilationDiff.Create(originalBinary, optionsReader, compilation, getDebugEntryPoint(), logger, options); return(compilationDiff); IMethodSymbol?getDebugEntryPoint() { if (optionsReader.GetMainTypeName() is { } mainTypeName&& optionsReader.GetMainMethodName() is { } mainMethodName) { var typeSymbol = compilation.GetTypeByMetadataName(mainTypeName); if (typeSymbol is object) { var methodSymbols = typeSymbol .GetMembers(mainMethodName) .OfType <IMethodSymbol>(); return(methodSymbols.FirstOrDefault()); } } return(null); } } finally { pdbReaderProvider?.Dispose(); } }