diff --git a/scripts/dotnet-cli-build/CompileTargets.cs b/scripts/dotnet-cli-build/CompileTargets.cs index 60136fe78..ae3393bfa 100644 --- a/scripts/dotnet-cli-build/CompileTargets.cs +++ b/scripts/dotnet-cli-build/CompileTargets.cs @@ -37,6 +37,8 @@ namespace Microsoft.DotNet.Cli.Build public const string SharedFrameworkName = "Microsoft.NETCore.App"; + public static Crossgen CrossgenUtil = new Crossgen(CoreCLRVersion); + private static string CoreHostBaseName => $"corehost{Constants.ExeSuffix}"; private static string DotnetHostFxrBaseName => $"{Constants.DynamicLibPrefix}hostfxr{Constants.DynamicLibSuffix}"; private static string HostPolicyBaseName => $"{Constants.DynamicLibPrefix}hostpolicy{Constants.DynamicLibSuffix}"; @@ -343,7 +345,7 @@ namespace Microsoft.DotNet.Cli.Build } string SharedFrameworkSourceRoot = GenerateSharedFrameworkProject(c, SharedFrameworkTemplateSourceRoot, sharedFrameworkRid); - + dotnetCli.Restore("--verbosity", "verbose", "--disable-parallel", "--infer-runtimes", "--fallbacksource", Dirs.Corehost) .WorkingDirectory(SharedFrameworkSourceRoot) .Execute() @@ -430,7 +432,7 @@ namespace Microsoft.DotNet.Cli.Build File.Delete(Path.Combine(SharedFrameworkNameAndVersionRoot, "mscorlib.dll")); } - CrossgenSharedFx(c, SharedFrameworkNameAndVersionRoot); + CrossgenUtil.CrossgenDirectory(c, SharedFrameworkNameAndVersionRoot); // Generate .version file for sharedfx var version = SharedFrameworkNugetVersion; @@ -530,6 +532,8 @@ namespace Microsoft.DotNet.Cli.Build File.Delete(compilersDeps); File.Delete(compilersRuntimeConfig); + CrossgenUtil.CrossgenDirectory(c, outputDir); + // Generate .version file var version = buildVersion.NuGetVersion; var content = $@"{c.BuildContext["CommitHash"]}{Environment.NewLine}{version}{Environment.NewLine}"; @@ -538,41 +542,6 @@ namespace Microsoft.DotNet.Cli.Build return c.Success(); } - public static BuildTargetResult CrossgenSharedFx(BuildTargetContext c, string pathToAssemblies) - { - // Check if we need to skip crossgen - if (string.Equals(Environment.GetEnvironmentVariable("DONT_CROSSGEN_SHAREDFRAMEWORK"), "1")) - { - c.Warn("Skipping crossgen for SharedFx because DONT_CROSSGEN_SHAREDFRAMEWORK is set to 1"); - return c.Success(); - } - - foreach (var file in Directory.GetFiles(pathToAssemblies)) - { - string fileName = Path.GetFileName(file); - - if (fileName == "mscorlib.dll" || fileName == "mscorlib.ni.dll" || !HasMetadata(file)) - { - continue; - } - - string tempPathName = Path.ChangeExtension(file, "readytorun"); - - // 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. - ExecSilent(Crossgen.GetCrossgenPathForVersion(CoreCLRVersion), - "-readytorun", "-in", file, "-out", tempPathName, "-platform_assemblies_paths", pathToAssemblies); - - File.Delete(file); - File.Move(tempPathName, file); - } - - return c.Success(); - } - private static void ChangeEntryPointLibraryName(string depsFile, string newName) { JToken deps; @@ -603,7 +572,7 @@ namespace Microsoft.DotNet.Cli.Build library.Replace(new JProperty(newName + '/' + version, library.Value)); } using (var file = File.CreateText(depsFile)) - using (var writer = new JsonTextWriter(file) { Formatting = Formatting.Indented}) + using (var writer = new JsonTextWriter(file) { Formatting = Formatting.Indented }) { deps.WriteTo(writer); } @@ -615,22 +584,5 @@ namespace Microsoft.DotNet.Cli.Build File.Delete(Path.Combine(path, $"{name}.dll")); File.Delete(Path.Combine(path, $"{name}.pdb")); } - - private static bool HasMetadata(string pathToFile) - { - try - { - using (var inStream = File.OpenRead(pathToFile)) - { - using (var peReader = new PEReader(inStream)) - { - return peReader.HasMetadata; - } - } - } - catch (BadImageFormatException) { } - - return false; - } } } diff --git a/scripts/dotnet-cli-build/Utils/Crossgen.cs b/scripts/dotnet-cli-build/Utils/Crossgen.cs index 37cf836aa..f7dd9417a 100644 --- a/scripts/dotnet-cli-build/Utils/Crossgen.cs +++ b/scripts/dotnet-cli-build/Utils/Crossgen.cs @@ -1,15 +1,32 @@ +using System; +using System.Collections.Generic; using System.IO; using System.Linq; -using System; using System.Runtime.InteropServices; using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.Extensions.PlatformAbstractions; +using static Microsoft.DotNet.Cli.Build.Framework.BuildHelpers; + namespace Microsoft.DotNet.Cli.Build { - internal static class Crossgen + public class Crossgen { - public static string GetCrossgenPathForVersion(string coreClrVersion) + private string _coreClrVersion; + private string _crossGenPath; + + // 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() { string arch = PlatformServices.Default.Runtime.RuntimeArchitecture; string packageId; @@ -42,9 +59,51 @@ namespace Microsoft.DotNet.Cli.Build return Path.Combine( Dirs.NuGetPackages, packageId, - coreClrVersion, + _coreClrVersion, "tools", $"crossgen{Constants.ExeSuffix}"); } + + public void CrossgenDirectory(BuildTargetContext c, string pathToAssemblies) + { + // Check if we need to skip crossgen + if (string.Equals(Environment.GetEnvironmentVariable("DISABLE_CROSSGEN"), "1")) + { + c.Warn("Skipping crossgen for because DISABLE_CROSSGEN is set to 1"); + return; + } + + foreach (var file in Directory.GetFiles(pathToAssemblies)) + { + string fileName = Path.GetFileName(file); + + if (fileName == "mscorlib.dll" || fileName == "mscorlib.ni.dll" || !PEUtils.HasMetadata(file)) + { + continue; + } + + string tempPathName = Path.ChangeExtension(file, "readytorun"); + string sharedFxPath = c.BuildContext.Get("SharedFrameworkPath"); + + // 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. + string addtionalPaths = string.Join(";", Directory.GetDirectories(pathToAssemblies, "*", SearchOption.AllDirectories)); + + IList crossgenArgs = new List { + "-readytorun", "-in", file, "-out", tempPathName, + "-platform_assemblies_paths", $"{sharedFxPath};{pathToAssemblies};{addtionalPaths}" + }; + + ExecSilent(_crossGenPath, crossgenArgs); + + File.Delete(file); + File.Move(tempPathName, file); + } + } } } diff --git a/scripts/dotnet-cli-build/Utils/PEUtils.cs b/scripts/dotnet-cli-build/Utils/PEUtils.cs new file mode 100644 index 000000000..ad702b51a --- /dev/null +++ b/scripts/dotnet-cli-build/Utils/PEUtils.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection.PortableExecutable; + +namespace Microsoft.DotNet.Cli.Build +{ + public class PEUtils + { + public static bool HasMetadata(string pathToFile) + { + try + { + using (var inStream = File.OpenRead(pathToFile)) + { + using (var peReader = new PEReader(inStream)) + { + return peReader.HasMetadata; + } + } + } + catch (BadImageFormatException) { } + + return false; + } + } +}