diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/PrepareForReadyToRunCompilation.cs b/src/Tasks/Microsoft.NET.Build.Tasks/PrepareForReadyToRunCompilation.cs index ef751b29fd51..742ccafa67fb 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/PrepareForReadyToRunCompilation.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/PrepareForReadyToRunCompilation.cs @@ -66,6 +66,30 @@ public class PrepareForReadyToRunCompilation : TaskBase private List _r2rCompositeReferences = new List(); private List _r2rCompositeInput = new List(); + private bool IsTargetWindows + { + get + { + // Crossgen2 V6 and above always has TargetOS metadata available + if (ReadyToRunUseCrossgen2 && !string.IsNullOrEmpty(Crossgen2Tool.GetMetadata(MetadataKeys.TargetOS))) + return Crossgen2Tool.GetMetadata(MetadataKeys.TargetOS) == "windows"; + else + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + } + } + + private bool IsTargetLinux + { + get + { + // Crossgen2 V6 and above always has TargetOS metadata available + if (ReadyToRunUseCrossgen2 && !string.IsNullOrEmpty(Crossgen2Tool.GetMetadata(MetadataKeys.TargetOS))) + return Crossgen2Tool.GetMetadata(MetadataKeys.TargetOS) == "linux"; + else + return RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + } + } + protected override void ExecuteCore() { if (ReadyToRunUseCrossgen2) @@ -140,13 +164,13 @@ private void ProcessInputFileList( if (EmitSymbols) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && hasValidDiaSymReaderLib) + if (IsTargetWindows && hasValidDiaSymReaderLib) { outputPDBImage = Path.ChangeExtension(outputR2RImage, "ni.pdb"); outputPDBImageRelativePath = Path.ChangeExtension(outputR2RImageRelativePath, "ni.pdb"); crossgen1CreatePDBCommand = $"/CreatePDB \"{Path.GetDirectoryName(outputPDBImage)}\""; } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + else if (IsTargetLinux) { string perfmapExtension; if (ReadyToRunUseCrossgen2 && !_crossgen2IsVersion5 && _perfmapFormatVersion >= 1) @@ -246,12 +270,12 @@ private void ProcessInputFileList( { string compositePDBImage = null; string compositePDBRelativePath = null; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && hasValidDiaSymReaderLib) + if (IsTargetWindows && hasValidDiaSymReaderLib) { compositePDBImage = Path.ChangeExtension(compositeR2RImage, ".ni.pdb"); compositePDBRelativePath = Path.ChangeExtension(compositeR2RImageRelativePath, ".ni.pdb"); } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + else if (IsTargetLinux) { string perfmapExtension = (_perfmapFormatVersion >= 1 ? ".ni.r2rmap" : ".ni.{composite}.map"); compositePDBImage = Path.ChangeExtension(compositeR2RImage, perfmapExtension); diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/RunReadyToRunCompiler.cs b/src/Tasks/Microsoft.NET.Build.Tasks/RunReadyToRunCompiler.cs index ad754ad564f0..e5f38770d8e9 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/RunReadyToRunCompiler.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/RunReadyToRunCompiler.cs @@ -318,7 +318,7 @@ private string GenerateCrossgen2ResponseFile() // 5.0 Crossgen2 doesn't support PDB generation. if (!Crossgen2IsVersion5 && _emitSymbols) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (Crossgen2Tool.GetMetadata(MetadataKeys.TargetOS) == "windows") { result.AppendLine("--pdb"); result.AppendLine($"--pdb-path:{Path.GetDirectoryName(_outputPDBImage)}"); diff --git a/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishASingleFileApp.cs b/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishASingleFileApp.cs index 89e5f195ba55..6192ba5a6b08 100644 --- a/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishASingleFileApp.cs +++ b/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishASingleFileApp.cs @@ -451,7 +451,7 @@ public void It_excludes_ni_pdbs_from_single_file() var intermediateDirectory = publishCommand.GetIntermediateDirectory(targetFramework, runtimeIdentifier: RuntimeInformation.RuntimeIdentifier); var mainProjectDll = Path.Combine(intermediateDirectory.FullName, $"{TestProjectName}.dll"); - var niPdbFile = GivenThatWeWantToPublishReadyToRun.GetPDBFileName(mainProjectDll, framework); + var niPdbFile = GivenThatWeWantToPublishReadyToRun.GetPDBFileName(mainProjectDll, framework, RuntimeInformation.RuntimeIdentifier); string[] expectedFiles = { SingleFile, PdbFile, niPdbFile }; GetPublishDirectory(publishCommand) diff --git a/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishReadyToRun.cs b/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishReadyToRun.cs index 1605332e462c..2cede7400807 100644 --- a/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishReadyToRun.cs +++ b/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishReadyToRun.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Runtime.CompilerServices; @@ -86,8 +87,8 @@ public void It_creates_readytorun_images_for_all_assemblies_except_excluded_ones NuGetFramework framework = NuGetFramework.Parse(targetFramework); publishDirectory.Should().NotHaveFiles(new[] { - GetPDBFileName(mainProjectDll, framework), - GetPDBFileName(classLibDll, framework), + GetPDBFileName(mainProjectDll, framework, testProject.RuntimeIdentifier), + GetPDBFileName(classLibDll, framework, testProject.RuntimeIdentifier), }); } @@ -112,7 +113,7 @@ public void It_creates_readytorun_symbols_when_switch_is_used(string targetFrame [InlineData("net6.0")] public void It_supports_framework_dependent_publishing(string targetFramework) { - TestProjectPublishing_Internal("FrameworkDependent", targetFramework, isSelfContained: false, emitNativeSymbols:true, identifier: targetFramework); + TestProjectPublishing_Internal("FrameworkDependent", targetFramework, isSelfContained: false, composite: false, emitNativeSymbols:true, identifier: targetFramework); } [Theory] @@ -199,7 +200,7 @@ public void It_warns_when_targetting_netcoreapp_2_x_readytorun() [InlineData("net6.0")] public void It_can_publish_readytorun_for_library_projects(string targetFramework) { - TestProjectPublishing_Internal("LibraryProject1", targetFramework, isSelfContained: false, makeExeProject: false, identifier: targetFramework); + TestProjectPublishing_Internal("LibraryProject1", targetFramework, isSelfContained: false, composite: false, makeExeProject: false, identifier: targetFramework); } [RequiresMSBuildVersionTheory("17.0.0.32901")] @@ -208,7 +209,7 @@ public void It_can_publish_readytorun_for_library_projects(string targetFramewor [InlineData("net6.0")] public void It_can_publish_readytorun_for_selfcontained_library_projects(string targetFramework) { - TestProjectPublishing_Internal("LibraryProject2", targetFramework, isSelfContained:true, makeExeProject: false, identifier: targetFramework); + TestProjectPublishing_Internal("LibraryProject2", targetFramework, isSelfContained:true, composite: true, makeExeProject: false, identifier: targetFramework); } [RequiresMSBuildVersionTheory("17.0.0.32901")] @@ -264,6 +265,103 @@ public void It_supports_libraries_when_using_crossgen2(string targetFramework) publishCommand.Execute().Should().Pass(); } + [RequiresMSBuildVersionTheory("17.0.0.32901")] + [InlineData("net6.0", "linux-x64", "windows,linux,osx", "X64,Arm64", "_", "_")] + [InlineData("net6.0", "linux-x64", "windows,linux,osx", "X64,Arm64", "composite", "selfcontained")] // Composite in .NET 6.0 is only supported for self-contained builds + // In .NET 6.0 building targetting Windows on linux or osx doesn't support emitting native symbols. + [InlineData("net6.0", "win-x64", "windows", "X64,Arm64", "composite", "selfcontained")] // Composite in .NET 6.0 is only supported for self-contained builds + [InlineData("net6.0", "osx-arm64", "windows,linux,osx", "X64,Arm64", "_", "_")] + // In .NET 6.0 building targetting Windows on linux or osx doesn't support emitting native symbols. + [InlineData("net6.0", "win-x86", "windows", "X86,X64,Arm64,Arm", "_", "_")] + public void It_supports_crossos_arch_compilation(string targetFramework, string runtimeIdentifier, string sdkSupportedOs, string sdkSupportedArch, string composite, string selfcontained) + { + var projectName = $"CrossArchOs{targetFramework}{runtimeIdentifier.Replace("-",".")}{composite}{selfcontained}"; + string sdkOs = "NOTHING"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + sdkOs = "linux"; + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + sdkOs = "windows"; + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + sdkOs = "osx"; + } + + Assert.NotEqual("NOTHING", sdkOs); // We should know which OS we are running on + Log.WriteLine($"sdkOs = {sdkOs}"); + if (!sdkSupportedOs.Contains(sdkOs)) + { + Log.WriteLine("Running test on OS that doesn't support this cross platform build"); + return; + } + + string sdkArch = RuntimeInformation.ProcessArchitecture.ToString(); + Log.WriteLine($"sdkArch = {sdkArch}"); + Assert.Contains(sdkArch, new string[]{"Arm", "Arm64", "X64", "X86"}); // Assert that the Architecture in use is a known architecture + if (!sdkSupportedArch.Split(',').Contains(sdkArch)) + { + Log.WriteLine("Running test on processor architecture that doesn't support this cross platform build"); + return; + } + + TestProjectPublishing_Internal(projectName, targetFramework, isSelfContained: selfcontained == "selfcontained", emitNativeSymbols: true, useCrossgen2: true, composite: composite == "composite", identifier: targetFramework, runtimeIdentifier: runtimeIdentifier); + } + + private enum TargetOSEnum + { + Windows, + Linux, + OsX + } + + private static TargetOSEnum GetTargetOS(string runtimeIdentifier) + { + if (runtimeIdentifier.Contains("osx")) + { + return TargetOSEnum.OsX; + } + else if (runtimeIdentifier.Contains("win")) + { + return TargetOSEnum.Windows; + } + else if (runtimeIdentifier.Contains("linux") || + runtimeIdentifier.Contains("ubuntu") || + runtimeIdentifier.Contains("alpine") || + runtimeIdentifier.Contains("android") || + runtimeIdentifier.Contains("centos") || + runtimeIdentifier.Contains("debian") || + runtimeIdentifier.Contains("fedora") || + runtimeIdentifier.Contains("gentoo") || + runtimeIdentifier.Contains("suse") || + runtimeIdentifier.Contains("rhel") || + runtimeIdentifier.Contains("sles") || + runtimeIdentifier.Contains("tizen")) + { + return TargetOSEnum.Linux; + } + + Assert.True(false, $"{runtimeIdentifier} could not be converted into a known OS type. Adjust the if statement above until this does not happen"); + return TargetOSEnum.Windows; + } + + private static bool IsTargetOsOsX(string runtimeIdentifier) + { + return GetTargetOS(runtimeIdentifier) == TargetOSEnum.OsX; + } + + private static bool IsTargetOsWindows(string runtimeIdentifier) + { + return GetTargetOS(runtimeIdentifier) == TargetOSEnum.Windows; + } + + private static bool IsTargetOsLinux(string runtimeIdentifier) + { + return GetTargetOS(runtimeIdentifier) == TargetOSEnum.Linux; + } + private void TestProjectPublishing_Internal(string projectName, string targetFramework, bool makeExeProject = true, @@ -272,13 +370,15 @@ private void TestProjectPublishing_Internal(string projectName, bool useCrossgen2 = false, bool composite = true, [CallerMemberName] string callingMethod = "", - string identifier = null) + string identifier = null, + string runtimeIdentifier = null) { var testProject = CreateTestProjectForR2RTesting( targetFramework, projectName, "ClassLib", - isExeProject: makeExeProject); + isExeProject: makeExeProject, + runtimeIdentifier: runtimeIdentifier); testProject.AdditionalProperties["PublishReadyToRun"] = "True"; testProject.AdditionalProperties["PublishReadyToRunEmitSymbols"] = emitNativeSymbols ? "True" : "False"; @@ -307,18 +407,34 @@ private void TestProjectPublishing_Internal(string projectName, else publishDirectory.Should().NotHaveFile("System.Private.CoreLib.dll"); - if (emitNativeSymbols && !RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + if (emitNativeSymbols && !IsTargetOsOsX(testProject.RuntimeIdentifier)) { NuGetFramework framework = NuGetFramework.Parse(targetFramework); + Log.WriteLine("Checking for symbol files"); + IEnumerable pdbFiles; - publishDirectory.Should().HaveFiles(new[] { - GetPDBFileName(mainProjectDll, framework), - GetPDBFileName(classLibDll, framework), - }); + if (composite) + { + pdbFiles = new[] { GetPDBFileName(Path.ChangeExtension(mainProjectDll, "r2r.dll"), framework, testProject.RuntimeIdentifier) }; + } + else + { + pdbFiles = new[] { + GetPDBFileName(mainProjectDll, framework, testProject.RuntimeIdentifier), + GetPDBFileName(classLibDll, framework, testProject.RuntimeIdentifier), + }; + } + + foreach (string s in pdbFiles) + { + Log.WriteLine($"{publishDirectory.FullName} {s}"); + } + + publishDirectory.Should().HaveFiles(pdbFiles); } } - private TestProject CreateTestProjectForR2RTesting(string targetFramework, string mainProjectName, string referenceProjectName, bool isExeProject = true) + private TestProject CreateTestProjectForR2RTesting(string targetFramework, string mainProjectName, string referenceProjectName, bool isExeProject = true, string runtimeIdentifier = null) { var referenceProject = new TestProject() { @@ -340,7 +456,7 @@ public string Func() Name = mainProjectName, TargetFrameworks = targetFramework, IsExe = isExeProject, - RuntimeIdentifier = EnvironmentInfo.GetCompatibleRid(targetFramework), + RuntimeIdentifier = runtimeIdentifier ?? EnvironmentInfo.GetCompatibleRid(targetFramework), ReferencedProjects = { referenceProject }, }; testProject.SourceFiles[$"{mainProjectName}.cs"] = @" @@ -356,14 +472,14 @@ public static void Main() return testProject; } - public static string GetPDBFileName(string assemblyFile, NuGetFramework framework) + public static string GetPDBFileName(string assemblyFile, NuGetFramework framework, string runtimeIdentifier) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (IsTargetOsWindows(runtimeIdentifier)) { return Path.GetFileName(Path.ChangeExtension(assemblyFile, "ni.pdb")); } - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + if (IsTargetOsLinux(runtimeIdentifier)) { if (framework.Version.Major >= 6) {