Add R2RTest support for compiling specific larger applications.
public FileInfo CrossgenResponseFile { get; set; }
public DirectoryInfo[] RewriteOldPath { get; set; }
public DirectoryInfo[] RewriteNewPath { get; set; }
+ public DirectoryInfo AspNetPath { get; set; }
public bool MeasurePerf { get; set; }
public string InputFileSearchString { get; set; }
public string ConfigurationSuffix => (Release ? "-ret.out" : "-chk.out");
.AddCommand(CompileSubtree())
.AddCommand(CompileFramework())
.AddCommand(CompileNugetPackages())
- .AddCommand(CompileCrossgenRsp());
+ .AddCommand(CompileCrossgenRsp())
+ .AddCommand(CompileSerp());
return parser;
},
handler: CommandHandler.Create<BuildOptions>(CompileFromCrossgenRspCommand.CompileFromCrossgenRsp));
+ Command CompileSerp() =>
+ new Command("compile-serp", "Compile existing application",
+ new Symbol[]
+ {
+ InputDirectory(),
+ OutputDirectory(),
+ DegreeOfParallelism(),
+ CoreRootDirectory(),
+ AspNetPath(),
+ Composite(),
+ },
+ handler: CommandHandler.Create<BuildOptions>(CompileSerpCommand.CompileSerpAssemblies));
+
// Todo: Input / Output directories should be required arguments to the command when they're made available to handlers
// https://github.com/dotnet/command-line-api/issues/297
Option InputDirectory() =>
//
Option PackageList() =>
new Option(new[] { "--package-list", "-pl" }, "Text file containing a package name on each line", new Argument<FileInfo>().ExistingOnly());
+
+ //
+ // compile-serp specific options
+ //
+ Option AspNetPath() =>
+ new Option(new[] { "--asp-net-path", "-asp" }, "Path to SERP's ASP.NET Core folder", new Argument<DirectoryInfo>().ExistingOnly());
}
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace R2RTest
+{
+ class CompileSerpCommand
+ {
+ public static int CompileSerpAssemblies(BuildOptions options)
+ {
+ if (options.InputDirectory == null)
+ {
+ Console.Error.WriteLine("Specify --response-file or --input-directory containing multiple response files.");
+ return 1;
+ }
+
+ if (options.CoreRootDirectory == null)
+ {
+ Console.Error.WriteLine("--core-root-directory (--cr) is a required argument.");
+ return 1;
+ }
+
+
+ // This command does not work in the context of an app, just a loose set of rsp files so don't execute anything we compile
+ options.NoJit = true;
+ options.NoEtw = true;
+
+ string serpDir = options.InputDirectory.FullName;
+ if (!File.Exists(Path.Combine(serpDir, "runserp.cmd")))
+ {
+ Console.Error.WriteLine($"Error: InputDirectory must point at a SERP build. Could not find {Path.Combine(serpDir, "runserp.cmd")}");
+ return 1;
+ }
+
+ string whiteListFilePath = Path.Combine(serpDir, "WhitelistDlls.txt");
+ if (!File.Exists(whiteListFilePath))
+ {
+ Console.Error.WriteLine($"File {whiteListFilePath} was not found");
+ return 1;
+ }
+
+ if (!File.Exists(Path.Combine(options.AspNetPath.FullName, "Microsoft.AspNetCore.dll")))
+ {
+ Console.Error.WriteLine($"Error: Asp.NET Core path must contain Microsoft.AspNetCore.dll");
+ return 1;
+ }
+
+ string binDir = Path.Combine(serpDir, "bin");
+
+ // Remove existing native images
+ foreach (var file in Directory.GetFiles(Path.Combine(serpDir, "App_Data\\Answers\\Services\\Packages"), "*.dll", SearchOption.AllDirectories))
+ {
+ if (file.EndsWith(".ni.dll") || file.EndsWith(".ni.exe"))
+ {
+ File.Delete(file);
+ }
+ }
+
+ foreach (var file in Directory.GetFiles(binDir, "*.dll", SearchOption.AllDirectories))
+ {
+ if (file.EndsWith(".ni.dll") || file.EndsWith(".ni.exe"))
+ {
+ File.Delete(file);
+ }
+ }
+
+ // Add all assemblies from the various SERP packages (filtered by ShouldInclude)
+ List<string> binFiles = Directory.GetFiles(Path.Combine(serpDir, "App_Data\\Answers\\Services\\Packages"), "*.dll", SearchOption.AllDirectories)
+ .Where((string x) => ShouldInclude(x))
+ .ToList();
+
+ // Add a whitelist of assemblies from bin
+ foreach (string item in new HashSet<string>(File.ReadAllLines(whiteListFilePath)))
+ {
+ binFiles.Add(Path.Combine(binDir, item));
+ }
+
+ HashSet<string> referenceAssemblyDirectories = new HashSet<string>();
+ foreach (var binFile in binFiles)
+ {
+ var directory = Path.GetDirectoryName(binFile);
+ if (!referenceAssemblyDirectories.Contains(directory))
+ referenceAssemblyDirectories.Add(directory);
+ }
+
+ // TestILC needs a list of all directories containing assemblies that are referenced from crossgen
+ List<string> referenceAssemblies = new List<string>();
+ HashSet<string> simpleNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+
+ // Reference all managed assemblies in /bin and /App_Data/answers/services/packages
+ foreach (string binFile in ResolveReferences(referenceAssemblyDirectories))
+ {
+ simpleNames.Add(Path.GetFileNameWithoutExtension(binFile));
+ referenceAssemblies.Add(binFile);
+ }
+
+ referenceAssemblies.AddRange(ComputeManagedAssemblies.GetManagedAssembliesInFolderNoSimpleNameDuplicates(simpleNames, options.AspNetPath.FullName, "*.dll"));
+
+ // Add CoreRoot last because it contains various non-framework assemblies that are duplicated in SERP and we want SERP's to be used
+ referenceAssemblies.AddRange(ComputeManagedAssemblies.GetManagedAssembliesInFolderNoSimpleNameDuplicates(simpleNames, options.CoreRootDirectory.FullName, "System.*.dll"));
+ referenceAssemblies.AddRange(ComputeManagedAssemblies.GetManagedAssembliesInFolderNoSimpleNameDuplicates(simpleNames, options.CoreRootDirectory.FullName, "Microsoft.*.dll"));
+ referenceAssemblies.Add(Path.Combine(options.CoreRootDirectory.FullName, "mscorlib.dll"));
+ referenceAssemblies.Add(Path.Combine(options.CoreRootDirectory.FullName, "netstandard.dll"));
+
+ List<ProcessInfo> fileCompilations = new List<ProcessInfo>();
+ if (options.Composite)
+ {
+ string serpDll = Path.Combine(binDir, "Serp.dll");
+ var runner = new CpaotRunner(options, referenceAssemblies);
+ var compilationProcess = new ProcessInfo(new CompilationProcessConstructor(runner, Path.ChangeExtension(serpDll, ".ni.dll"), referenceAssemblies));
+ fileCompilations.Add(compilationProcess);
+ }
+ else
+ {
+ var runner = new CpaotRunner(options, referenceAssemblies);
+ foreach (string assemblyName in binFiles)
+ {
+ var compilationProcess = new ProcessInfo(new CompilationProcessConstructor(runner, Path.ChangeExtension(assemblyName, ".ni.dll"), new string[] {assemblyName}));
+ fileCompilations.Add(compilationProcess);
+ }
+ }
+
+ ParallelRunner.Run(fileCompilations, options.DegreeOfParallelism);
+
+ bool success = true;
+ int compilationFailures = 0;
+ foreach (var compilationProcess in fileCompilations)
+ {
+ if (!compilationProcess.Succeeded)
+ {
+ success = false;
+ compilationFailures++;
+
+ Console.WriteLine($"Failed compiling {compilationProcess.Parameters.OutputFileName}");
+ }
+ }
+
+ Console.WriteLine("Serp Compilation Results");
+ Console.WriteLine($"Total compilations: {fileCompilations.Count}");
+ Console.WriteLine($"Compilation failures: {compilationFailures}");
+
+ return success ? 0 : 1;
+ }
+
+ private static bool ShouldInclude(string file)
+ {
+ if (!string.IsNullOrEmpty(file))
+ {
+ if (file.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase) || file.EndsWith(".ni.exe", StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+ if (file.EndsWith("Shared.Exports.dll", StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ if (file.EndsWith(".parallax.dll", StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+ if (!file.EndsWith("Exports.dll", StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static IEnumerable<string> ResolveReferences(IEnumerable<string> folders)
+ {
+ foreach (string referenceFolder in folders)
+ {
+ foreach (string reference in ComputeManagedAssemblies.GetManagedAssembliesInFolder(referenceFolder))
+ {
+ if (reference.EndsWith(".ni.dll"))
+ continue;
+ yield return reference;
+ }
+ }
+ }
+ }
+}
public const int R2RDumpTimeoutMilliseconds = 60 * 1000;
protected readonly BuildOptions _options;
- protected readonly IEnumerable<string> _referenceFolders;
-
- public CompilerRunner(BuildOptions options, IEnumerable<string> referenceFolders)
+ protected readonly List<string> _referenceFolders = new List<string>();
+ public CompilerRunner(BuildOptions options, IEnumerable<string> references)
{
_options = options;
- _referenceFolders = referenceFolders;
+
+ foreach (var reference in references)
+ {
+ if (Directory.Exists(reference))
+ {
+ _referenceFolders.Add(reference);
+ }
+ }
}
public IEnumerable<string> ReferenceFolders => _referenceFolders;
class ComputeManagedAssemblies
{
- public static IEnumerable<string> GetManagedAssembliesInFolder(string folder)
+ public static IEnumerable<string> GetManagedAssembliesInFolder(string folder, string fileNamePattern = "*.*")
{
- foreach (string file in Directory.EnumerateFiles(folder))
+ foreach (var file in Directory.GetFiles(folder, fileNamePattern, SearchOption.TopDirectoryOnly))
{
if (IsManaged(file))
{
}
}
+ /// <summary>
+ /// Returns a list of assemblies in "folder" whose simple name does not exist in "simpleNames". Each discovered
+ /// assembly is added to "simpleNames" to build a list without duplicates. When collecting multiple folders of assemblies
+ /// consider the call order for this function so duplicates are ignored as expected. For exmaple, CORE_ROOT's S.P.CoreLib
+ /// should normally win.
+ /// </summary>
+ /// <param name="simpleNames">Used to keep track of which simple names were used across multiple invocations of this method</param>
+ public static IEnumerable<string> GetManagedAssembliesInFolderNoSimpleNameDuplicates(HashSet<string> simpleNames, string folder, string fileNamePattern = "*.*")
+ {
+ foreach (var file in GetManagedAssembliesInFolder(folder, fileNamePattern))
+ {
+ var simpleName = Path.GetFileNameWithoutExtension(file);
+ if (!simpleNames.Contains(simpleName))
+ {
+ yield return file;
+ simpleNames.Add(simpleName);
+ }
+ }
+ }
+
static ConcurrentDictionary<string, bool> _isManagedCache = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
public static bool IsManaged(string file)
protected override string CompilerRelativePath => "";
protected override string CompilerFileName => "corerun".AppendOSExeSuffix();
+ protected readonly List<string> _referenceFiles = new List<string>();
private string Crossgen2Path => Path.Combine(_options.CoreRootDirectory.FullName, "crossgen2", "crossgen2.dll");
- public CpaotRunner(BuildOptions options, IEnumerable<string> referencePaths)
- : base(options, referencePaths)
+ public CpaotRunner(BuildOptions options, IEnumerable<string> references)
+ : base(options, references)
{
+ // Some scenarios are easier to express when we give Crossgen2 a list of reference assemblies instead of directories,
+ // so allow an override here.
+ foreach (var reference in references)
+ {
+ if (File.Exists(reference))
+ {
+ if (_referenceFolders.Count > 0)
+ {
+ // There's nothing wrong with this per se, but none of our current scenarios need it, so this is
+ // just a consistency check.
+ throw new ArgumentException($"A mix of files and directories was found in {references}");
+ }
+ _referenceFiles.Add(reference);
+ }
+ }
+
// Set R2RTest parallelism to a low enough value that ensures that each Crossgen2 invocation gets to use its parallelism
if (options.DegreeOfParallelism == 0)
options.DegreeOfParallelism = 2;
}
}
- StringComparer pathComparer = (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
- HashSet<string> uniqueFolders = new HashSet<string>(pathComparer);
-
- foreach (string assemblyFileName in assemblyFileNames)
+ if (_referenceFiles.Count == 0)
{
- uniqueFolders.Add(Path.GetDirectoryName(assemblyFileName));
- }
+ // Use reference folders and find the managed assemblies we want to reference from them.
+ // This is the standard path when we want to compile folders and compare crossgen1 and crossgen2.
+ StringComparer pathComparer = PathExtensions.OSPathCaseComparer;
+ HashSet<string> uniqueFolders = new HashSet<string>(pathComparer);
- uniqueFolders.UnionWith(_referenceFolders);
- uniqueFolders.Remove(frameworkFolder);
+ foreach (string assemblyFileName in assemblyFileNames)
+ {
+ uniqueFolders.Add(Path.GetDirectoryName(assemblyFileName));
+ }
- foreach (string reference in ResolveReferences(uniqueFolders, _options.Composite ? 'u' : 'r'))
+ uniqueFolders.UnionWith(_referenceFolders);
+ uniqueFolders.Remove(frameworkFolder);
+
+ foreach (string reference in ResolveReferences(uniqueFolders, _options.Composite ? 'u' : 'r'))
+ {
+ yield return reference;
+ }
+ }
+ else
{
- yield return reference;
+ // Use an explicit set of reference assemblies.
+ // This is useful for crossgen2-specific scenarios since crossgen2 expects a list of files unlike crossgen1
+ foreach (var reference in _referenceFiles)
+ {
+ yield return (_options.Composite ? "-u:" : "-r:") + reference;
+ }
}
}
return false;
}
}
+
+ public static StringComparer OSPathCaseComparer => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;
}