diff --git a/GitVersion.yml b/GitVersion.yml index b8d28d02e..c02e85371 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -5,7 +5,7 @@ build-metadata-padding: 5 commits-since-version-source-padding: 5 branches: master: - regex: ^(main|version3X)$ + regex: ^(main|version4)$ tag: dev release: tag: pre diff --git a/NUnitConsole.sln b/NUnitConsole.sln index f978a232f..760d8c675 100644 --- a/NUnitConsole.sln +++ b/NUnitConsole.sln @@ -110,6 +110,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cake", "cake", "{9BCA00E2-D072-424B-A6DF-5BECF719C1FB}" ProjectSection(SolutionItems) = preProject cake\constants.cake = cake\constants.cake + cake\dotnet.cake = cake\dotnet.cake cake\header-check.cake = cake\header-check.cake cake\package-checks.cake = cake\package-checks.cake cake\package-definitions.cake = cake\package-definitions.cake @@ -122,6 +123,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cake", "cake", "{9BCA00E2-D EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "msi", "msi", "{0C0D20CE-70CD-4CEF-BE9B-AEB8A2DE9C8A}" ProjectSection(SolutionItems) = preProject + msi\nunit-install.sln = msi\nunit-install.sln msi\resources\nunit.bundle.addins = msi\resources\nunit.bundle.addins EndProjectSection EndProject @@ -151,6 +153,21 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{C5B712 .config\dotnet-tools.json = .config\dotnet-tools.json EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nunit", "nunit", "{93E5CAF4-D5DB-4915-AF0F-908A6043E462}" + ProjectSection(SolutionItems) = preProject + msi\nunit\addin-files.wxi = msi\nunit\addin-files.wxi + msi\nunit\banner.bmp = msi\nunit\banner.bmp + msi\nunit\console-files.wxi = msi\nunit\console-files.wxi + msi\nunit\dialog.bmp = msi\nunit\dialog.bmp + msi\nunit\engine-files.wxi = msi\nunit\engine-files.wxi + msi\nunit\nunit.wixproj = msi\nunit\nunit.wixproj + msi\nunit\nunit.wxs = msi\nunit\nunit.wxs + msi\nunit\runner-directories.wxi = msi\nunit\runner-directories.wxi + msi\nunit\runner-features.wxi = msi\nunit\runner-features.wxi + msi\nunit\utility-files.wxi = msi\nunit\utility-files.wxi + msi\nunit\variables.wxi = msi\nunit\variables.wxi + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -235,6 +252,7 @@ Global {08F8160E-E691-4F07-9F57-EA31B9736429} = {25DA12FE-6209-4524-9A37-8E51F815E198} {50371E48-BEC3-4D53-BD37-F3A6149CFD0D} = {25DA12FE-6209-4524-9A37-8E51F815E198} {C5B7120C-190B-4C38-95CB-83F12799598D} = {49D441DF-39FD-4F4D-AECA-86CF8EFE23AF} + {93E5CAF4-D5DB-4915-AF0F-908A6043E462} = {0C0D20CE-70CD-4CEF-BE9B-AEB8A2DE9C8A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D8E4FC26-5422-4C51-8BBC-D1AC0A578711} diff --git a/build.cake b/build.cake index 2ec9efdc4..814abff27 100644 --- a/build.cake +++ b/build.cake @@ -3,6 +3,7 @@ static string Configuration; Configuration = GetArgument("configuration|c", "Rel static bool NoPush; NoPush = HasArgument("nopush"); #load cake/constants.cake +#load cake/dotnet.cake #load cake/header-check.cake #load cake/package-checks.cake #load cake/test-results.cake diff --git a/cake/dotnet.cake b/cake/dotnet.cake new file mode 100644 index 000000000..0fb801824 --- /dev/null +++ b/cake/dotnet.cake @@ -0,0 +1,88 @@ +public class DotnetInfo +{ + // Experimenting with finding dotnet installs for X64 vs x86 + // This code will end up moved into the engine as well. + + private ICakeContext _context; + private bool _onWindows; + + public DotnetInfo(ICakeContext context) + { + _context = context; + _onWindows = context.IsRunningOnWindows(); + + InstallPath = GetDotnetInstallDirectory(false); + X86InstallPath = GetDotnetInstallDirectory(true); + } + + // NOTES: + // * We don't need an IsInstalled property because our scripts all run under dotnet. + + public bool IsX86Installed => System.IO.Directory.Exists(X86InstallPath) && System.IO.File.Exists(X86Executable); + + public string InstallPath { get; } + public string Executable => InstallPath + "dotnet.exe"; + public List Runtimes { get; } + + public string X86InstallPath { get; } + public string X86Executable => X86InstallPath + "dotnet.exe"; + public List X86Runtimes { get; } + + public void Display() + { + _context.Information($"Install Path: {InstallPath}"); + _context.Information($"Executable: {Executable}"); + _context.Information("Runtimes:"); + foreach (string dir in System.IO.Directory.GetDirectories(System.IO.Path.Combine(InstallPath, "shared"))) + { + string runtime = System.IO.Path.GetFileName(dir); + foreach (string dir2 in System.IO.Directory.GetDirectories(dir)) + { + string version = System.IO.Path.GetFileName(dir2); + _context.Information($" {runtime} {version}"); + } + } + + if (IsX86Installed) + { + _context.Information($"\nX86 Install Path: {X86InstallPath}"); + _context.Information($"X86 Executable: {X86Executable}"); + _context.Information("Runtimes:"); + foreach (var dir in System.IO.Directory.GetDirectories(System.IO.Path.Combine(X86InstallPath, "shared"))) + { + string runtime = System.IO.Path.GetFileName(dir); + foreach (string dir2 in System.IO.Directory.GetDirectories(dir)) + { + string version = System.IO.Path.GetFileName(dir2); + _context.Information($" {runtime} {version}"); + } + } + } + else + _context.Information("\nDotnet X86 is not installed"); + } + + private string GetDotnetInstallDirectory(bool forX86 = false) + { + if (_onWindows) + { + if (forX86) + { + Microsoft.Win32.RegistryKey key = + Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\dotnet\SetUp\InstalledVersions\x86\"); + return (string)key?.GetValue("InstallLocation"); + } + else + { + Microsoft.Win32.RegistryKey key = + Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\dotnet\SetUp\InstalledVersions\x64\sharedHost\"); + return (string)key?.GetValue("Path"); + } + } + else // Assuming linux for now + return "/usr/shared/dotnet/"; + } +} + +// Use this task to verify that the script understands the dotnet environment +Task("DotnetInfo").Does(() => { new DotnetInfo(Context).Display(); }); diff --git a/cake/package-definitions.cake b/cake/package-definitions.cake index 04f431216..2d8b40008 100644 --- a/cake/package-definitions.cake +++ b/cake/package-definitions.cake @@ -28,6 +28,8 @@ public void InitializePackageDefinitions(ICakeContext context) NetCore31Test, Net50Test, Net60Test, + Net70Test, + Net80Test, Net50PlusNet60Test, Net40PlusNet60Test }; @@ -42,6 +44,24 @@ public void InitializePackageDefinitions(ICakeContext context) Net80Test, }; + // TODO: Remove the limitation to Windows + if (IsRunningOnWindows() && dotnetX86Available) + { + StandardRunnerTests.Add(Net60X86Test); + // TODO: Make these tests run on AppVeyor + if (!context.BuildSystem().IsRunningOnAppVeyor) + { + StandardRunnerTests.Add(NetCore31X86Test); + StandardRunnerTests.Add(Net50X86Test); + StandardRunnerTests.Add(Net70X86Test); + StandardRunnerTests.Add(Net80X86Test); + } + // Currently, NetCoreRunner runs tests in process. As a result, + // X86 tests will work in our environment, although uses may run + // it as a tool using the X86 architecture. + } + + AllPackages.AddRange(new PackageDefinition[] { NUnitConsoleNuGetPackage = new NuGetPackage( diff --git a/cake/package-tests.cake b/cake/package-tests.cake index 26eee6af3..2b5413ab2 100644 --- a/cake/package-tests.cake +++ b/cake/package-tests.cake @@ -48,30 +48,60 @@ static PackageTest Net80Test = new PackageTest( "net8.0/mock-assembly.dll", MockAssemblyExpectedResult(1)); +static PackageTest Net80X86Test = new PackageTest( + "Net80X86Test", + "Run mock-assembly-x86.dll under .NET 8.0", + "net8.0/mock-assembly-x86.dll", + MockAssemblyExpectedResult(1)); + static PackageTest Net70Test = new PackageTest( "Net70Test", "Run mock-assembly.dll under .NET 7.0", "net7.0/mock-assembly.dll", MockAssemblyExpectedResult(1)); +static PackageTest Net70X86Test = new PackageTest( + "Net70X86Test", + "Run mock-assembly-x86.dll under .NET 7.0", + "net7.0/mock-assembly-x86.dll", + MockAssemblyExpectedResult(1)); + static PackageTest Net60Test = new PackageTest( "Net60Test", "Run mock-assembly.dll under .NET 6.0", "net6.0/mock-assembly.dll", MockAssemblyExpectedResult(1)); +static PackageTest Net60X86Test = new PackageTest( + "Net60X86Test", + "Run mock-assembly-x86.dll under .NET 6.0", + "net6.0/mock-assembly-x86.dll --trace:Debug", + MockAssemblyExpectedResult(1)); + static PackageTest Net50Test = new PackageTest( "Net50Test", "Run mock-assembly.dll under .NET 5.0", "net5.0/mock-assembly.dll", MockAssemblyExpectedResult(1)); +static PackageTest Net50X86Test = new PackageTest( + "Net50X86Test", + "Run mock-assembly-x86.dll under .NET 5.0", + "net5.0/mock-assembly-x86.dll", + MockAssemblyExpectedResult(1)); + static PackageTest NetCore31Test = new PackageTest( "NetCore31Test", "Run mock-assembly.dll under .NET Core 3.1", "netcoreapp3.1/mock-assembly.dll", MockAssemblyExpectedResult(1)); +static PackageTest NetCore31X86Test = new PackageTest( + "NetCore31X86Test", + "Run mock-assembly-x86.dll under .NET Core 3.1", + "netcoreapp3.1/mock-assembly-x86.dll", + MockAssemblyExpectedResult(1)); + static PackageTest Net50PlusNet60Test = new PackageTest( "Net50PlusNet60Test", "Run mock-assembly under .NET 5.0 and 6.0 together", diff --git a/msi/nunit/engine-files.wxi b/msi/nunit/engine-files.wxi index 6422781b5..2b914f015 100644 --- a/msi/nunit/engine-files.wxi +++ b/msi/nunit/engine-files.wxi @@ -49,6 +49,8 @@ + + @@ -175,24 +177,94 @@ + Source="$(var.InstallImage)bin/agents/net6.0/nunit.engine.api.dll" /> + Source="$(var.InstallImage)bin/agents/net6.0/nunit.engine.api.xml" /> + Source="$(var.InstallImage)bin/agents/net6.0/nunit.engine.core.dll" /> + Source="$(var.InstallImage)bin/agents/net6.0/testcentric.engine.metadata.dll" /> + Source="$(var.InstallImage)bin/agents/net6.0/Microsoft.Extensions.DependencyModel.dll" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/msi/nunit/runner-directories.wxi b/msi/nunit/runner-directories.wxi index 1f4eab58e..906654d74 100644 --- a/msi/nunit/runner-directories.wxi +++ b/msi/nunit/runner-directories.wxi @@ -16,6 +16,8 @@ + + diff --git a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj b/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj index 5abc42dff..34e36d39c 100644 --- a/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj +++ b/src/NUnitEngine/mock-assembly-x86/mock-assembly-x86.csproj @@ -2,7 +2,7 @@ NUnit.Tests - net35;net40;netcoreapp2.1;netcoreapp3.1 + net35;net40;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 true ..\..\nunit.snk x86 diff --git a/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs b/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs index 0e48ebe6d..9f7021092 100644 --- a/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs +++ b/src/NUnitEngine/nunit.engine.api/IRuntimeFrameworkService.cs @@ -16,8 +16,9 @@ public interface IRuntimeFrameworkService /// the string passed as an argument is available. /// /// A string representing a framework, like 'net-4.0' + /// A flag indicating whether the X86 architecture is needed. Defaults to false. /// True if the framework is available, false if unavailable or nonexistent - bool IsAvailable(string framework); + bool IsAvailable(string framework, bool x86); /// /// Selects a target runtime framework for a TestPackage based on diff --git a/src/NUnitEngine/nunit.engine.core.tests/RuntimeFrameworkTests.cs b/src/NUnitEngine/nunit.engine.core.tests/RuntimeFrameworkTests.cs index 3d1b650d5..6dc24c6c1 100644 --- a/src/NUnitEngine/nunit.engine.core.tests/RuntimeFrameworkTests.cs +++ b/src/NUnitEngine/nunit.engine.core.tests/RuntimeFrameworkTests.cs @@ -30,41 +30,42 @@ public void CurrentFrameworkHasBuildSpecified() Assert.That(RuntimeFramework.CurrentFramework.ClrVersion.Build, Is.GreaterThan(0)); } - [Test] - public void CurrentFrameworkMustBeAvailable() - { - var current = RuntimeFramework.CurrentFramework; - Console.WriteLine("Current framework is {0} ({1})", current.DisplayName, current.Id); - Assert.That(current.IsAvailable, "{0} not available", current); - } + // TODO: The commented tests should be in RuntimeFrameworkService Tests + //[Test] + //public void CurrentFrameworkMustBeAvailable() + //{ + // var current = RuntimeFramework.CurrentFramework; + // Console.WriteLine("Current framework is {0} ({1})", current.DisplayName, current.Id); + // Assert.That(current.IsAvailable, "{0} not available", current); + //} - [Test] - public void AvailableFrameworksList() - { - RuntimeFramework[] available = RuntimeFramework.AvailableFrameworks; - Assert.That(RuntimeFramework.AvailableFrameworks.Length, Is.GreaterThan(0) ); - foreach (var framework in RuntimeFramework.AvailableFrameworks) - Console.WriteLine("Available: {0}", framework.DisplayName); - } + //[Test] + //public void AvailableFrameworksList() + //{ + // RuntimeFramework[] available = RuntimeFramework.AvailableFrameworks; + // Assert.That(RuntimeFramework.AvailableFrameworks.Length, Is.GreaterThan(0) ); + // foreach (var framework in RuntimeFramework.AvailableFrameworks) + // Console.WriteLine("Available: {0}", framework.DisplayName); + //} - [Test] - public void AvailableFrameworksList_IncludesCurrentFramework() - { - foreach (var framework in RuntimeFramework.AvailableFrameworks) - if (RuntimeFramework.CurrentFramework.Supports(framework)) - return; + //[Test] + //public void AvailableFrameworksList_IncludesCurrentFramework() + //{ + // foreach (var framework in RuntimeFramework.AvailableFrameworks) + // if (RuntimeFramework.CurrentFramework.Supports(framework)) + // return; - Assert.Fail("CurrentFramework not listed as available"); - } + // Assert.Fail("CurrentFramework not listed as available"); + //} - [Test] - public void AvailableFrameworksList_ContainsNoDuplicates() - { - var names = new List(); - foreach (var framework in RuntimeFramework.AvailableFrameworks) - names.Add(framework.DisplayName); - Assert.That(names, Is.Unique); - } + //[Test] + //public void AvailableFrameworksList_ContainsNoDuplicates() + //{ + // var names = new List(); + // foreach (var framework in RuntimeFramework.AvailableFrameworks) + // names.Add(framework.DisplayName); + // Assert.That(names, Is.Unique); + //} [TestCaseSource(nameof(frameworkData))] public void CanCreateUsingFrameworkVersion(FrameworkData data) diff --git a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs index c3cfc570a..fe9d53336 100644 --- a/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs +++ b/src/NUnitEngine/nunit.engine.core/Internal/TestAssemblyResolver.cs @@ -23,13 +23,17 @@ internal sealed class TestAssemblyResolver : IDisposable private readonly DependencyContext _dependencyContext; private readonly AssemblyLoadContext _loadContext; - private static readonly string INSTALL_DIR = GetDotNetInstallDirectory(); - private static readonly string WINDOWS_DESKTOP_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.WindowsDesktop.App"); - private static readonly string ASP_NET_CORE_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.AspNetCore.App"); + private static readonly string INSTALL_DIR; + private static readonly string WINDOWS_DESKTOP_DIR; + private static readonly string ASP_NET_CORE_DIR; private static readonly List AdditionalFrameworkDirectories; static TestAssemblyResolver() { + INSTALL_DIR = GetDotNetInstallDirectory(); + WINDOWS_DESKTOP_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.WindowsDesktop.App"); + ASP_NET_CORE_DIR = Path.Combine(INSTALL_DIR, "shared", "Microsoft.AspNetCore.App"); + AdditionalFrameworkDirectories = new List(); if (Directory.Exists(WINDOWS_DESKTOP_DIR)) AdditionalFrameworkDirectories.Add(WINDOWS_DESKTOP_DIR); @@ -169,10 +173,16 @@ private static string GetDotNetInstallDirectory() if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // Running on Windows so use registry - RegistryKey key = Environment.Is64BitProcess - ? Registry.LocalMachine.OpenSubKey(@"Software\dotnet\SetUp\InstalledVersions\x64\sharedHost\") - : Registry.LocalMachine.OpenSubKey(@"Software\dotnet\SetUp\InstalledVersions\x86\sharedHost\"); - return (string)key?.GetValue("Path"); + if (Environment.Is64BitProcess) + { + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\dotnet\SetUp\InstalledVersions\x64\sharedHost\"); + return (string)key?.GetValue("Path"); + } + else + { + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\dotnet\SetUp\InstalledVersions\x86\"); + return (string)key?.GetValue("InstallLocation"); + } } else return "/usr/shared/dotnet/"; diff --git a/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs b/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs index c04ed5766..bb1d8b111 100644 --- a/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs +++ b/src/NUnitEngine/nunit.engine.core/RuntimeFramework.cs @@ -98,7 +98,7 @@ public string Id #endregion private static RuntimeFramework _currentFramework; - private static List _availableFrameworks; + //private static List _availableFrameworks; private static readonly string DEFAULT_WINDOWS_MONO_DIR = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Mono"); @@ -314,20 +314,6 @@ public static RuntimeFramework CurrentFramework } } - /// - /// Gets an array of all available frameworks - /// - public static RuntimeFramework[] AvailableFrameworks - { - get - { - if (_availableFrameworks == null) - FindAvailableFrameworks(); - - return _availableFrameworks.ToArray(); - } - } - /// /// The version of Mono in use or null if no Mono runtime /// is available on this machine. @@ -356,25 +342,7 @@ public static string MonoExePath } } - /// - /// Returns true if the current RuntimeFramework is available. - /// In the current implementation, only Mono and Microsoft .NET - /// are supported. - /// - /// True if it's available, false if not - public bool IsAvailable - { - get - { - foreach (RuntimeFramework framework in AvailableFrameworks) - if (framework.Supports(this)) - return true; - - return false; - } - } - - /// + /// /// Parses a string representing a RuntimeFramework. /// The string may be just a RuntimeType name or just /// a Version or a hyphenated RuntimeType-Version or @@ -548,204 +516,6 @@ private static string GetMonoPrefixFromAssembly(Assembly assembly) return prefix; } - - private static void FindAvailableFrameworks() - { - _availableFrameworks = new List(); - - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - _availableFrameworks.AddRange(DotNetFrameworkLocator.FindDotNetFrameworks()); - - FindDefaultMonoFramework(); - FindDotNetCoreFrameworks(); - } - - private static void FindDefaultMonoFramework() - { - if (CurrentFramework.Runtime == RuntimeType.Mono) - UseCurrentMonoFramework(); - else - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - FindBestMonoFrameworkOnWindows(); - } - - private static void UseCurrentMonoFramework() - { - Debug.Assert(CurrentFramework.Runtime == RuntimeType.Mono && MonoPrefix != null && MonoVersion != null); - - // Multiple profiles are no longer supported with Mono 4.0 - if (MonoVersion.Major < 4 && FindAllMonoProfiles() > 0) - return; - - // If Mono 4.0+ or no profiles found, just use current runtime - _availableFrameworks.Add(RuntimeFramework.CurrentFramework); - } - - private static void FindBestMonoFrameworkOnWindows() - { - // First, look for recent frameworks that use the Software\Mono Key - RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Mono"); - - if (key != null && (int)key.GetValue("Installed", 0) == 1) - { - string version = key.GetValue("Version") as string; - MonoPrefix = key.GetValue("SdkInstallRoot") as string; - - if (version != null) - { - MonoVersion = new Version(version); - AddMonoFramework(new Version(4, 5), null); - return; - } - } - - // Some later 3.x Mono releases didn't use the registry - // so check in standard location for them. - if (Directory.Exists(DEFAULT_WINDOWS_MONO_DIR)) - { - MonoPrefix = DEFAULT_WINDOWS_MONO_DIR; - AddMonoFramework(new Version(4, 5), null); - return; - } - - // Look in the Software\Novell key for older versions - key = Registry.LocalMachine.OpenSubKey(@"Software\Novell\Mono"); - if (key != null) - { - string version = key.GetValue("DefaultCLR") as string; - if (version != null) - { - RegistryKey subKey = key.OpenSubKey(version); - if (subKey != null) - { - MonoPrefix = subKey.GetValue("SdkInstallRoot") as string; - MonoVersion = new Version(version); - - FindAllMonoProfiles(); - } - } - } - } - - private static int FindAllMonoProfiles() - { - int count = 0; - - if (MonoPrefix != null) - { - if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/1.0/mscorlib.dll"))) - { - AddMonoFramework(new Version(1, 1, 4322), "1.0"); - count++; - } - - if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/2.0/mscorlib.dll"))) - { - AddMonoFramework(new Version(2, 0), "2.0"); - count++; - } - - if (Directory.Exists(Path.Combine(MonoPrefix, "lib/mono/3.5"))) - { - AddMonoFramework(new Version(3, 5), "3.5"); - count++; - } - - if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/4.0/mscorlib.dll"))) - { - AddMonoFramework(new Version(4, 0), "4.0"); - count++; - } - - if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/4.5/mscorlib.dll"))) - { - AddMonoFramework(new Version(4, 5), "4.5"); - count++; - } - } - - return count; - } - - private static void AddMonoFramework(Version frameworkVersion, string profile) - { - var framework = new RuntimeFramework(RuntimeType.Mono, frameworkVersion) - { - Profile = profile, - DisplayName = MonoVersion != null - ? "Mono " + MonoVersion.ToString() + " - " + profile + " Profile" - : "Mono - " + profile + " Profile" - }; - - _availableFrameworks.Add(framework); - } - - private static void FindDotNetCoreFrameworks() - { - const string WINDOWS_INSTALL_DIR = "C:\\Program Files\\dotnet\\"; - const string LINUX_INSTALL_DIR = "/usr/shared/dotnet/"; - string INSTALL_DIR = Path.DirectorySeparatorChar == '\\' - ? WINDOWS_INSTALL_DIR - : LINUX_INSTALL_DIR; - - if (!Directory.Exists(INSTALL_DIR)) - return; - if (!File.Exists(Path.Combine(INSTALL_DIR, "dotnet.exe"))) - return; - - string runtimeDir = Path.Combine(INSTALL_DIR, Path.Combine("shared", "Microsoft.NETCore.App")); - if (!Directory.Exists(runtimeDir)) - return; - - var dirList = new DirectoryInfo(runtimeDir).GetDirectories(); - var dirNames = new List(); - foreach (var dir in dirList) - dirNames.Add(dir.Name); - var runtimes = GetNetCoreRuntimesFromDirectoryNames(dirNames); - - _availableFrameworks.AddRange(runtimes); - } - - // Deal with oddly named directories, which may sometimes appear when previews are installed - internal static IList GetNetCoreRuntimesFromDirectoryNames(IEnumerable dirNames) - { - const string VERSION_CHARS = ".0123456789"; - var runtimes = new List(); - - foreach (string dirName in dirNames) - { - int len = 0; - foreach (char c in dirName) - { - if (VERSION_CHARS.IndexOf(c) >= 0) - len++; - else - break; - } - - if (len == 0) - continue; - - Version fullVersion = null; - try - { - fullVersion = new Version(dirName.Substring(0, len)); - } - catch - { - continue; - } - - var newVersion = new Version(fullVersion.Major, fullVersion.Minor); - int count = runtimes.Count; - if (count > 0 && runtimes[count - 1].FrameworkVersion == newVersion) - continue; - - runtimes.Add(new RuntimeFramework(RuntimeType.NetCore, newVersion)); - } - - return runtimes; - } } } #endif diff --git a/src/NUnitEngine/nunit.engine.tests/Services/Fakes/FakeRuntimeService.cs b/src/NUnitEngine/nunit.engine.tests/Services/Fakes/FakeRuntimeService.cs index 9b181e991..9d5558e7f 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/Fakes/FakeRuntimeService.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/Fakes/FakeRuntimeService.cs @@ -4,7 +4,7 @@ namespace NUnit.Engine.Services.Tests.Fakes { public class FakeRuntimeService : FakeService, IRuntimeFrameworkService { - bool IRuntimeFrameworkService.IsAvailable(string framework) + bool IRuntimeFrameworkService.IsAvailable(string framework, bool x86) { return true; } diff --git a/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs index 659c378af..d61517e06 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Services/RuntimeFrameworkServiceTests.cs @@ -2,11 +2,8 @@ #if NETFRAMEWORK using System; -using System.Collections.Generic; using System.IO; -using System.Text; using NUnit.Framework; -using NUnit.Tests; namespace NUnit.Engine.Services.Tests { @@ -76,8 +73,6 @@ public void EngineOptionPreferredOverImageTarget(string framework, int majorVers public void RuntimeFrameworkIsSetForSubpackages() { //Runtime Service verifies that requested frameworks are available, therefore this test can only currently be run on platforms with both CLR v2 and v4 available - Assume.That(new RuntimeFramework(RuntimeType.Net, new Version("2.0.50727")), Has.Property(nameof(RuntimeFramework.IsAvailable)).True); - Assume.That(new RuntimeFramework(RuntimeType.Net, new Version("4.0.30319")), Has.Property(nameof(RuntimeFramework.IsAvailable)).True); var topLevelPackage = new TestPackage(new [] {"a.dll", "b.dll"}); diff --git a/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs b/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs index 41734a93d..06e3c989d 100644 --- a/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs +++ b/src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs @@ -376,8 +376,10 @@ private void ValidatePackageSettings() { // Check requested framework is actually available var runtimeService = _services.GetService(); - if (!runtimeService.IsAvailable(frameworkSetting)) - throw new NUnitEngineException(string.Format("The requested framework {0} is unknown or not available.", frameworkSetting)); + if (!runtimeService.IsAvailable(frameworkSetting, runAsX86)) + throw new NUnitEngineException(runAsX86 + ? $"The requested framework {frameworkSetting} is unknown or not available for X86." + : $"The requested framework {frameworkSetting} is unknown or not available."); // If running in process, check requested framework is compatible if (runningInProcess) diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs index 025721c9b..673b76420 100644 --- a/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeFrameworkService.cs @@ -7,6 +7,7 @@ using TestCentric.Metadata; using NUnit.Common; using NUnit.Engine.Internal; +using NUnit.Engine.Services.RuntimeLocators; #if NET20 using FrameworkName = NUnit.Engine.Compatibility.FrameworkName; #endif @@ -17,33 +18,35 @@ public class RuntimeFrameworkService : Service, IRuntimeFrameworkService, IAvail { static readonly Logger log = InternalTrace.GetLogger(typeof(RuntimeFrameworkService)); - // HACK: This line forces RuntimeFramework to initialize the static property - // AvailableFrameworks before it is accessed by multiple threads. See comment - // on RuntimeFramework class for a more detailled explanation. - static readonly RuntimeFramework[] _availableRuntimes = RuntimeFramework.AvailableFrameworks; + private List _availableRuntimes = new List(); + private List _availableX86Runtimes = new List(); /// /// Gets a list of available runtimes. /// - public IList AvailableRuntimes - { - get { return _availableRuntimes; } - } + public IList AvailableRuntimes => _availableRuntimes.ToArray(); + + /// + /// Gets a list of available runtimes. + /// + public IList AvailableX86Runtimes => _availableX86Runtimes.ToArray(); /// /// Returns true if the runtime framework represented by /// the string passed as an argument is available. /// /// A string representing a framework, like 'net-4.0' + /// A flag indicating whether the X86 architecture is needed. Defaults to false. /// True if the framework is available, false if unavailable or nonexistent - public bool IsAvailable(string name) + public bool IsAvailable(string name, bool x86) { Guard.ArgumentNotNullOrEmpty(name, nameof(name)); if (!RuntimeFramework.TryParse(name, out RuntimeFramework requestedFramework)) throw new NUnitEngineException("Invalid or unknown framework requested: " + name); - foreach (var framework in RuntimeFramework.AvailableFrameworks) + var runtimes = x86 ? _availableX86Runtimes : _availableRuntimes; + foreach (var framework in runtimes) if (FrameworksMatch(requestedFramework, framework)) return true; @@ -112,6 +115,7 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) log.Debug("Current framework is " + currentFramework); string frameworkSetting = package.GetSetting(EnginePackageSettings.RequestedRuntimeFramework, ""); + bool runAsX86 = package.GetSetting(EnginePackageSettings.RunAsX86, false); if (frameworkSetting.Length > 0) { @@ -120,7 +124,7 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) log.Debug($"Requested framework for {package.Name} is {requestedFramework}"); - if (!IsAvailable(frameworkSetting)) + if (!IsAvailable(frameworkSetting, runAsX86)) throw new NUnitEngineException("Requested framework is not available: " + frameworkSetting); package.Settings[EnginePackageSettings.TargetRuntimeFramework] = frameworkSetting; @@ -159,7 +163,7 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) targetVersion = frameworkName.Version; } - if (!new RuntimeFramework(targetRuntime, targetVersion).IsAvailable) + if (!IsAvailable(new RuntimeFramework(targetRuntime, targetVersion).Id, runAsX86)) { log.Debug("Preferred version {0} is not installed or this NUnit installation does not support it", targetVersion); if (targetVersion < currentFramework.FrameworkVersion) @@ -173,6 +177,20 @@ private RuntimeFramework SelectRuntimeFrameworkInner(TestPackage package) return targetFramework; } + public override void StartService() + { + try + { + FindAvailableRuntimes(); + } + catch + { + Status = ServiceStatus.Error; + throw; + } + + Status = ServiceStatus.Started; + } /// /// Returns the best available framework that matches a target framework. @@ -194,6 +212,26 @@ public RuntimeFramework GetBestAvailableFramework(RuntimeFramework target) return result; } + private void FindAvailableRuntimes() + { + _availableRuntimes = new List(); + _availableX86Runtimes = new List(); + + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + var netFxRuntimes = NetFxRuntimeLocator.FindRuntimes(); + _availableRuntimes.AddRange(netFxRuntimes); + _availableX86Runtimes.AddRange(netFxRuntimes); + } + + var monoRuntimes = MonoRuntimeLocator.FindRuntimes(); + _availableRuntimes.AddRange(monoRuntimes); + _availableX86Runtimes.AddRange(monoRuntimes); + + _availableRuntimes.AddRange(NetCoreRuntimeLocator.FindRuntimes(x86: false)); + _availableX86Runtimes.AddRange(NetCoreRuntimeLocator.FindRuntimes(x86: true)); + } + /// /// Use TestCentric.Metadata to get information about all assemblies and /// apply it to the package using special internal keywords. diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/MonoRuntimeLocator.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/MonoRuntimeLocator.cs new file mode 100644 index 000000000..13f7837b1 --- /dev/null +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/MonoRuntimeLocator.cs @@ -0,0 +1,137 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETFRAMEWORK +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace NUnit.Engine.Services.RuntimeLocators +{ + public static class MonoRuntimeLocator + { + public static IEnumerable FindRuntimes() + { + var current = RuntimeFramework.CurrentFramework; + if (current.Runtime == RuntimeType.Mono) + { + yield return current; + } + //else + //if (Environment.OSVersion.Platform == PlatformID.Win32NT) + // FindBestMonoFrameworkOnWindows(); + } + + //private static void UseCurrentMonoFramework() + //{ + // Debug.Assert(CurrentFramework.Runtime == Runtime.Mono && MonoPrefix != null && MonoVersion != null); + + // // Multiple profiles are no longer supported with Mono 4.0 + // if (RuntimeFramework.MonoVersion.Major < 4 && FindAllMonoProfiles() > 0) + // return; + + // // If Mono 4.0+ or no profiles found, just use current runtime + // _availableFrameworks.Add(RuntimeFramework.CurrentFramework); + //} + + //private static void FindBestMonoFrameworkOnWindows() + //{ + // // First, look for recent frameworks that use the Software\Mono Key + // RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Mono"); + + // if (key != null && (int)key.GetValue("Installed", 0) == 1) + // { + // string version = key.GetValue("Version") as string; + // MonoPrefix = key.GetValue("SdkInstallRoot") as string; + + // if (version != null) + // { + // MonoVersion = new Version(version); + // AddMonoFramework(new Version(4, 5), null); + // return; + // } + // } + + // // Some later 3.x Mono releases didn't use the registry + // // so check in standard location for them. + // if (Directory.Exists(DEFAULT_WINDOWS_MONO_DIR)) + // { + // MonoPrefix = DEFAULT_WINDOWS_MONO_DIR; + // AddMonoFramework(new Version(4, 5), null); + // return; + // } + + // // Look in the Software\Novell key for older versions + // key = Registry.LocalMachine.OpenSubKey(@"Software\Novell\Mono"); + // if (key != null) + // { + // string version = key.GetValue("DefaultCLR") as string; + // if (version != null) + // { + // RegistryKey subKey = key.OpenSubKey(version); + // if (subKey != null) + // { + // MonoPrefix = subKey.GetValue("SdkInstallRoot") as string; + // MonoVersion = new Version(version); + + // FindAllMonoProfiles(); + // } + // } + // } + //} + + //private static int FindAllMonoProfiles() + //{ + // int count = 0; + + // if (MonoPrefix != null) + // { + // if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/1.0/mscorlib.dll"))) + // { + // AddMonoFramework(new Version(1, 1, 4322), "1.0"); + // count++; + // } + + // if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/2.0/mscorlib.dll"))) + // { + // AddMonoFramework(new Version(2, 0), "2.0"); + // count++; + // } + + // if (Directory.Exists(Path.Combine(MonoPrefix, "lib/mono/3.5"))) + // { + // AddMonoFramework(new Version(3, 5), "3.5"); + // count++; + // } + + // if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/4.0/mscorlib.dll"))) + // { + // AddMonoFramework(new Version(4, 0), "4.0"); + // count++; + // } + + // if (File.Exists(Path.Combine(MonoPrefix, "lib/mono/4.5/mscorlib.dll"))) + // { + // AddMonoFramework(new Version(4, 5), "4.5"); + // count++; + // } + // } + + // return count; + //} + + //private static void AddMonoFramework(Version frameworkVersion, string profile) + //{ + // var framework = new RuntimeFramework(Runtime.Mono, frameworkVersion) + // { + // Profile = profile, + // DisplayName = MonoVersion != null + // ? "Mono " + MonoVersion.ToString() + " - " + profile + " Profile" + // : "Mono - " + profile + " Profile" + // }; + + // _availableFrameworks.Add(framework); + //} + } +} +#endif diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs new file mode 100644 index 000000000..333e5db7b --- /dev/null +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetCoreRuntimeLocator.cs @@ -0,0 +1,136 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETFRAMEWORK +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +namespace NUnit.Engine.Services.RuntimeLocators +{ + public static class NetCoreRuntimeLocator + { + public static IEnumerable FindRuntimes(bool x86) + { + List alreadyFound = new List(); + + foreach (string dirName in GetRuntimeDirectories(x86)) + { + Version newVersion; + if (TryGetVersionFromString(dirName, out newVersion) && !alreadyFound.Contains(newVersion)) + { + alreadyFound.Add(newVersion); + yield return new RuntimeFramework(RuntimeType.NetCore, newVersion); + } + } + + foreach (string line in GetRuntimeList()) + { + Version newVersion; + if (TryGetVersionFromString(line, out newVersion) && !alreadyFound.Contains(newVersion)) + { + alreadyFound.Add(newVersion); + yield return new RuntimeFramework(RuntimeType.NetCore, newVersion); + } + } + } + + private static IEnumerable GetRuntimeDirectories(bool x86) + { + string installDir = GetDotNetInstallDirectory(x86); + + if (installDir != null && Directory.Exists(installDir) && + File.Exists(Path.Combine(installDir, "dotnet.exe"))) + { + string runtimeDir = Path.Combine(installDir, Path.Combine("shared", "Microsoft.NETCore.App")); + if (Directory.Exists(runtimeDir)) + foreach (var dir in new DirectoryInfo(runtimeDir).GetDirectories()) + yield return dir.Name; + } + } + + private static IEnumerable GetRuntimeList() + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = "--list-runtimes", + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + } + }; + + try + { + process.Start(); + } + catch (Exception) + { + // Failed to start dotnet command. Assume no versions are installed and just r eturn just return + yield break; + } + + const string PREFIX = "Microsoft.NETCore.App "; + const int VERSION_START = 22; + + while (!process.StandardOutput.EndOfStream) + { + var line = process.StandardOutput.ReadLine(); + if (line.StartsWith(PREFIX)) + yield return line.Substring(VERSION_START); + } + } + + private static bool TryGetVersionFromString(string text, out Version newVersion) + { + const string VERSION_CHARS = ".0123456789"; + + int len = 0; + foreach (char c in text) + { + if (VERSION_CHARS.IndexOf(c) >= 0) + len++; + else + break; + } + + try + { + var fullVersion = new Version(text.Substring(0, len)); + newVersion = new Version(fullVersion.Major, fullVersion.Minor); + return true; + } + catch + { + newVersion = new Version(); + return false; + } + } + + internal static string GetDotNetInstallDirectory(bool x86) + { + if (Path.DirectorySeparatorChar == '\\') + { + if (x86) + { + RegistryKey key = + Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\dotnet\SetUp\InstalledVersions\x86\"); + return (string)key?.GetValue("InstallLocation"); + } + else + { + RegistryKey key = + Registry.LocalMachine.OpenSubKey(@"SOFTWARE\dotnet\SetUp\InstalledVersions\x64\sharedHost\"); + return (string)key?.GetValue("Path"); + } + } + else + return "/usr/shared/dotnet/"; + } + } +} +#endif diff --git a/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetFxRuntimeLocator.cs b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetFxRuntimeLocator.cs new file mode 100644 index 000000000..944a0838e --- /dev/null +++ b/src/NUnitEngine/nunit.engine/Services/RuntimeLocators/NetFxRuntimeLocator.cs @@ -0,0 +1,113 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +#if NETFRAMEWORK +using System; +using System.Collections.Generic; +using Microsoft.Win32; + +namespace NUnit.Engine.Services.RuntimeLocators +{ + public static class NetFxRuntimeLocator + { + // Note: this method cannot be generalized past V4, because (a) it has + // specific code for detecting .NET 4.5 and (b) we don't know what + // microsoft will do in the future + public static IEnumerable FindRuntimes() + { + // Handle Version 1.0, using a different registry key + foreach (var framework in FindExtremelyOldDotNetFrameworkVersions()) + yield return framework; + + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\NET Framework Setup\NDP"); + if (key != null) + { + foreach (string name in key.GetSubKeyNames()) + { + if (name.StartsWith("v") && name != "v4.0") // v4.0 is a duplicate, legacy key + { + var versionKey = key.OpenSubKey(name); + if (versionKey == null) continue; + + if (name.StartsWith("v4", StringComparison.Ordinal)) + { + // Version 4 and 4.5 + foreach (var framework in FindDotNetFourFrameworkVersions(versionKey)) + { + yield return framework; + } + } + else if (CheckInstallDword(versionKey)) + { + // Versions 1.1, 2.0, 3.0 and 3.5 are possible here + yield return new RuntimeFramework(RuntimeType.Net, new Version(name.Substring(1, 3))); + } + } + } + } + } + + private static IEnumerable FindExtremelyOldDotNetFrameworkVersions() + { + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\.NETFramework\policy\v1.0"); + if (key == null) + yield break; + + foreach (var build in key.GetValueNames()) + yield return new RuntimeFramework(RuntimeType.Net, new Version("1.0." + build)); + } + + private struct MinimumRelease + { + public readonly int Release; + public readonly Version Version; + + public MinimumRelease(int release, Version version) + { + Release = release; + Version = version; + } + } + + private static readonly MinimumRelease[] ReleaseTable = new MinimumRelease[] + { + new MinimumRelease(378389, new Version(4, 5)), + new MinimumRelease(378675, new Version(4, 5, 1)), + new MinimumRelease(379893, new Version(4, 5, 2)), + new MinimumRelease(393295, new Version(4, 6)), + new MinimumRelease(394254, new Version(4, 6, 1)), + new MinimumRelease(394802, new Version(4, 6, 2)), + new MinimumRelease(460798, new Version(4, 7)), + new MinimumRelease(461308, new Version(4, 7, 1)), + new MinimumRelease(461808, new Version(4, 7, 2)), + new MinimumRelease(528040, new Version(4, 8)) + }; + + private static IEnumerable FindDotNetFourFrameworkVersions(RegistryKey versionKey) + { + foreach (string profile in new string[] { "Full", "Client" }) + { + var profileKey = versionKey.OpenSubKey(profile); + if (profileKey == null) continue; + + if (CheckInstallDword(profileKey)) + { + yield return new RuntimeFramework(RuntimeType.Net, new Version(4, 0), profile); + + var release = (int)profileKey.GetValue("Release", 0); + foreach (var entry in ReleaseTable) + if (release >= entry.Release) + yield return new RuntimeFramework(RuntimeType.Net, entry.Version); + + + yield break; //If full profile found don't check for client profile + } + } + } + + private static bool CheckInstallDword(RegistryKey key) + { + return ((int)key.GetValue("Install", 0) == 1); + } + } +} +#endif diff --git a/src/NUnitEngine/nunit.engine/Services/TestAgency.cs b/src/NUnitEngine/nunit.engine/Services/TestAgency.cs index 4f0591c19..0cfe65ede 100644 --- a/src/NUnitEngine/nunit.engine/Services/TestAgency.cs +++ b/src/NUnitEngine/nunit.engine/Services/TestAgency.cs @@ -55,14 +55,18 @@ public ITestAgent GetAgent(TestPackage package) // Target Runtime must be specified by this point string runtimeSetting = package.GetSetting(EnginePackageSettings.TargetRuntimeFramework, ""); Guard.OperationValid(runtimeSetting.Length > 0, "LaunchAgentProcess called with no runtime specified"); + bool runAsX86 = package.GetSetting(EnginePackageSettings.RunAsX86, false); // If target runtime is not available, something went wrong earlier. // We list all available frameworks to use in debugging. var targetRuntime = RuntimeFramework.Parse(runtimeSetting); - if (!_runtimeService.IsAvailable(targetRuntime.Id)) + if (!_runtimeService.IsAvailable(targetRuntime.Id, runAsX86)) { - string msg = $"The {targetRuntime} framework is not available.\r\nAvailable frameworks:"; - foreach (var runtime in RuntimeFramework.AvailableFrameworks) + string msg = $"The {targetRuntime} framework is not available for X86={runAsX86}.\r\nAvailable frameworks:"; + // HACK + var service = _runtimeService as RuntimeFrameworkService; + var availableRuntimes = runAsX86 ? service.AvailableX86Runtimes : service.AvailableRuntimes; + foreach (var runtime in availableRuntimes) msg += $" {runtime}"; throw new ArgumentException(msg); }