From d53d38739e7155eafb8d0f5b7f7288df6dbd8a51 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Wed, 28 Feb 2018 18:53:11 -0600 Subject: [PATCH] Support 'additionalFrameworks' section in app's runtimeconfig.json (dotnet/core-setup#3741) Commit migrated from https://github.com/dotnet/core-setup/commit/a94c3251709071805f51fad5fb606d804ab3fad8 --- src/installer/corehost/cli/args.h | 1 - src/installer/corehost/cli/fx_definition.cpp | 5 +- src/installer/corehost/cli/fx_definition.h | 2 +- src/installer/corehost/cli/fxr/fx_muxer.cpp | 19 ++- src/installer/corehost/cli/runtime_config.cpp | 176 +++++++++++++++++---- src/installer/corehost/cli/runtime_config.h | 77 ++++++++- .../GivenThatICareAboutMultilevelSharedFxLookup.cs | 105 ++++++++++-- 7 files changed, 324 insertions(+), 61 deletions(-) diff --git a/src/installer/corehost/cli/args.h b/src/installer/corehost/cli/args.h index 49218fb..02316a6 100644 --- a/src/installer/corehost/cli/args.h +++ b/src/installer/corehost/cli/args.h @@ -14,7 +14,6 @@ struct probe_config_t { pal::string_t probe_dir; bool patch_roll_fwd; - bool prerelease_roll_fwd; const deps_json_t* probe_deps_json; int fx_level; diff --git a/src/installer/corehost/cli/fx_definition.cpp b/src/installer/corehost/cli/fx_definition.cpp index 1a8274a..481d289 100644 --- a/src/installer/corehost/cli/fx_definition.cpp +++ b/src/installer/corehost/cli/fx_definition.cpp @@ -26,10 +26,11 @@ fx_definition_t::fx_definition_t( void fx_definition_t::parse_runtime_config( const pal::string_t& path, const pal::string_t& dev_path, - const runtime_config_t* defaults + const runtime_config_t* higher_layer_config, + const runtime_config_t* app_config ) { - m_runtime_config.parse(path, dev_path, defaults); + m_runtime_config.parse(path, dev_path, higher_layer_config, app_config); } void fx_definition_t::parse_deps() diff --git a/src/installer/corehost/cli/fx_definition.h b/src/installer/corehost/cli/fx_definition.h index 29f7e8d..3819e0f 100644 --- a/src/installer/corehost/cli/fx_definition.h +++ b/src/installer/corehost/cli/fx_definition.h @@ -24,7 +24,7 @@ public: const pal::string_t& get_found_version() const { return m_found_version; } const pal::string_t& get_dir() const { return m_dir; } const runtime_config_t& get_runtime_config() const { return m_runtime_config; } - void parse_runtime_config(const pal::string_t& path, const pal::string_t& dev_path, const runtime_config_t* defaults); + void parse_runtime_config(const pal::string_t& path, const pal::string_t& dev_path, const runtime_config_t* higher_layer_config, const runtime_config_t* app_config); const pal::string_t& get_deps_file() const { return m_deps_file; } void set_deps_file(const pal::string_t value) { m_deps_file = value; } diff --git a/src/installer/corehost/cli/fxr/fx_muxer.cpp b/src/installer/corehost/cli/fxr/fx_muxer.cpp index 0002d7e..f767abd 100644 --- a/src/installer/corehost/cli/fxr/fx_muxer.cpp +++ b/src/installer/corehost/cli/fxr/fx_muxer.cpp @@ -1098,7 +1098,7 @@ int read_config( get_runtime_config_paths_from_arg(runtime_config, &config_file, &dev_config_file); } - app.parse_runtime_config(config_file, dev_config_file, nullptr); + app.parse_runtime_config(config_file, dev_config_file, nullptr, nullptr); if (!app.get_runtime_config().is_valid()) { trace::error(_X("Invalid runtimeconfig.json [%s] [%s]"), app.get_runtime_config().get_path().c_str(), app.get_runtime_config().get_dev_path().c_str()); @@ -1151,19 +1151,22 @@ int fx_muxer_t::read_config_and_execute( return rc; } - auto config = app->get_runtime_config(); + auto app_config = app->get_runtime_config(); // 'Roll forward on no candidate fx' is set to 1 (roll_fwd_on_no_candidate_fx_option::minor) by default. It can be changed through: - // 1. Command line argument (--roll-forward-on-no-candidate-fx). Only defaults the app's config. - // 2. Runtimeconfig json file ('rollForwardOnNoCandidateFx' property), which is used as a default for lower level frameworks if they don't specify a value. - // 3. DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX env var. Only defaults the app's config. - // The conflicts will be resolved by following the priority rank described above (from 1 to 3). + // 1. Command line argument (--roll-forward-on-no-candidate-fx). + // 2. Runtimeconfig json file ('rollForwardOnNoCandidateFx' property in "framework" section:). + // 3. Runtimeconfig json file ('rollForwardOnNoCandidateFx' property), which is used as a default for lower level frameworks if they don't specify a value. + // 4. DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX env var. Only defaults the app's config. + // The conflicts will be resolved by following the priority rank described above (from 1 to 4). // The env var condition is verified in the config file processing if (!roll_fwd_on_no_candidate_fx.empty()) { - config.set_roll_fwd_on_no_candidate_fx(static_cast(pal::xtoi(roll_fwd_on_no_candidate_fx.c_str()))); + app_config.force_roll_fwd_on_no_candidate_fx(static_cast(pal::xtoi(roll_fwd_on_no_candidate_fx.c_str()))); } + auto config = app_config; + // Determine additional deps pal::string_t additional_deps_serialized; bool is_portable = config.get_portable(); @@ -1193,7 +1196,7 @@ int fx_muxer_t::read_config_and_execute( pal::string_t config_file; pal::string_t dev_config_file; get_runtime_config_paths(fx->get_dir(), config.get_fx_name(), &config_file, &dev_config_file); - fx->parse_runtime_config(config_file, dev_config_file, &config); + fx->parse_runtime_config(config_file, dev_config_file, &config, &app_config); config = fx->get_runtime_config(); if (!config.is_valid()) diff --git a/src/installer/corehost/cli/runtime_config.cpp b/src/installer/corehost/cli/runtime_config.cpp index 6ed5b94..020b4e2 100644 --- a/src/installer/corehost/cli/runtime_config.cpp +++ b/src/installer/corehost/cli/runtime_config.cpp @@ -8,26 +8,36 @@ #include "runtime_config.h" #include + +// The semantics of applying the runtimeconfig.json values follows, in the following steps from +// first to last, where last always wins. These steps are also annotated in the code here. +// 1a) If the app, apply the default values from the environment +// 1b) If the framework, apply the default values from the higher framework +// 2) Apply the values in the current "framework" section; use these as defaults for current layer +// 3) Apply the values in the app's "additionalFrameworks" section for the targeted framework +// 4) Apply the readonly values which are the settings that can't be changed by lower layers + runtime_config_t::runtime_config_t() : m_patch_roll_fwd(true) - , m_prerelease_roll_fwd(false) , m_roll_fwd_on_no_candidate_fx(roll_fwd_on_no_candidate_fx_option::minor) , m_portable(false) , m_valid(false) { } -void runtime_config_t::parse(const pal::string_t& path, const pal::string_t& dev_path, const runtime_config_t* defaults) +void runtime_config_t::parse(const pal::string_t& path, const pal::string_t& dev_path, const runtime_config_t* higher_layer_config, const runtime_config_t* app_config) { m_path = path; m_dev_path = dev_path; - // Apply the defaults from previous config. - if (defaults) + // Step #1: apply the defaults from the environment (for the app) or previous\higher layer (for a framework) + if (higher_layer_config != nullptr) { - m_patch_roll_fwd = defaults->m_patch_roll_fwd; - m_prerelease_roll_fwd = defaults->m_prerelease_roll_fwd; - m_roll_fwd_on_no_candidate_fx = defaults->m_roll_fwd_on_no_candidate_fx; + // Copy the previous defaults so we can default the next framework; these may be changed by the current fx + copy_framework_settings_to(higher_layer_config->m_fx_global, m_fx_global); + + // Apply the defaults + set_effective_values(m_fx_global); } else { @@ -37,15 +47,20 @@ void runtime_config_t::parse(const pal::string_t& path, const pal::string_t& dev if (pal::getenv(_X("DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX"), &env_no_candidate)) { m_roll_fwd_on_no_candidate_fx = static_cast(pal::xtoi(env_no_candidate.c_str())); + m_fx_global.set_roll_fwd_on_no_candidate_fx(m_roll_fwd_on_no_candidate_fx); } } - m_valid = ensure_parsed(); + m_valid = ensure_parsed(app_config); - // Overwrite values that can't be changed from previous config. - if (defaults) + if (m_valid) { - m_tfm = defaults->m_tfm; + // Step #4: apply the readonly values + if (app_config != nullptr) + { + m_tfm = app_config->m_tfm; + set_effective_values(app_config->m_fx_readonly); + } } trace::verbose(_X("Runtime config [%s] is valid=[%d]"), path.c_str(), m_valid); @@ -95,18 +110,14 @@ bool runtime_config_t::parse_opts(const json_value& opts) if (patch_roll_fwd != opts_obj.end()) { m_patch_roll_fwd = patch_roll_fwd->second.as_bool(); - } - - auto prerelease_roll_fwd = opts_obj.find(_X("preReleaseRollForward")); - if (prerelease_roll_fwd != opts_obj.end()) - { - m_prerelease_roll_fwd = prerelease_roll_fwd->second.as_bool(); + m_fx_global.set_patch_roll_fwd(m_patch_roll_fwd); } auto roll_fwd_on_no_candidate_fx = opts_obj.find(_X("rollForwardOnNoCandidateFx")); if (roll_fwd_on_no_candidate_fx != opts_obj.end()) { m_roll_fwd_on_no_candidate_fx = static_cast(roll_fwd_on_no_candidate_fx->second.as_integer()); + m_fx_global.set_roll_fwd_on_no_candidate_fx(m_roll_fwd_on_no_candidate_fx); } auto tfm = opts_obj.find(_X("tfm")); @@ -115,6 +126,8 @@ bool runtime_config_t::parse_opts(const json_value& opts) m_tfm = tfm->second.as_string(); } + // Step #2: apply the "framework" section + auto framework = opts_obj.find(_X("framework")); if (framework == opts_obj.end()) { @@ -124,8 +137,73 @@ bool runtime_config_t::parse_opts(const json_value& opts) m_portable = true; const auto& fx_obj = framework->second.as_object(); + m_fx_name = fx_obj.at(_X("name")).as_string(); - m_fx_ver = fx_obj.at(_X("version")).as_string(); + + bool rc = parse_framework(fx_obj); + if (rc) + { + set_effective_values(m_fx); + } + + return rc; +} + +void runtime_config_t::set_effective_values(const runtime_config_framework_t& overrides) +{ + if (overrides.get_fx_ver() != nullptr) + { + m_fx_ver = *overrides.get_fx_ver(); + } + + if (overrides.get_roll_fwd_on_no_candidate_fx() != nullptr) + { + m_roll_fwd_on_no_candidate_fx = *overrides.get_roll_fwd_on_no_candidate_fx(); + } + + if (overrides.get_patch_roll_fwd() != nullptr) + { + m_patch_roll_fwd = *overrides.get_patch_roll_fwd(); + } +} + +/*static*/ void runtime_config_t::copy_framework_settings_to(const runtime_config_framework_t& from, runtime_config_framework_t& to) +{ + if (from.get_fx_ver() != nullptr) + { + to.set_fx_ver(*from.get_fx_ver()); + } + + if (from.get_roll_fwd_on_no_candidate_fx() != nullptr) + { + to.set_roll_fwd_on_no_candidate_fx(*from.get_roll_fwd_on_no_candidate_fx()); + } + + if (from.get_patch_roll_fwd() != nullptr) + { + to.set_patch_roll_fwd(*from.get_patch_roll_fwd()); + } +} + +bool runtime_config_t::parse_framework(const json_object& fx_obj) +{ + auto fx_ver = fx_obj.find(_X("version")); + if (fx_ver != fx_obj.end()) + { + m_fx.set_fx_ver(fx_ver->second.as_string()); + } + + auto patch_roll_fwd = fx_obj.find(_X("applyPatches")); + if (patch_roll_fwd != fx_obj.end()) + { + m_fx.set_patch_roll_fwd(patch_roll_fwd->second.as_bool()); + } + + auto roll_fwd_on_no_candidate_fx = fx_obj.find(_X("rollForwardOnNoCandidateFx")); + if (roll_fwd_on_no_candidate_fx != fx_obj.end()) + { + m_fx.set_roll_fwd_on_no_candidate_fx(static_cast(roll_fwd_on_no_candidate_fx->second.as_integer())); + } return true; } @@ -174,7 +252,7 @@ bool runtime_config_t::ensure_dev_config_parsed() return true; } -bool runtime_config_t::ensure_parsed() +bool runtime_config_t::ensure_parsed(const runtime_config_t* app_config) { trace::verbose(_X("Attempting to read runtime config: %s"), m_path.c_str()); if (!ensure_dev_config_parsed()) @@ -201,6 +279,7 @@ bool runtime_config_t::ensure_parsed() trace::verbose(_X("UTF-8 BOM skipped while reading [%s]"), m_path.c_str()); } + bool rc = true; try { const auto root = json_value::parse(file); @@ -208,7 +287,52 @@ bool runtime_config_t::ensure_parsed() const auto iter = json.find(_X("runtimeOptions")); if (iter != json.end()) { - parse_opts(iter->second); + rc = parse_opts(iter->second); + + if (rc) + { + if (app_config == nullptr) + { + // If there is no app_config yet, then we are the app + // Read the additionalFrameworks section so we can apply later when each framework's runtimeconfig is read + const auto& opts_obj = iter->second.as_object(); + const auto iter = opts_obj.find(_X("additionalFrameworks")); + if (iter != opts_obj.end()) + { + const auto& additional_frameworks = iter->second.as_array(); + for (const auto& fx : additional_frameworks) + { + runtime_config_t fx_overrides; + const auto& fx_obj = fx.as_object(); + fx_overrides.m_fx_name = fx_obj.at(_X("name")).as_string(); + if (fx_overrides.m_fx_name.length() == 0) + { + trace::verbose(_X("No framework name in additionalFrameworks section.")); + rc = false; + break; + } + + rc = fx_overrides.parse_framework(fx_obj); + if (!rc) + { + break; + } + + m_additional_frameworks[fx_overrides.m_fx_name] = fx_overrides.m_fx; + } + } + + // Follow through to step #3 in case the framework is also specified in the additionalFrameworks section + app_config = this; + } + + // Step #3: apply the values from "additionalFrameworks" + auto overrides = app_config->m_additional_frameworks.find(m_fx_name); + if (overrides != app_config->m_additional_frameworks.end()) + { + set_effective_values(overrides->second); + } + } } } catch (const std::exception& je) @@ -218,7 +342,8 @@ bool runtime_config_t::ensure_parsed() trace::error(_X("A JSON parsing exception occurred in [%s]: %s"), m_path.c_str(), jes.c_str()); return false; } - return true; + + return rc; } const pal::string_t& runtime_config_t::get_tfm() const @@ -245,22 +370,17 @@ bool runtime_config_t::get_patch_roll_fwd() const return m_patch_roll_fwd; } -bool runtime_config_t::get_prerelease_roll_fwd() const -{ - assert(m_valid); - return m_prerelease_roll_fwd; -} - roll_fwd_on_no_candidate_fx_option runtime_config_t::get_roll_fwd_on_no_candidate_fx() const { assert(m_valid); return m_roll_fwd_on_no_candidate_fx; } -void runtime_config_t::set_roll_fwd_on_no_candidate_fx(roll_fwd_on_no_candidate_fx_option value) +void runtime_config_t::force_roll_fwd_on_no_candidate_fx(roll_fwd_on_no_candidate_fx_option value) { assert(m_valid); m_roll_fwd_on_no_candidate_fx = value; + m_fx_readonly.set_roll_fwd_on_no_candidate_fx(value); } bool runtime_config_t::get_portable() const diff --git a/src/installer/corehost/cli/runtime_config.h b/src/installer/corehost/cli/runtime_config.h index 32aa258..3204717 100644 --- a/src/installer/corehost/cli/runtime_config.h +++ b/src/installer/corehost/cli/runtime_config.h @@ -10,6 +10,7 @@ #include "cpprest/json.h" typedef web::json::value json_value; +typedef web::json::object json_object; enum class roll_fwd_on_no_candidate_fx_option { @@ -18,45 +19,109 @@ enum class roll_fwd_on_no_candidate_fx_option major_or_minor }; +class runtime_config_framework_t +{ +public: + // Uses a "nullable" pattern until we add such a type + + runtime_config_framework_t() + : has_fx_ver(false) + , has_roll_fwd_on_no_candidate_fx(false) + , has_patch_roll_fwd(false) + , fx_ver(_X("")) + , patch_roll_fwd(false) + , roll_fwd_on_no_candidate_fx((roll_fwd_on_no_candidate_fx_option)0) + { } + + const pal::string_t* get_fx_ver() const + { + return (has_fx_ver ? &fx_ver : nullptr); + } + void set_fx_ver(pal::string_t value) + { + has_fx_ver = true; + fx_ver = value; + } + + const bool* get_patch_roll_fwd() const + { + return (has_patch_roll_fwd ? &patch_roll_fwd : nullptr); + } + void set_patch_roll_fwd(bool value) + { + has_patch_roll_fwd = true; + patch_roll_fwd = value; + } + + const roll_fwd_on_no_candidate_fx_option* get_roll_fwd_on_no_candidate_fx() const + { + return (has_roll_fwd_on_no_candidate_fx ? &roll_fwd_on_no_candidate_fx : nullptr); + } + void set_roll_fwd_on_no_candidate_fx(roll_fwd_on_no_candidate_fx_option value) + { + has_roll_fwd_on_no_candidate_fx = true; + roll_fwd_on_no_candidate_fx = value; + } + +private: + bool has_fx_ver; + bool has_patch_roll_fwd; + bool has_roll_fwd_on_no_candidate_fx; + + pal::string_t fx_ver; + bool patch_roll_fwd; + roll_fwd_on_no_candidate_fx_option roll_fwd_on_no_candidate_fx; +}; + class runtime_config_t { public: runtime_config_t(); - void parse(const pal::string_t& path, const pal::string_t& dev_path, const runtime_config_t* defaults); + void parse(const pal::string_t& path, const pal::string_t& dev_path, const runtime_config_t* higher_layer_config, const runtime_config_t* app_config); bool is_valid() const { return m_valid; } const pal::string_t& get_path() const { return m_path; } const pal::string_t& get_dev_path() const { return m_dev_path; } const pal::string_t& get_fx_version() const; - void set_fx_version(const pal::string_t& value); const pal::string_t& get_fx_name() const; const pal::string_t& get_tfm() const; const std::list& get_probe_paths() const; bool get_patch_roll_fwd() const; - bool get_prerelease_roll_fwd() const; roll_fwd_on_no_candidate_fx_option get_roll_fwd_on_no_candidate_fx() const; - void set_roll_fwd_on_no_candidate_fx(roll_fwd_on_no_candidate_fx_option value); + void force_roll_fwd_on_no_candidate_fx(roll_fwd_on_no_candidate_fx_option value); bool get_portable() const; bool parse_opts(const json_value& opts); void combine_properties(std::unordered_map& combined_properties) const; private: - bool ensure_parsed(); + bool ensure_parsed(const runtime_config_t* defaults); bool ensure_dev_config_parsed(); std::unordered_map m_properties; + std::unordered_map m_additional_frameworks; + runtime_config_framework_t m_fx_global; // the settings that will be applied to the next lower layer; does not include version (Step #1) + runtime_config_framework_t m_fx; // the settings in the current "framework" section (Step #3) + runtime_config_framework_t m_fx_readonly; // the settings that can't be changed by lower layers (Step #4) std::vector m_prop_keys; std::vector m_prop_values; std::list m_probe_paths; + pal::string_t m_tfm; pal::string_t m_fx_name; + + // These are the effective settings pal::string_t m_fx_ver; bool m_patch_roll_fwd; - bool m_prerelease_roll_fwd; roll_fwd_on_no_candidate_fx_option m_roll_fwd_on_no_candidate_fx; pal::string_t m_dev_path; pal::string_t m_path; bool m_portable; bool m_valid; + +private: + bool parse_framework(const json_object& fx_obj); + void set_effective_values(const runtime_config_framework_t& overrides); + static void copy_framework_settings_to(const runtime_config_framework_t& from, runtime_config_framework_t& to); + }; #endif // __RUNTIME_CONFIG_H__ \ No newline at end of file diff --git a/src/installer/test/HostActivationTests/GivenThatICareAboutMultilevelSharedFxLookup.cs b/src/installer/test/HostActivationTests/GivenThatICareAboutMultilevelSharedFxLookup.cs index 63f254b..5799695 100644 --- a/src/installer/test/HostActivationTests/GivenThatICareAboutMultilevelSharedFxLookup.cs +++ b/src/installer/test/HostActivationTests/GivenThatICareAboutMultilevelSharedFxLookup.cs @@ -716,7 +716,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku } [Fact] - public void Multiple_SharedFxLookup_Propagated_RuntimeConfig_Values() + public void Multiple_SharedFxLookup_Propagated_Global_RuntimeConfig_Values() { var fixture = PreviouslyBuiltAndRestoredPortableTestProjectFixture .Copy(); @@ -806,7 +806,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku } [Fact] - public void Multiple_SharedFxLookup_UberFx_Deps_Overrides_NetCoreApp() + public void Multiple_SharedFxLookup_Propagated_Additional_Framework_RuntimeConfig_Values() { var fixture = PreviouslyBuiltAndRestoredPortableTestProjectFixture .Copy(); @@ -815,19 +815,21 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku var appDll = fixture.TestProject.AppDll; string runtimeConfig = Path.Combine(fixture.TestProject.OutputDirectory, "SharedFxLookupPortableApp.runtimeconfig.json"); - SetRuntimeConfigJson(runtimeConfig, "7777.0.0", null, useUberFramework: true); + + var additionalfxs = new JArray(); + additionalfxs.Add(GetAdditionalFramework("Microsoft.NETCore.App", "9999.1.0", applyPatches: false, rollForwardOnNoCandidateFx: 0)); + SetRuntimeConfigJson(runtimeConfig, "7777.0.0", null, useUberFramework: true, additionalFrameworks : additionalfxs); // Add versions in the exe folders - AddAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.0"); - AddAvailableSharedUberFxVersions(_exeSharedUberFxBaseDir, "9999.0.0", null, "7777.0.0"); + AddAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.1.0"); + AddAvailableSharedUberFxVersions(_exeSharedUberFxBaseDir, "9999.5.5", "UberValue", "7777.0.0"); - // The System.Collections.Immutable.dll is located in the UberFramework and NetCoreApp - // The System.Collections.dll is only located in NetCoreApp - // Version: NetCoreApp 9999.0.0 + // Version: NetCoreApp 9999.5.5 (in framework section) + // NetCoreApp 9999.1.0 (in app's additionalFrameworks section) // UberFramework 7777.0.0 - // Exe: NetCoreApp 9999.0.0 + // Exe: NetCoreApp 9999.1.0 // UberFramework 7777.0.0 - // Expected: 9999.0.0 + // Expected: 9999.1.0 // 7777.0.0 dotnet.Exec(appDll) .WorkingDirectory(_currentWorkingDir) @@ -838,13 +840,59 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku .Should() .Pass() .And - .HaveStdErrContaining(Path.Combine("7777.0.0", "System.Collections.Immutable.dll")) + .HaveStdErrContaining(Path.Combine(_exeSelectedMessage, "9999.1.0")) + .And + .HaveStdErrContaining(Path.Combine(_exeFoundUberFxMessage, "7777.0.0")); + + // Change the additionalFrameworks to allow roll forward, overriding Uber's global section and ignoring Uber's framework section + additionalfxs.Clear(); + additionalfxs.Add(GetAdditionalFramework("Microsoft.NETCore.App", "9999.0.0", applyPatches: false, rollForwardOnNoCandidateFx: 1)); + additionalfxs.Add(GetAdditionalFramework("UberFx", "7777.0.0", applyPatches: false, rollForwardOnNoCandidateFx: 0)); + SetRuntimeConfigJson(runtimeConfig, "7777.0.0", rollFwdOnNoCandidateFx:0, useUberFramework: true, additionalFrameworks: additionalfxs); + + // Version: NetCoreApp 9999.5.5 (in framework section) + // NetCoreApp 9999.0.0 (in app's additionalFrameworks section) + // UberFramework 7777.0.0 + // UberFramework 7777.0.0 (in app's additionalFrameworks section) + // 'Roll forward on no candidate fx' disabled through env var + // 'Roll forward on no candidate fx' disabled through Uber's global runtimeconfig + // 'Roll forward on no candidate fx' enabled for NETCore.App enabled through additionalFrameworks section + // Exe: NetCoreApp 9999.1.0 + // UberFramework 7777.0.0 + // Expected: 9999.1.0 + // 7777.0.0 + dotnet.Exec(appDll) + .WorkingDirectory(_currentWorkingDir) + .EnvironmentVariable("COREHOST_TRACE", "1") + .EnvironmentVariable("DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX", "0") + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should() + .Pass() .And - .HaveStdErrContaining(Path.Combine("9999.0.0", "System.Collections.dll")) + .HaveStdErrContaining(Path.Combine(_exeSelectedMessage, "9999.1.0")) .And - .NotHaveStdErrContaining(Path.Combine("9999.0.0", "System.Collections.Immutable.dll")); + .HaveStdErrContaining(Path.Combine(_exeFoundUberFxMessage, "7777.0.0")); - DeleteAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.0"); + // Same as previous except use of '--roll-forward-on-no-candidate-fx' + // Expected: Fail since '--roll-forward-on-no-candidate-fx' should apply to all layers + dotnet.Exec( + "exec", + "--roll-forward-on-no-candidate-fx", "0", + appDll) + .WorkingDirectory(_currentWorkingDir) + .EnvironmentVariable("COREHOST_TRACE", "1") + .EnvironmentVariable("DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX", "1") + .CaptureStdOut() + .CaptureStdErr() + .Execute() + .Should() + .Fail() + .And + .HaveStdErrContaining("It was not possible to find any compatible framework version"); + + DeleteAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.1.0"); DeleteAvailableSharedFxVersions(_exeSharedUberFxBaseDir, "7777.0.0"); } @@ -1070,7 +1118,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku * } * } */ - private void SetRuntimeConfigJson(string destFile, string version, int? rollFwdOnNoCandidateFx = null, string testConfigPropertyValue = null, bool? useUberFramework = false) + private void SetRuntimeConfigJson(string destFile, string version, int? rollFwdOnNoCandidateFx = null, string testConfigPropertyValue = null, bool? useUberFramework = false, JArray additionalFrameworks = null) { string name = useUberFramework.HasValue && useUberFramework.Value ? "Microsoft.UberFramework" : "Microsoft.NETCore.App"; @@ -1099,6 +1147,11 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku ); } + if (additionalFrameworks != null) + { + runtimeOptions.Add("additionalFrameworks", additionalFrameworks); + } + FileInfo file = new FileInfo(destFile); if (!file.Directory.Exists) { @@ -1110,6 +1163,28 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku File.WriteAllText(destFile, json.ToString()); } + static private JObject GetAdditionalFramework(string fxName, string fxVersion, bool? applyPatches, int? rollForwardOnNoCandidateFx) + { + var jobject = new JObject(new JProperty("name", fxName)); + + if (fxVersion != null) + { + jobject.Add(new JProperty("version", fxVersion)); + } + + if (applyPatches.HasValue) + { + jobject.Add(new JProperty("applyPatches", applyPatches.Value)); + } + + if (rollForwardOnNoCandidateFx.HasValue) + { + jobject.Add(new JProperty("rollForwardOnNoCandidateFx", rollForwardOnNoCandidateFx)); + } + + return jobject; + } + static private void CreateUberFrameworkArtifacts(string builtSharedFxDir, string builtSharedUberFxDir, string assemblyVersion = null, string fileVersion = null) { DirectoryInfo dir = new DirectoryInfo(builtSharedUberFxDir); -- 2.7.4