diff --git a/src/installer/tests/Assets/Projects/HostApiInvokerApp/HostFXR.cs b/src/installer/tests/Assets/Projects/HostApiInvokerApp/HostFXR.cs index 1a67e91e1d8c72..fb5714be004b81 100644 --- a/src/installer/tests/Assets/Projects/HostApiInvokerApp/HostFXR.cs +++ b/src/installer/tests/Assets/Projects/HostApiInvokerApp/HostFXR.cs @@ -23,6 +23,7 @@ internal enum hostfxr_resolve_sdk2_result_key_t : int resolved_sdk_dir = 0, global_json_path = 1, requested_version = 2, + global_json_state = 3, } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] @@ -159,7 +160,7 @@ static void Test_hostfxr_resolve_sdk2(string[] args) var data = new List<(hostfxr.hostfxr_resolve_sdk2_result_key_t, string)>(); int rc = hostfxr.hostfxr_resolve_sdk2( exe_dir: args[0], - working_dir: args[1], + working_dir: args[1] == "" ? string.Empty : args[1], // Empty string disables global.json look-up flags: Enum.Parse(args[2]), result: (key, value) => data.Add((key, value))); diff --git a/src/installer/tests/HostActivation.Tests/DependencyResolution/ResolveComponentDependencies.cs b/src/installer/tests/HostActivation.Tests/DependencyResolution/ResolveComponentDependencies.cs index 5c66d948f6d0f2..e4aa91edad5fd4 100644 --- a/src/installer/tests/HostActivation.Tests/DependencyResolution/ResolveComponentDependencies.cs +++ b/src/installer/tests/HostActivation.Tests/DependencyResolution/ResolveComponentDependencies.cs @@ -365,7 +365,7 @@ public void ComponentWithCorruptedDepsJsonShouldFail() .Should().Fail() .And.HaveStdOutContaining($"corehost_resolve_component_dependencies:Fail[0x{Constants.ErrorCode.ResolverInitFailure.ToString("x")}]") .And.HaveStdOutContaining("corehost reported errors:") - .And.HaveStdOutContaining($"A JSON parsing exception occurred in [{component.DepsJson}], offset 0 (line 1, column 1): Invalid value.") + .And.HaveStdOutContaining($"Failed to parse file [{component.DepsJson}]. JSON parsing exception: Invalid value. [offset 0: line 1, column 1]") .And.HaveStdOutContaining($"Error initializing the dependency resolver: An error occurred while parsing: {component.DepsJson}"); } @@ -426,7 +426,7 @@ public void MultiThreadedComponentDependencyResolutionWithFailures() .Should().Fail() .And.HaveStdOutContaining($"ComponentA: corehost_resolve_component_dependencies:Fail[0x{Constants.ErrorCode.ResolverInitFailure.ToString("x")}]") .And.HaveStdOutContaining($"ComponentA: corehost reported errors:") - .And.HaveStdOutContaining($"ComponentA: A JSON parsing exception occurred in [{componentWithNoDependencies.DepsJson}], offset 0 (line 1, column 1): Invalid value.") + .And.HaveStdOutContaining($"ComponentA: Failed to parse file [{componentWithNoDependencies.DepsJson}]. JSON parsing exception: Invalid value. [offset 0: line 1, column 1]") .And.HaveStdOutContaining($"ComponentA: Error initializing the dependency resolver: An error occurred while parsing: {componentWithNoDependencies.DepsJson}") .And.HaveStdOutContaining($"ComponentB: corehost_resolve_component_dependencies:Fail[0x{Constants.ErrorCode.LibHostInvalidArgs.ToString("x")}]") .And.HaveStdOutContaining($"ComponentB: corehost reported errors:") diff --git a/src/installer/tests/HostActivation.Tests/HostCommands.cs b/src/installer/tests/HostActivation.Tests/HostCommands.cs index 7b01096ca2acef..d7fb6d30cfd83a 100644 --- a/src/installer/tests/HostActivation.Tests/HostCommands.cs +++ b/src/installer/tests/HostActivation.Tests/HostCommands.cs @@ -8,6 +8,7 @@ using Microsoft.DotNet.Cli.Build; using Microsoft.DotNet.CoreSetup.Test; using Microsoft.DotNet.CoreSetup.Test.HostActivation; +using Microsoft.DotNet.TestUtils; using Xunit; namespace HostActivation.Tests @@ -168,6 +169,65 @@ public void Info_ListEnvironment_LegacyPrefixDetection() .And.HaveStdOutContaining("Detected COMPlus_* environment variable(s). Consider transitioning to DOTNET_* equivalent."); } + [Fact] + public void Info_GlobalJson_InvalidJson() + { + using (TestArtifact workingDir = TestArtifact.Create(nameof(Info_GlobalJson_InvalidJson))) + { + string globalJsonPath = GlobalJson.Write(workingDir.Location, "{ \"sdk\": { }"); + TestContext.BuiltDotNet.Exec("--info") + .WorkingDirectory(workingDir.Location) + .CaptureStdOut().CaptureStdErr() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining($"Invalid [{globalJsonPath}]") + .And.HaveStdOutContaining("JSON parsing exception:") + .And.NotHaveStdErr(); + } + } + + [Theory] + [InlineData("9")] + [InlineData("9.0")] + [InlineData("9.0.x")] + [InlineData("invalid")] + public void Info_GlobalJson_InvalidData(string version) + { + using (TestArtifact workingDir = TestArtifact.Create(nameof(Info_GlobalJson_InvalidData))) + { + string globalJsonPath = GlobalJson.CreateWithVersion(workingDir.Location, version); + TestContext.BuiltDotNet.Exec("--info") + .WorkingDirectory(workingDir.Location) + .CaptureStdOut().CaptureStdErr() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining($"Invalid [{globalJsonPath}]") + .And.HaveStdOutContaining($"Version '{version}' is not valid for the 'sdk/version' value") + .And.HaveStdOutContaining($"Invalid global.json is ignored for SDK resolution") + .And.NotHaveStdErr(); + } + } + + [Theory] + [InlineData("9.0.0")] + [InlineData("9.1.99")] + public void Info_GlobalJson_NonExistentFeatureBand(string version) + { + using (TestArtifact workingDir = TestArtifact.Create(nameof(Info_GlobalJson_NonExistentFeatureBand))) + { + string globalJsonPath = GlobalJson.CreateWithVersion(workingDir.Location, version); + var result = TestContext.BuiltDotNet.Exec("--info") + .WorkingDirectory(workingDir.Location) + .CaptureStdOut().CaptureStdErr() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining($"Invalid [{globalJsonPath}]") + .And.HaveStdOutContaining($"Version '{version}' is not valid for the 'sdk/version' value. SDK feature bands start at 1 - for example, {Version.Parse(version).ToString(2)}.100") + .And.NotHaveStdOutContaining($"Invalid global.json is ignored for SDK resolution") + .And.NotHaveStdErr(); + } + } + [Fact] public void ListRuntimes() { diff --git a/src/installer/tests/HostActivation.Tests/NativeHostApis.cs b/src/installer/tests/HostActivation.Tests/NativeHostApis.cs index 93d3b1f6f9a219..89854dab4934e7 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHostApis.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHostApis.cs @@ -4,12 +4,15 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using FluentAssertions; using Microsoft.DotNet.Cli.Build; +using Microsoft.DotNet.CoreSetup.Test; +using Microsoft.DotNet.CoreSetup.Test.HostActivation; using Microsoft.DotNet.TestUtils; using Xunit; -namespace Microsoft.DotNet.CoreSetup.Test.HostActivation +namespace HostActivation.Tests { internal class ApiNames { @@ -21,6 +24,7 @@ internal class ApiNames public class NativeHostApis : IClassFixture { + private const string NoGlobalJson = ""; private SharedTestState sharedTestState; public NativeHostApis(SharedTestState fixture) @@ -32,12 +36,13 @@ internal sealed class SdkAndFrameworkFixture : IDisposable { private readonly TestArtifact _artifact; - public string EmptyGlobalJsonDir => Path.Combine(_artifact.Location, "wd"); - + // Versions are assumed to be in ascending order. We use these properties to check against the + // expected values passed to hostfxr API callbacks in the expected order. public string ExeDir => Path.Combine(_artifact.Location, "ed"); public string LocalSdkDir => Path.Combine(ExeDir, "sdk"); public string LocalFrameworksDir => Path.Combine(ExeDir, "shared"); - public string[] LocalSdks = new[] { "0.1.2", "5.6.7-preview", "1.2.3" }; + public string[] LocalSdks = new[] { "0.1.200", "1.2.300", "5.6.701-preview" }; + public IEnumerable LocalSdkPaths => LocalSdks.Select(sdk => Path.Combine(LocalSdkDir, sdk)); public List<(string fwName, string[] fwVersions)> LocalFrameworks = new List<(string fwName, string[] fwVersions)>() { @@ -48,7 +53,7 @@ internal sealed class SdkAndFrameworkFixture : IDisposable public string ProgramFiles => Path.Combine(_artifact.Location, "pf"); public string ProgramFilesGlobalSdkDir => Path.Combine(ProgramFiles, "dotnet", "sdk"); public string ProgramFilesGlobalFrameworksDir => Path.Combine(ProgramFiles, "dotnet", "shared"); - public string[] ProgramFilesGlobalSdks = new[] { "4.5.6", "1.2.3", "2.3.4-preview" }; + public string[] ProgramFilesGlobalSdks = new[] { "1.2.300", "2.3.400-preview", "4.5.600" }; public List<(string fwName, string[] fwVersions)> ProgramFilesGlobalFrameworks = new List<(string fwName, string[] fwVersions)>() { @@ -58,18 +63,12 @@ internal sealed class SdkAndFrameworkFixture : IDisposable public string SelfRegistered => Path.Combine(_artifact.Location, "sr"); public string SelfRegisteredGlobalSdkDir => Path.Combine(SelfRegistered, "sdk"); - public string[] SelfRegisteredGlobalSdks = new[] { "3.0.0", "15.1.4-preview", "5.6.7" }; + public string[] SelfRegisteredGlobalSdks = new[] { "3.0.100", "5.6.700", "15.1.400-preview" }; public SdkAndFrameworkFixture() { _artifact = TestArtifact.Create(nameof(SdkAndFrameworkFixture)); - Directory.CreateDirectory(EmptyGlobalJsonDir); - - // start with an empty global.json, it will be ignored, but prevent one lying on disk - // on a given machine from impacting the test. - GlobalJson.CreateEmpty(EmptyGlobalJsonDir); - foreach (string sdk in ProgramFilesGlobalSdks) { AddSdkDirectory(ProgramFilesGlobalSdkDir, sdk); @@ -128,12 +127,7 @@ public void Hostfxr_get_available_sdks_with_multilevel_lookup() // Starting with .NET 7, multi-level lookup is completely disabled for hostfxr API calls. // This test is still valuable to validate that it is in fact disabled var f = sharedTestState.SdkAndFrameworkFixture; - string expectedList = string.Join(';', new[] - { - Path.Combine(f.LocalSdkDir, "0.1.2"), - Path.Combine(f.LocalSdkDir, "1.2.3"), - Path.Combine(f.LocalSdkDir, "5.6.7-preview"), - }); + string expectedList = string.Join(';', f.LocalSdkPaths); string api = ApiNames.hostfxr_get_available_sdks; sharedTestState.TestBehaviorEnabledDotNet.Exec(sharedTestState.HostApiInvokerApp.AppDll, api, f.ExeDir) @@ -152,12 +146,7 @@ public void Hostfxr_get_available_sdks() // Get SDKs sorted by ascending version var f = sharedTestState.SdkAndFrameworkFixture; - string expectedList = string.Join(';', new[] - { - Path.Combine(f.LocalSdkDir, "0.1.2"), - Path.Combine(f.LocalSdkDir, "1.2.3"), - Path.Combine(f.LocalSdkDir, "5.6.7-preview"), - }); + string expectedList = string.Join(';', f.LocalSdkPaths); string api = ApiNames.hostfxr_get_available_sdks; TestContext.BuiltDotNet.Exec(sharedTestState.HostApiInvokerApp.AppDll, api, f.ExeDir) @@ -169,18 +158,19 @@ public void Hostfxr_get_available_sdks() } [Fact] - public void Hostfxr_resolve_sdk2_without_global_json_or_flags() + public void Hostfxr_resolve_sdk2_NoGlobalJson() { - // with no global.json and no flags, pick latest SDK + // With no global.json and no flags, pick latest SDK var f = sharedTestState.SdkAndFrameworkFixture; string expectedData = string.Join(';', new[] { - ("resolved_sdk_dir", Path.Combine(f.LocalSdkDir, "5.6.7-preview")), + ("resolved_sdk_dir", Path.Combine(f.LocalSdkDir, "5.6.701-preview")), + ("global_json_state", "not_found"), }); string api = ApiNames.hostfxr_resolve_sdk2; - TestContext.BuiltDotNet.Exec(sharedTestState.HostApiInvokerApp.AppDll, api, f.ExeDir, f.EmptyGlobalJsonDir, "0") + TestContext.BuiltDotNet.Exec(sharedTestState.HostApiInvokerApp.AppDll, api, f.ExeDir, NoGlobalJson, "0") .EnableTracingAndCaptureOutputs() .Execute() .Should().Pass() @@ -189,18 +179,19 @@ public void Hostfxr_resolve_sdk2_without_global_json_or_flags() } [Fact] - public void Hostfxr_resolve_sdk2_without_global_json_and_disallowing_previews() + public void Hostfxr_resolve_sdk2_NoGlobalJson_DisallowPrerelease() { - // Without global.json and disallowing previews, pick latest non-preview + // With no global.json and disallowing previews, pick latest non-preview var f = sharedTestState.SdkAndFrameworkFixture; string expectedData = string.Join(';', new[] { - ("resolved_sdk_dir", Path.Combine(f.LocalSdkDir, "1.2.3")) + ("resolved_sdk_dir", Path.Combine(f.LocalSdkDir, "1.2.300")), + ("global_json_state", "not_found"), }); string api = ApiNames.hostfxr_resolve_sdk2; - TestContext.BuiltDotNet.Exec(sharedTestState.HostApiInvokerApp.AppDll, api, f.ExeDir, f.EmptyGlobalJsonDir, "disallow_prerelease") + TestContext.BuiltDotNet.Exec(sharedTestState.HostApiInvokerApp.AppDll, api, f.ExeDir, NoGlobalJson, "disallow_prerelease") .EnableTracingAndCaptureOutputs() .Execute() .Should().Pass() @@ -209,7 +200,7 @@ public void Hostfxr_resolve_sdk2_without_global_json_and_disallowing_previews() } [Fact] - public void Hostfxr_resolve_sdk2_with_global_json_and_disallowing_previews() + public void Hostfxr_resolve_sdk2_GlobalJson_DisallowPrerelease() { // With global.json specifying a preview, roll forward to preview // since flag has no impact if global.json specifies a preview. @@ -218,13 +209,14 @@ public void Hostfxr_resolve_sdk2_with_global_json_and_disallowing_previews() var f = sharedTestState.SdkAndFrameworkFixture; using (TestArtifact workingDir = TestArtifact.Create(nameof(workingDir))) { - string requestedVersion = "5.6.6-preview"; + string requestedVersion = "5.6.700-preview"; string globalJson = GlobalJson.CreateWithVersion(workingDir.Location, requestedVersion); string expectedData = string.Join(';', new[] { - ("resolved_sdk_dir", Path.Combine(f.LocalSdkDir, "5.6.7-preview")), + ("resolved_sdk_dir", Path.Combine(f.LocalSdkDir, "5.6.701-preview")), ("global_json_path", globalJson), ("requested_version", requestedVersion), + ("global_json_state", "valid"), }); string api = ApiNames.hostfxr_resolve_sdk2; @@ -249,8 +241,35 @@ public void Hostfxr_resolve_sdk2_GlobalJson_Paths() string globalJson = GlobalJson.Write(workingDir.Location, new GlobalJson.Sdk() { Paths = [ f.SelfRegistered, f.LocalSdkDir ] }); string expectedData = string.Join(';', new[] { - ("resolved_sdk_dir", Path.Combine(f.SelfRegisteredGlobalSdkDir, "15.1.4-preview")), - ("global_json_path", globalJson) + ("resolved_sdk_dir", Path.Combine(f.SelfRegisteredGlobalSdkDir, "15.1.400-preview")), + ("global_json_path", globalJson), + ("global_json_state", "valid"), + }); + + string api = ApiNames.hostfxr_resolve_sdk2; + TestContext.BuiltDotNet.Exec(sharedTestState.HostApiInvokerApp.AppDll, api, f.ExeDir, workingDir.Location, "0") + .EnableTracingAndCaptureOutputs() + .Execute() + .Should().Pass() + .And.ReturnStatusCode(api, Constants.ErrorCode.Success) + .And.HaveStdOutContaining($"{api} data:[{expectedData}]"); + } + } + + [Fact] + public void Hostfxr_resolve_sdk2_GlobalJson_InvalidJson() + { + // With global.json with malformed JSON + // Pick latest SDK (invalid global.json ignored), report invalid JSON for global.json + + var f = sharedTestState.SdkAndFrameworkFixture; + using (TestArtifact workingDir = TestArtifact.Create(nameof(Hostfxr_resolve_sdk2_GlobalJson_InvalidJson))) + { + GlobalJson.Write(workingDir.Location, "{ \"sdk\": { }"); + string expectedData = string.Join(';', new[] + { + ("resolved_sdk_dir", Path.Combine(f.LocalSdkDir, "5.6.701-preview")), + ("global_json_state", "invalid_json"), }); string api = ApiNames.hostfxr_resolve_sdk2; @@ -263,6 +282,61 @@ public void Hostfxr_resolve_sdk2_GlobalJson_Paths() } } + [Fact] + public void Hostfxr_resolve_sdk2_GlobalJson_InvalidData() + { + // With global.json with invalid version value + // Pick latest SDK (invalid global.json ignored), report invalid data for global.json + + var f = sharedTestState.SdkAndFrameworkFixture; + using (TestArtifact workingDir = TestArtifact.Create(nameof(Hostfxr_resolve_sdk2_GlobalJson_InvalidData))) + { + string invalidVersion = "invalid"; + GlobalJson.CreateWithVersion(workingDir.Location, invalidVersion); + string expectedData = string.Join(';', new[] + { + ("resolved_sdk_dir", Path.Combine(f.LocalSdkDir, "5.6.701-preview")), + ("global_json_state", "invalid_data"), + }); + + string api = ApiNames.hostfxr_resolve_sdk2; + TestContext.BuiltDotNet.Exec(sharedTestState.HostApiInvokerApp.AppDll, api, f.ExeDir, workingDir.Location, "0") + .EnableTracingAndCaptureOutputs() + .Execute() + .Should().Pass() + .And.ReturnStatusCode(api, Constants.ErrorCode.Success) + .And.HaveStdOutContaining($"{api} data:[{expectedData}]"); + } + } + + [Fact] + public void Hostfxr_resolve_sdk2_GlobalJson_InvalidDataNoFallback() + { + // With global.json with invalid version value - feature band < 1 + // No match, report invalid data for global.json + + var f = sharedTestState.SdkAndFrameworkFixture; + using (TestArtifact workingDir = TestArtifact.Create(nameof(Hostfxr_resolve_sdk2_GlobalJson_InvalidData))) + { + string invalidVersion = "1.2.0"; + string globalJson = GlobalJson.CreateWithVersion(workingDir.Location, invalidVersion); + string expectedData = string.Join(';', new[] + { + ("global_json_path", globalJson), + ("requested_version", invalidVersion), + ("global_json_state", "__invalid_data_no_fallback"), + }); + + string api = ApiNames.hostfxr_resolve_sdk2; + TestContext.BuiltDotNet.Exec(sharedTestState.HostApiInvokerApp.AppDll, api, f.ExeDir, workingDir.Location, "0") + .EnableTracingAndCaptureOutputs() + .Execute() + .Should().Pass() + .And.ReturnStatusCode(api, Constants.ErrorCode.SdkResolveFailure) + .And.HaveStdOutContaining($"{api} data:[{expectedData}]"); + } + } + [Fact] public void Hostfxr_corehost_set_error_writer_test() { @@ -276,19 +350,8 @@ public void Hostfxr_corehost_set_error_writer_test() public void Hostfxr_get_dotnet_environment_info_dotnet_root_only() { var f = sharedTestState.SdkAndFrameworkFixture; - string expectedSdkVersions = string.Join(";", new[] - { - "0.1.2", - "1.2.3", - "5.6.7-preview" - }); - - string expectedSdkPaths = string.Join(';', new[] - { - Path.Combine(f.LocalSdkDir, "0.1.2"), - Path.Combine(f.LocalSdkDir, "1.2.3"), - Path.Combine(f.LocalSdkDir, "5.6.7-preview"), - }); + string expectedSdkVersions = string.Join(";", f.LocalSdks); + string expectedSdkPaths = string.Join(';', f.LocalSdkPaths); string expectedFrameworkNames = string.Join(';', new[] { @@ -329,19 +392,8 @@ public void Hostfxr_get_dotnet_environment_info_dotnet_root_only() public void Hostfxr_get_dotnet_environment_info_with_multilevel_lookup_with_dotnet_root() { var f = sharedTestState.SdkAndFrameworkFixture; - string expectedSdkVersions = string.Join(';', new[] - { - "0.1.2", - "1.2.3", - "5.6.7-preview", - }); - - string expectedSdkPaths = string.Join(';', new[] - { - Path.Combine(f.LocalSdkDir, "0.1.2"), - Path.Combine(f.LocalSdkDir, "1.2.3"), - Path.Combine(f.LocalSdkDir, "5.6.7-preview"), - }); + string expectedSdkVersions = string.Join(';', f.LocalSdks); + string expectedSdkPaths = string.Join(';', f.LocalSdkPaths); string expectedFrameworkNames = string.Join(';', new[] { diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/GetNativeSearchDirectories.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/GetNativeSearchDirectories.cs index 16cf0ee8bb68c8..d382ff9244bd59 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/GetNativeSearchDirectories.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/GetNativeSearchDirectories.cs @@ -125,7 +125,7 @@ public void WithInvalidDepsJson() .And.HaveStdOutContaining($"get_native_search_directories (null,0) returned unexpected error code 0x{Constants.ErrorCode.ResolverInitFailure:x} expected HostApiBufferTooSmall (0x80008098).") .And.HaveStdOutContaining("buffer_size: 0") .And.HaveStdOutContaining("hostfxr reported errors:") - .And.HaveStdOutContaining($"A JSON parsing exception occurred in [{depsJsonFile}], offset 1 (line 1, column 2): Missing a name for object member.") + .And.HaveStdOutContaining($"Failed to parse file [{depsJsonFile}]. JSON parsing exception: Missing a name for object member. [offset 1: line 1, column 2]") .And.HaveStdOutContaining($"Error initializing the dependency resolver: An error occurred while parsing: {depsJsonFile}"); } diff --git a/src/installer/tests/HostActivation.Tests/SDKLookup.cs b/src/installer/tests/HostActivation.Tests/SDKLookup.cs index 29d0eab362664c..d633ec94d8d0f8 100644 --- a/src/installer/tests/HostActivation.Tests/SDKLookup.cs +++ b/src/installer/tests/HostActivation.Tests/SDKLookup.cs @@ -632,7 +632,7 @@ public static IEnumerable InvalidGlobalJsonData yield return new object[] { "{ sdk: { \"version\": \"9999.0.100\" } }", new[] { - "A JSON parsing exception occurred", + "JSON parsing exception:", IgnoringSDKSettings } }; diff --git a/src/installer/tests/TestUtils/Constants.cs b/src/installer/tests/TestUtils/Constants.cs index e9e2e21b2df22e..4d3be90319ad15 100644 --- a/src/installer/tests/TestUtils/Constants.cs +++ b/src/installer/tests/TestUtils/Constants.cs @@ -128,6 +128,7 @@ public static class ErrorCode public const int AppArgNotRunnable = unchecked((int)0x80008094); public const int AppHostExeNotBoundFailure = unchecked((int)0x80008095); public const int FrameworkMissingFailure = unchecked((int)0x80008096); + public const int SdkResolveFailure = unchecked((int)0x8000809b); public const int FrameworkCompatFailure = unchecked((int)0x8000809c); public const int BundleExtractionFailure = unchecked((int)0x8000809f); diff --git a/src/native/corehost/comhost/clsidmap.cpp b/src/native/corehost/comhost/clsidmap.cpp index 001649765f662e..af18abd8b2db92 100644 --- a/src/native/corehost/comhost/clsidmap.cpp +++ b/src/native/corehost/comhost/clsidmap.cpp @@ -102,7 +102,7 @@ namespace json_parser_t json; if (!json.parse_raw_data(reinterpret_cast(data), size, _X(""))) { - trace::error(_X("Embedded .clsidmap format is invalid")); + trace::error(_X("Embedded .clsidmap is invalid.\n %s"), json.get_error_message().c_str()); throw HResultException{ StatusCode::InvalidConfigFile }; } @@ -180,7 +180,7 @@ namespace json_parser_t json; if (!json.parse_file(map_file_name)) { - trace::error(_X("File .clsidmap format is invalid")); + trace::error(_X("File .clsidmap [%s] is invalid.\n %s"), map_file_name.c_str(), json.get_error_message().c_str()); throw HResultException{ StatusCode::InvalidConfigFile }; } diff --git a/src/native/corehost/fxr/command_line.cpp b/src/native/corehost/fxr/command_line.cpp index 01e77b4b23d236..b23f6f4bb1677d 100644 --- a/src/native/corehost/fxr/command_line.cpp +++ b/src/native/corehost/fxr/command_line.cpp @@ -280,7 +280,7 @@ int command_line::parse_args_for_sdk_command( return parse_args(host_info, 1, argc, argv, false, host_mode_t::muxer, new_argoff, app_candidate, opts); } -void command_line::print_muxer_info(const pal::string_t &dotnet_root, const pal::string_t &global_json_path, bool skip_sdk_info_output) +void command_line::print_muxer_info(const pal::string_t &dotnet_root, const sdk_resolver::global_file_info &global_json, bool skip_sdk_info_output) { pal::string_t commit = _STRINGIFY(REPO_COMMIT_HASH); trace::println(_X("\n") @@ -322,9 +322,32 @@ void command_line::print_muxer_info(const pal::string_t &dotnet_root, const pal: } trace::println(_X("\n") - _X("global.json file:\n") - _X(" %s"), - global_json_path.empty() ? _X("Not found") : global_json_path.c_str()); + _X("global.json file:")); + switch (global_json.state) + { + case sdk_resolver::global_file_info::state::not_found: + trace::println(_X(" Not found")); + break; + case sdk_resolver::global_file_info::state::valid: + trace::println(_X(" %s"), global_json.path.c_str()); + break; + case sdk_resolver::global_file_info::state::invalid_json: + case sdk_resolver::global_file_info::state::invalid_data: + case sdk_resolver::global_file_info::state::__invalid_data_no_fallback: + trace::println(_X(" Invalid [%s]"), global_json.path.c_str()); + if (!global_json.error_message.empty()) + { + trace::println(_X(" %s"), global_json.error_message.c_str()); + } + if (global_json.state != sdk_resolver::global_file_info::state::__invalid_data_no_fallback) + { + trace::println(_X(" Invalid global.json is ignored for SDK resolution.")); + } + break; + case sdk_resolver::global_file_info::state::__last: + assert(false && "Unexpected __last state"); + break; + } trace::println(_X("\n") _X("Learn more:\n") diff --git a/src/native/corehost/fxr/command_line.h b/src/native/corehost/fxr/command_line.h index 935e66800870e5..979694b44e9a7d 100644 --- a/src/native/corehost/fxr/command_line.h +++ b/src/native/corehost/fxr/command_line.h @@ -8,6 +8,8 @@ #include #include +#include "sdk_resolver.h" + enum class known_options { additional_probing_path, @@ -59,7 +61,7 @@ namespace command_line // skip_sdk_info_output indicates whether or not to skip any information that the SDK would have // already printed. Related: https://github.com/dotnet/sdk/issues/33697 - void print_muxer_info(const pal::string_t &dotnet_root, const pal::string_t &global_json_path, bool skip_sdk_info_output); + void print_muxer_info(const pal::string_t &dotnet_root, const sdk_resolver::global_file_info& global_json, bool skip_sdk_info_output); void print_muxer_usage(bool is_sdk_present); }; diff --git a/src/native/corehost/fxr/fx_muxer.cpp b/src/native/corehost/fxr/fx_muxer.cpp index 36fec3d9ec00a1..cef02fd75a4a9f 100644 --- a/src/native/corehost/fxr/fx_muxer.cpp +++ b/src/native/corehost/fxr/fx_muxer.cpp @@ -1103,7 +1103,7 @@ int fx_muxer_t::handle_cli( } else if (pal::strcasecmp(_X("--info"), argv[1]) == 0) { - command_line::print_muxer_info(host_info.dotnet_root, resolver.global_file_path(), false /*skip_sdk_info_output*/); + command_line::print_muxer_info(host_info.dotnet_root, resolver.global_file(), false /*skip_sdk_info_output*/); return StatusCode::Success; } @@ -1160,7 +1160,7 @@ int fx_muxer_t::handle_cli( if (pal::strcasecmp(_X("--info"), argv[1]) == 0) { - command_line::print_muxer_info(host_info.dotnet_root, resolver.global_file_path(), result == 0 /*skip_sdk_info_output*/); + command_line::print_muxer_info(host_info.dotnet_root, resolver.global_file(), result == 0 /*skip_sdk_info_output*/); } return result; diff --git a/src/native/corehost/fxr/hostfxr.cpp b/src/native/corehost/fxr/hostfxr.cpp index bb3d8345568a9c..1b3438c05b4d28 100644 --- a/src/native/corehost/fxr/hostfxr.cpp +++ b/src/native/corehost/fxr/hostfxr.cpp @@ -173,15 +173,29 @@ enum class hostfxr_resolve_sdk2_result_key_t : int32_t resolved_sdk_dir = 0, global_json_path = 1, requested_version = 2, + global_json_state = 3, }; typedef void (HOSTFXR_CALLTYPE *hostfxr_resolve_sdk2_result_fn)( hostfxr_resolve_sdk2_result_key_t key, const pal::char_t* value); +namespace +{ + const pal::char_t *GlobalJsonStates[] = + { + _X("not_found"), + _X("valid"), + _X("invalid_json"), + _X("invalid_data"), + _X("__invalid_data_no_fallback"), + }; + static_assert((sizeof(GlobalJsonStates) / sizeof(*GlobalJsonStates)) == static_cast(sdk_resolver::global_file_info::state::__last), "Invalid state count"); +} + // // Determines the directory location of the SDK accounting for -// global.json and multi-level lookup policy. +// global.json. // // Invoked via MSBuild SDK resolver to locate SDK props and targets // from an msbuild other than the one bundled by the CLI. @@ -192,13 +206,12 @@ typedef void (HOSTFXR_CALLTYPE *hostfxr_resolve_sdk2_result_fn)( // sub-folders. Pass the directory of a dotnet executable to // mimic how that executable would search in its own directory. // It is also valid to pass nullptr or empty, in which case -// multi-level lookup can still search other locations if -// it has not been disabled by the user's environment. +// only paths from any found global.json will be searched. // // working_dir // The directory where the search for global.json (which can // control the resolved SDK version) starts and proceeds -// upwards. +// upwards. If nullptr or empty, global.json search is disabled. // // flags // Bitwise flags that influence resolution. @@ -228,6 +241,10 @@ typedef void (HOSTFXR_CALLTYPE *hostfxr_resolve_sdk2_result_fn)( // value will hold the requested version. This will occur for // both resolution success and failure. // +// The result will be invoked with global_json_state key and +// the value will hold one of: not_found, valid, invalid_json, +// invalid_data. +// // Return value: // 0 on success, otherwise failure // 0x8000809b - SDK could not be resolved (SdkResolveFailure) @@ -273,11 +290,11 @@ SHARED_API int32_t HOSTFXR_CALLTYPE hostfxr_resolve_sdk2( resolved_sdk_dir.c_str()); } - if (!resolver.global_file_path().empty()) + if (resolver.global_file().is_data_used() && !resolver.global_file().path.empty()) { result( hostfxr_resolve_sdk2_result_key_t::global_json_path, - resolver.global_file_path().c_str()); + resolver.global_file().path.c_str()); } if (!resolver.get_requested_version().is_empty()) @@ -287,6 +304,10 @@ SHARED_API int32_t HOSTFXR_CALLTYPE hostfxr_resolve_sdk2( resolver.get_requested_version().as_str().c_str()); } + result( + hostfxr_resolve_sdk2_result_key_t::global_json_state, + GlobalJsonStates[static_cast(resolver.global_file().state)]); + return !resolved_sdk_dir.empty() ? StatusCode::Success : StatusCode::SdkResolveFailure; diff --git a/src/native/corehost/fxr/sdk_resolver.cpp b/src/native/corehost/fxr/sdk_resolver.cpp index f70ee651720a7d..d3063a3db3d5a7 100644 --- a/src/native/corehost/fxr/sdk_resolver.cpp +++ b/src/native/corehost/fxr/sdk_resolver.cpp @@ -26,25 +26,21 @@ namespace _X("latestMinor"), _X("latestMajor"), }; -} - -sdk_resolver::sdk_resolver(bool allow_prerelease) - : sdk_resolver({}, sdk_roll_forward_policy::latest_major, allow_prerelease) -{ -} -sdk_resolver::sdk_resolver(fx_ver_t version, sdk_roll_forward_policy roll_forward, bool allow_prerelease) - : requested_version(std::move(version)) - , roll_forward(roll_forward) - , allow_prerelease(allow_prerelease) - , has_custom_paths(false) -{ + int get_feature_band(const fx_ver_t& version) + { + // SDK versions encode both the feature band and patch version in the SemVer patch version. + // They have the form x.y.znn, where z is the feature band and nn is the patch version. + // To get the feature band, we divide the SemVer patch by 100 + return version.get_patch() / 100; + } } -const pal::string_t& sdk_resolver::global_file_path() const -{ - return global_file; -} +sdk_resolver::sdk_resolver(bool allow_prerelease) + : roll_forward{sdk_roll_forward_policy::latest_major} + , allow_prerelease{allow_prerelease} + , has_custom_paths{false} +{ } const fx_ver_t& sdk_resolver::get_requested_version() const { @@ -121,7 +117,7 @@ std::vector sdk_resolver::get_search_paths(const pal::string_t& d else { // Use custom paths specified in the global.json - pal::string_t json_dir = get_directory(global_file); + pal::string_t json_dir = get_directory(global_json.path); for (const pal::string_t& path : paths) { if (path == _X("$host$")) @@ -166,10 +162,10 @@ void sdk_resolver::print_resolution_error(const pal::string_t& dotnet_root, cons main_error_prefix, requested.c_str()); - bool has_global_file = !global_file.empty(); + bool has_global_file = global_json.is_data_used(); if (has_global_file) { - trace::error(_X("global.json file: %s"), global_file.c_str()); + trace::error(_X("global.json file: %s"), global_json.path.c_str()); if (has_custom_paths) { trace::error(_X(" Search paths:")); @@ -188,7 +184,7 @@ void sdk_resolver::print_resolution_error(const pal::string_t& dotnet_root, cons trace::error(_X("")); if (has_global_file) { - trace::error(_X("Install the [%s] .NET SDK or update [%s] to match an installed SDK."), requested.c_str(), global_file.c_str()); + trace::error(_X("Install the [%s] .NET SDK or update [%s] to match an installed SDK."), requested.c_str(), global_json.path.c_str()); } else { @@ -200,7 +196,7 @@ void sdk_resolver::print_resolution_error(const pal::string_t& dotnet_root, cons trace::error(_X("%s%s"), main_error_prefix, no_sdk_message); if (has_custom_paths && paths.empty()) { - trace::error(_X("%sEmpty search paths specified in global.json file: %s"), main_error_prefix, global_file.c_str()); + trace::error(_X("%sEmpty search paths specified in global.json file: %s"), main_error_prefix, global_json.path.c_str()); } } @@ -237,14 +233,27 @@ sdk_resolver sdk_resolver::from_nearest_global_file(const pal::string_t& cwd, bo { sdk_resolver resolver{ allow_prerelease }; - if (!resolver.parse_global_file(find_nearest_global_file(cwd))) + pal::string_t global_file_path = find_nearest_global_file(cwd); + + if (global_file_path.empty()) + { + resolver.global_json.state = global_file_info::state::not_found; + } + else { - // Fall back to a default SDK resolver - resolver = sdk_resolver{ allow_prerelease }; + global_file_info global_file = resolver.parse_global_file(global_file_path); + if (!global_file.is_data_used()) + { + // Fall back to a default SDK resolver + resolver = sdk_resolver{ allow_prerelease }; - trace::warning( - _X("Ignoring SDK settings in global.json: the latest installed .NET SDK (%s prereleases) will be used"), - resolver.allow_prerelease ? _X("including") : _X("excluding")); + trace::verbose(_X("Invalid global.json [%s]:\n %s"), global_file.path.c_str(), global_file.error_message.c_str()); + trace::warning( + _X("Ignoring SDK settings in global.json: the latest installed .NET SDK (%s prereleases) will be used"), + resolver.allow_prerelease ? _X("including") : _X("excluding")); + } + + resolver.global_json = std::move(global_file); } // If the requested version is a prerelease, always allow prerelease versions @@ -312,22 +321,27 @@ pal::string_t sdk_resolver::find_nearest_global_file(const pal::string_t& cwd) return {}; } -bool sdk_resolver::parse_global_file(pal::string_t global_file_path) +sdk_resolver::global_file_info sdk_resolver::parse_global_file(const pal::string_t& global_file_path) { + global_file_info ret; if (global_file_path.empty()) { // Missing global.json is treated as success (use default resolver) - return true; + ret.state = global_file_info::state::not_found; + return ret; } trace::verbose(_X("--- Resolving SDK information from global.json [%s]"), global_file_path.c_str()); + ret.path = global_file_path; // After we're done parsing `global_file_path`, none of its contents will be referenced // from the data private to json_parser_t; it's safe to declare it on the stack. json_parser_t json; if (!json.parse_file(global_file_path)) { - return false; + ret.error_message = json.get_error_message(); + ret.state = global_file_info::state::invalid_json; + return ret; } const auto& sdk = json.document().FindMember(_X("sdk")); @@ -335,13 +349,16 @@ bool sdk_resolver::parse_global_file(pal::string_t global_file_path) { // Missing SDK is treated as success (use default resolver) trace::verbose(_X("Value 'sdk' is missing or null in [%s]"), global_file_path.c_str()); - return true; + ret.state = global_file_info::state::valid; + return ret; } + ret.state = global_file_info::state::invalid_data; + if (!sdk->value.IsObject()) { - trace::warning(_X("Expected a JSON object for the 'sdk' value in [%s]"), global_file_path.c_str()); - return false; + ret.error_message = _X("Expected a JSON object for the 'sdk' value"); + return ret; } const auto& version_value = sdk->value.FindMember(_X("version")); @@ -353,18 +370,14 @@ bool sdk_resolver::parse_global_file(pal::string_t global_file_path) { if (!version_value->value.IsString()) { - trace::warning(_X("Expected a string for the 'sdk/version' value in [%s]"), global_file_path.c_str()); - return false; + ret.error_message = _X("Expected a string for the 'sdk/version' value"); + return ret; } if (!fx_ver_t::parse(version_value->value.GetString(), &requested_version, false)) { - trace::warning( - _X("Version '%s' is not valid for the 'sdk/version' value in [%s]"), - version_value->value.GetString(), - global_file_path.c_str() - ); - return false; + ret.error_message = utils::format_string(_X("Version '%s' is not valid for the 'sdk/version' value"), version_value->value.GetString()); + return ret; } // The default policy when a version is specified is 'patch' @@ -380,30 +393,22 @@ bool sdk_resolver::parse_global_file(pal::string_t global_file_path) { if (!roll_forward_value->value.IsString()) { - trace::warning(_X("Expected a string for the 'sdk/rollForward' value in [%s]"), global_file_path.c_str()); - return false; + ret.error_message = _X("Expected a string for the 'sdk/rollForward' value"); + return ret; } roll_forward = to_policy(roll_forward_value->value.GetString()); if (roll_forward == sdk_roll_forward_policy::unsupported) { - trace::warning( - _X("The roll-forward policy '%s' is not supported for the 'sdk/rollForward' value in [%s]"), - roll_forward_value->value.GetString(), - global_file_path.c_str() - ); - return false; + ret.error_message = utils::format_string(_X("The roll-forward policy '%s' is not supported for the 'sdk/rollForward' value"), roll_forward_value->value.GetString()); + return ret; } // All policies other than 'latestMajor' require a version to operate if (roll_forward != sdk_roll_forward_policy::latest_major && requested_version.is_empty()) { - trace::warning( - _X("The roll-forward policy '%s' requires a 'sdk/version' value in [%s]"), - roll_forward_value->value.GetString(), - global_file_path.c_str() - ); - return false; + ret.error_message = utils::format_string(_X("The roll-forward policy '%s' requires a 'sdk/version' value"), roll_forward_value->value.GetString()); + return ret; } } @@ -416,8 +421,8 @@ bool sdk_resolver::parse_global_file(pal::string_t global_file_path) { if (!allow_prerelease_value->value.IsBool()) { - trace::warning(_X("Expected a boolean for the 'sdk/allowPrerelease' value in [%s]"), global_file_path.c_str()); - return false; + ret.error_message = _X("Expected a boolean for the 'sdk/allowPrerelease' value"); + return ret; } allow_prerelease = allow_prerelease_value->value.GetBool(); @@ -434,8 +439,8 @@ bool sdk_resolver::parse_global_file(pal::string_t global_file_path) { if (!paths_value->value.IsArray()) { - trace::warning(_X("Expected an array for 'sdk/paths' value in [%s]"), global_file_path.c_str()); - return false; + ret.error_message = _X("Expected an array for 'sdk/paths' value"); + return ret; } has_custom_paths = true; @@ -459,15 +464,25 @@ bool sdk_resolver::parse_global_file(pal::string_t global_file_path) { if (!error_message_value->value.IsString()) { - trace::warning(_X("Expected a string for the 'sdk/errorMessage' value in [%s]"), global_file_path.c_str()); - return false; + ret.error_message = _X("Expected a string for the 'sdk/errorMessage' value"); + return ret; } error_message = error_message_value->value.GetString(); } - global_file = std::move(global_file_path); - return true; + // SDK feature bands start at 1, so a version with a feature band < 1 is not a valid version. + // We want to provide an error message, but we should still use the settings in the global.json + // and not fall back to the default resolver. + if (!requested_version.is_empty() && get_feature_band(requested_version) < 1) + { + ret.error_message = utils::format_string(_X("Version '%s' is not valid for the 'sdk/version' value. SDK feature bands start at 1 - for example, %d.%d.100"), requested_version.as_str().c_str(), requested_version.get_major(), requested_version.get_minor()); + ret.state = global_file_info::state::__invalid_data_no_fallback; + return ret; + } + + ret.state = global_file_info::state::valid; + return ret; } bool sdk_resolver::matches_policy(const fx_ver_t& current) const @@ -487,8 +502,8 @@ bool sdk_resolver::matches_policy(const fx_ver_t& current) const return true; } - int requested_feature = requested_version.get_patch() / 100; - int current_feature = current.get_patch() / 100; + int requested_feature = get_feature_band(requested_version); + int current_feature = get_feature_band(current); int requested_minor = requested_version.get_minor(); int current_minor = current.get_minor(); @@ -543,7 +558,7 @@ bool sdk_resolver::is_better_match(const fx_ver_t& current, const fx_ver_t& prev is_policy_use_latest() || (current.get_major() == previous.get_major() && current.get_minor() == previous.get_minor() && - (current.get_patch() / 100) == (previous.get_patch() / 100))) + (get_feature_band(current) == get_feature_band(previous)))) { // Accept the later of the versions // This will also handle stable and prerelease comparisons diff --git a/src/native/corehost/fxr/sdk_resolver.h b/src/native/corehost/fxr/sdk_resolver.h index d8724acd03b5b4..73a81407a4da72 100644 --- a/src/native/corehost/fxr/sdk_resolver.h +++ b/src/native/corehost/fxr/sdk_resolver.h @@ -34,10 +34,29 @@ enum class sdk_roll_forward_policy class sdk_resolver { public: - explicit sdk_resolver(bool allow_prerelease = true); - sdk_resolver(fx_ver_t version, sdk_roll_forward_policy roll_forward, bool allow_prerelease); + struct global_file_info + { + enum class state + { + not_found, + valid, + invalid_json, + invalid_data, + // Invalid data that doesn't fall back to default resolution. If we are able to remove the fallback + // and just fail on invalid data, this should be removed and invalid_data used instead. + __invalid_data_no_fallback, + __last + }; + + state state; + pal::string_t path; + pal::string_t error_message; - const pal::string_t& global_file_path() const; + // Whether or not the global.json is actually used for resolution. + bool is_data_used() const { return state == state::valid || state == state::__invalid_data_no_fallback; } + }; + + const global_file_info& global_file() const { return global_json; } const fx_ver_t& get_requested_version() const; @@ -54,10 +73,11 @@ class sdk_resolver bool allow_prerelease = true); private: + explicit sdk_resolver(bool allow_prerelease = true); static sdk_roll_forward_policy to_policy(const pal::string_t& name); static const pal::char_t* to_policy_name(sdk_roll_forward_policy policy); static pal::string_t find_nearest_global_file(const pal::string_t& cwd); - bool parse_global_file(pal::string_t global_file_path); + global_file_info parse_global_file(const pal::string_t& global_file_path); bool matches_policy(const fx_ver_t& current) const; bool is_better_match(const fx_ver_t& current, const fx_ver_t& previous) const; bool exact_match_preferred() const; @@ -66,7 +86,7 @@ class sdk_resolver // Returns true and sets sdk_path/resolved_version if a matching SDK was found bool resolve_sdk_path_and_version(const pal::string_t& dir, pal::string_t& sdk_path, fx_ver_t& resolved_version) const; - pal::string_t global_file; + global_file_info global_json; fx_ver_t requested_version; sdk_roll_forward_policy roll_forward; bool allow_prerelease; diff --git a/src/native/corehost/hostmisc/pal.h b/src/native/corehost/hostmisc/pal.h index 334ab5a4a0f97e..84b3698af0cced 100644 --- a/src/native/corehost/hostmisc/pal.h +++ b/src/native/corehost/hostmisc/pal.h @@ -160,6 +160,12 @@ namespace pal inline int str_vprintf(char_t* buffer, size_t count, const char_t* format, va_list vl) { return ::_vsnwprintf_s(buffer, count, _TRUNCATE, format, vl); } inline int strlen_vprintf(const char_t* format, va_list vl) { return ::_vscwprintf(format, vl); } + template + int str_printf(char_t* buffer, size_t count, const char_t* format, Args&&... args) { return ::_snwprintf_s(buffer, count, _TRUNCATE, format, std::forward(args)...); } + + template + inline int strlen_printf(const char_t* format, Args&&... args) { return ::_scwprintf(format, std::forward(args)...); } + inline const string_t strerror(int errnum) { // Windows does not provide strerrorlen to get the actual error length. @@ -231,6 +237,12 @@ namespace pal inline int str_vprintf(char_t* str, size_t size, const char_t* format, va_list vl) { return ::vsnprintf(str, size, format, vl); } inline int strlen_vprintf(const char_t* format, va_list vl) { return ::vsnprintf(nullptr, 0, format, vl); } + template + int str_printf(char_t* buffer, size_t size, const char_t* format, Args&&... args) { return ::snprintf(buffer, size, format, std::forward(args)...); } + + template + inline int strlen_printf(const char_t* format, Args&&... args) { return ::snprintf(nullptr, 0, format, std::forward(args)...); } + inline const string_t strerror(int errnum) { return ::strerror(errnum); } inline size_t pal_utf8string(const string_t& str, char* out_buffer, size_t buffer_len) diff --git a/src/native/corehost/hostmisc/utils.h b/src/native/corehost/hostmisc/utils.h index 0bcee74fc849f2..98674cc5007449 100644 --- a/src/native/corehost/hostmisc/utils.h +++ b/src/native/corehost/hostmisc/utils.h @@ -69,6 +69,15 @@ namespace utils { return starts_with(value, prefix, L - 1, match_case); } + + template + pal::string_t format_string(const pal::char_t* format, Args&&... args) + { + int len = pal::strlen_printf(format, std::forward(args)...) + 1; + std::vector buffer(len); + pal::str_printf(&buffer[0], len, format, std::forward(args)...); + return pal::string_t(buffer.data(), buffer.size() - 1); + } } pal::string_t strip_executable_ext(const pal::string_t& filename); diff --git a/src/native/corehost/hostpolicy/deps_format.cpp b/src/native/corehost/hostpolicy/deps_format.cpp index 7a58f12bb067f2..637364246c6708 100644 --- a/src/native/corehost/hostpolicy/deps_format.cpp +++ b/src/native/corehost/hostpolicy/deps_format.cpp @@ -587,7 +587,10 @@ void deps_json_t::load(bool is_framework_dependent, std::function; const document_t& document() const { return m_document; } + const pal::string_t& get_error_message() const { return m_parse_error; } bool parse_raw_data(char* data, int64_t size, const pal::string_t& context); bool parse_file(const pal::string_t& path); @@ -56,6 +57,9 @@ class json_parser_t { // If a json file is parsed from a single-file bundle, the following fields represents // the location of this json file within the bundle. const bundle::location_t* m_bundle_location; + + // Error message from parsing + pal::string_t m_parse_error; }; #endif // __JSON_PARSER_H__ diff --git a/src/native/corehost/runtime_config.cpp b/src/native/corehost/runtime_config.cpp index be14888d1c28ba..c833610358a3fd 100644 --- a/src/native/corehost/runtime_config.cpp +++ b/src/native/corehost/runtime_config.cpp @@ -409,6 +409,7 @@ bool runtime_config_t::ensure_parsed() json_parser_t json; if (!json.parse_file(m_path)) { + trace::error(_X("Failed to parse file [%s]. %s"), m_path.c_str(), json.get_error_message().c_str()); return false; }