using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.DotNet.InternalAbstractions; using static Microsoft.DotNet.Cli.Build.Framework.BuildHelpers; namespace Microsoft.DotNet.Cli.Build { public class Crossgen { private string _coreClrVersion; private string _crossGenPath; private static readonly string[] s_excludedLibraries = { "mscorlib.dll", "mscorlib.ni.dll", "System.Private.CoreLib", "System.Private.CoreLib.ni.dll" }; // This is not always correct. The version of crossgen we need to pick up is whatever one was restored as part // of the Microsoft.NETCore.Runtime.CoreCLR package that is part of the shared library. For now, the version hardcoded // in CompileTargets and the one in the shared library project.json match and are updated in lock step, but long term // we need to be able to look at the project.lock.json file and figure out what version of Microsoft.NETCore.Runtime.CoreCLR // was used, and then select that version. public Crossgen(string coreClrVersion) { _coreClrVersion = coreClrVersion; _crossGenPath = GetCrossgenPathForVersion(); } private string GetCrossgenPathForVersion() { var crossgenPackagePath = GetCrossGenPackagePathForVersion(); if (crossgenPackagePath == null) { return null; } return Path.Combine( crossgenPackagePath, "tools", $"crossgen{Constants.ExeSuffix}"); } private string GetLibCLRJitPathForVersion() { var coreclrRid = GetCoreCLRRid(); var crossgenPackagePath = GetCrossGenPackagePathForVersion(); if (crossgenPackagePath == null) { return null; } return Path.Combine( crossgenPackagePath, "runtimes", coreclrRid, "native", $"{Constants.DynamicLibPrefix}clrjit{Constants.DynamicLibSuffix}"); } private string GetCrossGenPackagePathForVersion() { string coreclrRid = GetCoreCLRRid(); if (coreclrRid == null) { return null; } string packageId = $"runtime.{coreclrRid}.Microsoft.NETCore.Runtime.CoreCLR"; return Path.Combine( Dirs.NuGetPackages, packageId, _coreClrVersion); } private string GetCoreCLRRid() { string rid = null; if (CurrentPlatform.IsWindows) { var arch = RuntimeEnvironment.RuntimeArchitecture; rid = $"win7-{arch}"; } else if (CurrentPlatform.IsUbuntu) { rid = $"ubuntu.{RuntimeEnvironment.OperatingSystemVersion}-x64"; } else if (CurrentPlatform.IsCentOS || CurrentPlatform.IsRHEL) { // CentOS runtime is in the runtime.rhel.7-x64... package. rid = "rhel.7-x64"; } else if (CurrentPlatform.IsOSX) { rid = "osx.10.10-x64"; } else if (CurrentPlatform.IsDebian) { rid = "debian.8-x64"; } else if (CurrentPlatform.IsFedora) { rid = $"fedora.{RuntimeEnvironment.OperatingSystemVersion}-x64"; } else if (CurrentPlatform.IsOpenSuse) { rid = $"opensuse.{RuntimeEnvironment.OperatingSystemVersion}-x64"; } return rid; } public void CrossgenDirectory(string sharedFxPath, string pathToAssemblies) { // Check if we need to skip crossgen if (string.Equals(Environment.GetEnvironmentVariable("DISABLE_CROSSGEN"), "1")) { var originalColor = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("Skipping crossgen for because DISABLE_CROSSGEN is set to 1"); Console.ForegroundColor = originalColor; return; } // HACK // The input directory can be a portable FAT app (example the CLI itself). // In that case there can be RID specific managed dependencies which are not right next to the app binary (example System.Diagnostics.TraceSource). // We need those dependencies during crossgen. For now we just pass all subdirectories of the input directory as input to crossgen. // The right fix - // If the assembly has deps.json then parse the json file to get all the dependencies, pass these dependencies as input to crossgen. // else pass the current directory of assembly as input to crossgen. var addtionalPaths = Directory.GetDirectories(pathToAssemblies, "*", SearchOption.AllDirectories).ToList(); var paths = new List() { sharedFxPath, pathToAssemblies }; paths.AddRange(addtionalPaths); var platformAssembliesPaths = string.Join(Path.PathSeparator.ToString(), paths.Distinct()); var env = new Dictionary() { // disable partial ngen { "COMPlus_PartialNGen", "0" } }; foreach (var file in Directory.GetFiles(pathToAssemblies)) { string fileName = Path.GetFileName(file); if (s_excludedLibraries.Any(lib => String.Equals(lib, fileName, StringComparison.OrdinalIgnoreCase)) || !PEUtils.HasMetadata(file)) { continue; } string tempPathName = Path.ChangeExtension(file, "readytorun"); IList crossgenArgs = new List { "-readytorun", "-in", file, "-out", tempPathName, "-platform_assemblies_paths", platformAssembliesPaths }; if (CurrentPlatform.IsUnix) { crossgenArgs.Add("-JITPath"); crossgenArgs.Add(GetLibCLRJitPathForVersion()); } ExecSilent(_crossGenPath, crossgenArgs, env); File.Copy(tempPathName, file, overwrite: true); File.Delete(tempPathName); } } } }