Add graph support for multiple shared frameworks (dotnet/core-setup#4538)
authorSteve Harter <steveharter@users.noreply.github.com>
Thu, 13 Sep 2018 21:18:20 +0000 (16:18 -0500)
committerGitHub <noreply@github.com>
Thu, 13 Sep 2018 21:18:20 +0000 (16:18 -0500)
Commit migrated from https://github.com/dotnet/core-setup/commit/8fe49b4211abaa649784966a4f6b55a38a237d9e

26 files changed:
src/installer/corehost/cli/deps_resolver.cpp
src/installer/corehost/cli/fx_definition.cpp
src/installer/corehost/cli/fx_definition.h
src/installer/corehost/cli/fx_reference.cpp [new file with mode: 0644]
src/installer/corehost/cli/fx_reference.h [new file with mode: 0644]
src/installer/corehost/cli/fxr/CMakeLists.txt
src/installer/corehost/cli/fxr/framework_info.cpp
src/installer/corehost/cli/fxr/framework_info.h
src/installer/corehost/cli/fxr/fx_muxer.cpp
src/installer/corehost/cli/fxr/fx_muxer.h
src/installer/corehost/cli/fxr/fx_muxer.messages.cpp [new file with mode: 0644]
src/installer/corehost/cli/fxr/fx_ver.cpp
src/installer/corehost/cli/fxr/fx_ver.h
src/installer/corehost/cli/fxr/sdk_info.cpp
src/installer/corehost/cli/fxr/sdk_resolver.cpp
src/installer/corehost/cli/hostpolicy/CMakeLists.txt
src/installer/corehost/cli/libhost.cpp
src/installer/corehost/cli/roll_fwd_on_no_candidate_fx_option.h [new file with mode: 0644]
src/installer/corehost/cli/runtime_config.cpp
src/installer/corehost/cli/runtime_config.h
src/installer/corehost/corehost.cpp
src/installer/corehost/error_codes.h
src/installer/test/HostActivationTests/GivenThatICareAboutLightupAppActivation.cs
src/installer/test/HostActivationTests/GivenThatICareAboutMultilevelSharedFxLookup.DepsVersion.cs
src/installer/test/HostActivationTests/GivenThatICareAboutMultilevelSharedFxLookup.cs
src/installer/test/HostActivationTests/SharedFramework.cs

index 0d4bb8d..c2e0669 100644 (file)
@@ -629,8 +629,8 @@ void deps_resolver_t::resolve_additional_deps(const hostpolicy_init_t& init)
         {
             for (int i = 1; i < m_fx_definitions.size(); ++i)
             {
-                fx_ver_t most_compatible_deps_folder_version(-1, -1, -1);
-                fx_ver_t framework_found_version(-1, -1, -1);
+                fx_ver_t most_compatible_deps_folder_version;
+                fx_ver_t framework_found_version;
                 fx_ver_t::parse(m_fx_definitions[i]->get_found_version(), &framework_found_version);
 
                 // We'll search deps directories in 'base_dir'/shared/fx_name/ for closest compatible patch version
@@ -643,7 +643,7 @@ void deps_resolver_t::resolve_additional_deps(const hostpolicy_init_t& init)
 
                 for (pal::string_t dir : deps_dirs)
                 {
-                    fx_ver_t ver(-1, -1, -1);
+                    fx_ver_t ver;
                     if (fx_ver_t::parse(dir, &ver))
                     {
                         if (ver > most_compatible_deps_folder_version &&
@@ -656,7 +656,7 @@ void deps_resolver_t::resolve_additional_deps(const hostpolicy_init_t& init)
                     }
                 }
 
-                if (most_compatible_deps_folder_version == fx_ver_t(-1, -1, -1))
+                if (most_compatible_deps_folder_version == fx_ver_t())
                 {
                     trace::verbose(_X("No additional deps directory less than or equal to [%s] found with same major and minor version."), framework_found_version.as_str().c_str());
                 }
index 64e1487..342a38c 100644 (file)
@@ -26,11 +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* higher_layer_config,
-    const runtime_config_t* app_config
+    const fx_reference_t& fx_ref,
+    const fx_reference_t& override_settings
 )
 {
-    m_runtime_config.parse(path, dev_path, higher_layer_config, app_config);
+    m_runtime_config.parse(path, dev_path, fx_ref, override_settings);
 }
 
 void fx_definition_t::parse_deps()
index c4d38ac..4237bf1 100644 (file)
@@ -12,7 +12,6 @@ class fx_definition_t
 {
 public:
     fx_definition_t();
-
     fx_definition_t(
         const pal::string_t& name,
         const pal::string_t& dir,
@@ -24,7 +23,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* higher_layer_config, const runtime_config_t* app_config);
+    void parse_runtime_config(const pal::string_t& path, const pal::string_t& dev_path, const fx_reference_t& fx_ref, const fx_reference_t& override_settings);
 
     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/fx_reference.cpp b/src/installer/corehost/cli/fx_reference.cpp
new file mode 100644 (file)
index 0000000..906d3f7
--- /dev/null
@@ -0,0 +1,104 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#include "pal.h"
+#include "fx_ver.h"
+#include "fx_reference.h"
+#include "roll_fwd_on_no_candidate_fx_option.h"
+
+bool fx_reference_t::is_roll_forward_compatible(const fx_ver_t& other) const
+{
+    // We expect the version to be <
+    assert(get_fx_version_number() < other);
+
+    if (get_fx_version_number() == other)
+    {
+        return true;
+    }
+
+    if (get_use_exact_version())
+    {
+        return false;
+    }
+
+    // Verify major roll forward
+    if (get_fx_version_number().get_major() != other.get_major()
+        && roll_fwd_on_no_candidate_fx != roll_fwd_on_no_candidate_fx_option::major)
+    {
+        return false;
+    }
+
+    // Verify minor roll forward
+    if (get_fx_version_number().get_minor() != other.get_minor()
+        && roll_fwd_on_no_candidate_fx != roll_fwd_on_no_candidate_fx_option::major
+        && roll_fwd_on_no_candidate_fx != roll_fwd_on_no_candidate_fx_option::minor)
+    {
+        return false;
+    }
+
+    // Verify patch roll forward
+    // We do not distinguish here whether a previous framework reference found a patch version based on:
+    //  - initial reference matching a patch version,
+    //  - or roll_fwd_on_no_candidate_fx_option=major\minor finding a compatible patch version as initial framework,
+    //  - or applyPatches=true finding a newer patch version
+    if (get_fx_version_number().get_patch() != other.get_patch()
+        && patch_roll_fwd == false
+        && roll_fwd_on_no_candidate_fx != roll_fwd_on_no_candidate_fx_option::major
+        && roll_fwd_on_no_candidate_fx != roll_fwd_on_no_candidate_fx_option::minor)
+    {
+        return false;
+    }
+
+    // Release cannot roll forward to pre-release
+    if (!get_fx_version_number().is_prerelease() && other.is_prerelease())
+    {
+        return false;
+    }
+
+    return true;
+}
+
+void fx_reference_t::apply_settings_from(const fx_reference_t& from)
+{
+    if (from.get_fx_version().length() > 0)
+    {
+        set_fx_version(from.get_fx_version());
+    }
+
+    const roll_fwd_on_no_candidate_fx_option* from_rollfwd = from.get_roll_fwd_on_no_candidate_fx();
+    if (from_rollfwd != nullptr)
+    {
+        set_roll_fwd_on_no_candidate_fx(*from_rollfwd);
+    }
+
+    const bool* from_patch = from.get_patch_roll_fwd();
+    if (from_patch != nullptr)
+    {
+        set_patch_roll_fwd(*from_patch);
+    }
+}
+
+void fx_reference_t::merge_roll_forward_settings_from(const fx_reference_t& from)
+{
+    const roll_fwd_on_no_candidate_fx_option* from_rollfwd = from.get_roll_fwd_on_no_candidate_fx();
+    if (from_rollfwd != nullptr)
+    {
+        const roll_fwd_on_no_candidate_fx_option* to_rollfwd = get_roll_fwd_on_no_candidate_fx();
+        if (to_rollfwd == nullptr ||
+            *from_rollfwd < *to_rollfwd)
+        {
+            set_roll_fwd_on_no_candidate_fx(*from_rollfwd);
+        }
+    }
+
+    const bool* from_patch = from.get_patch_roll_fwd();
+    if (from_patch != nullptr)
+    {
+        const bool* to_patch = get_patch_roll_fwd();
+        if (to_patch == nullptr ||
+            *from_patch == false)
+        {
+            set_patch_roll_fwd(*from_patch);
+        }
+    }
+}
diff --git a/src/installer/corehost/cli/fx_reference.h b/src/installer/corehost/cli/fx_reference.h
new file mode 100644 (file)
index 0000000..2fbb501
--- /dev/null
@@ -0,0 +1,107 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#ifndef __FX_REFERENCE_H__
+#define __FX_REFERENCE_H__
+
+#include <list>
+#include "pal.h"
+#include "fx_ver.h"
+#include "roll_fwd_on_no_candidate_fx_option.h"
+
+class fx_reference_t
+{
+public:
+    fx_reference_t()
+        : fx_name(_X(""))
+        , fx_version(_X(""))
+        , fx_version_number()
+        , has_patch_roll_fwd(false)
+        , patch_roll_fwd(false)
+        , has_roll_fwd_on_no_candidate_fx(false)
+        , use_exact_version(false)
+        , roll_fwd_on_no_candidate_fx((roll_fwd_on_no_candidate_fx_option)0)
+        { }
+
+    const pal::string_t& get_fx_name() const
+    {
+        return fx_name;
+    }
+    void set_fx_name(const pal::string_t& value)
+    {
+        fx_name = value;
+    }
+
+    const pal::string_t& get_fx_version() const
+    {
+        return fx_version;
+    }
+    void set_fx_version(const pal::string_t& value)
+    {
+        fx_version = value;
+
+        fx_ver_t::parse(fx_version, &fx_version_number);
+    }
+
+    const fx_ver_t& get_fx_version_number() const
+    {
+        return fx_version_number;
+    }
+
+    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 bool get_use_exact_version() const
+    {
+        return use_exact_version;
+    }
+    void set_use_exact_version(bool value)
+    {
+        use_exact_version = 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;
+    }
+
+    // Is the current version compatible with another instance with roll-forward semantics.
+    bool is_roll_forward_compatible(const fx_ver_t& other) const;
+
+    // Copy over any non-null values
+    void apply_settings_from(const fx_reference_t& from);
+
+    // Apply the most restrictive settings
+    void merge_roll_forward_settings_from(const fx_reference_t& from);
+
+private:
+    bool has_patch_roll_fwd;
+    bool patch_roll_fwd;
+
+    bool has_roll_fwd_on_no_candidate_fx;
+    roll_fwd_on_no_candidate_fx_option roll_fwd_on_no_candidate_fx;
+
+    bool use_exact_version;
+
+    pal::string_t fx_name;
+
+    pal::string_t fx_version;
+    fx_ver_t fx_version_number;
+};
+
+typedef std::vector<fx_reference_t> fx_reference_vector_t;
+typedef std::unordered_map<pal::string_t, fx_reference_t> fx_name_to_fx_reference_map_t;
+
+#endif // __FX_REFERENCE_H__
index 3d554bb..9d02b8a 100644 (file)
@@ -39,10 +39,12 @@ set(SOURCES
     ../json/casablanca/src/json/json_serialization.cpp
     ../json/casablanca/src/utilities/asyncrt_utils.cpp
     ../fx_definition.cpp
+    ../fx_reference.cpp
     ../version.cpp
     ./hostfxr.cpp
     ./fx_ver.cpp
     ./fx_muxer.cpp
+    ./fx_muxer.messages.cpp
     ./framework_info.cpp
     ./sdk_info.cpp
     ./sdk_resolver.cpp
index e4de608..74a9008 100644 (file)
@@ -23,18 +23,10 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info &
 }
 
 /*static*/ void framework_info::get_all_framework_infos(
-    host_mode_t mode,
     const pal::string_t& own_dir,
     const pal::string_t& fx_name,
     std::vector<framework_info>* framework_infos)
 {
-    // No FX resolution for mixed apps 
-    if (mode == host_mode_t::split_fx)
-    {
-        trace::verbose(_X("Split/FX mode detected. Not gathering shared FX locations"));
-        return;
-    }
-
     std::vector<pal::string_t> global_dirs;
     bool multilevel_lookup = multilevel_lookup_enabled();
 
@@ -89,7 +81,7 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info &
                     for (const auto& ver : versions)
                     {
                         // Make sure we filter out any non-version folders.
-                        fx_ver_t parsed(-1, -1, -1);
+                        fx_ver_t parsed;
                         if (fx_ver_t::parse(ver, &parsed, false))
                         {
                             trace::verbose(_X("Found FX version [%s]"), ver.c_str());
@@ -109,7 +101,7 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info &
 /*static*/ bool framework_info::print_all_frameworks(const pal::string_t& own_dir, const pal::string_t& leading_whitespace)
 {
     std::vector<framework_info> framework_infos;
-    get_all_framework_infos(host_mode_t::muxer, own_dir, _X(""), &framework_infos);
+    get_all_framework_infos(own_dir, _X(""), &framework_infos);
     for (framework_info info : framework_infos)
     {
         trace::println(_X("%s%s %s [%s]"), leading_whitespace.c_str(), info.name.c_str(), info.version.as_str().c_str(), info.path.c_str());
index 9aaea39..d502f95 100644 (file)
@@ -14,7 +14,6 @@ struct framework_info
         , version(version) { }
 
     static void get_all_framework_infos(
-        host_mode_t mode,
         const pal::string_t& own_dir,
         const pal::string_t& fx_name,
         std::vector<framework_info>* framework_infos);
index 27bda32..fb40f30 100644 (file)
@@ -9,6 +9,7 @@
 #include "framework_info.h"
 #include "fx_definition.h"
 #include "fx_muxer.h"
+#include "fx_reference.h"
 #include "fx_ver.h"
 #include "host_startup_info.h"
 #include "libhost.h"
@@ -23,8 +24,7 @@
 * When the framework is not found, display detailed error message
 *   about available frameworks and installation of new framework.
 */
-void handle_missing_framework_error(
-    host_mode_t mode,
+void fx_muxer_t::display_missing_framework_error(
     const pal::string_t& fx_name,
     const pal::string_t& fx_version,
     const pal::string_t& fx_dir,
@@ -35,14 +35,14 @@ void handle_missing_framework_error(
     if (fx_dir.length())
     {
         fx_ver_dirs = fx_dir;
-        framework_info::get_all_framework_infos(mode, get_directory(fx_dir), fx_name, &framework_infos);
+        framework_info::get_all_framework_infos(get_directory(fx_dir), fx_name, &framework_infos);
     }
     else
     {
         fx_ver_dirs = dotnet_root;
     }
 
-    framework_info::get_all_framework_infos(mode, dotnet_root, fx_name, &framework_infos);
+    framework_info::get_all_framework_infos(dotnet_root, fx_name, &framework_infos);
 
     // Display the error message about missing FX.
     if (fx_version.length())
@@ -64,7 +64,7 @@ void handle_missing_framework_error(
     // Gather the list of versions installed at the shared FX location.
     bool is_print_header = true;
 
-    for (framework_info info : framework_infos)
+    for (const framework_info& info : framework_infos)
     {
         // Print banner only once before printing the versions
         if (is_print_header)
@@ -274,7 +274,6 @@ bool fx_muxer_t::resolve_hostpolicy_dir(
     const fx_definition_vector_t& fx_definitions,
     const pal::string_t& app_candidate,
     const pal::string_t& specified_deps_file,
-    const pal::string_t& specified_fx_version,
     const std::vector<pal::string_t>& probe_realpaths,
     pal::string_t* impl_dir)
 {
@@ -345,7 +344,7 @@ bool fx_muxer_t::resolve_hostpolicy_dir(
             trace::error(_X("Failed to run as a self-contained app. If this should be a framework-dependent app, add the %s file specifying the appropriate framework."),
                 get_app(fx_definitions).get_runtime_config().get_path().c_str());
         }
-        else if (get_app(fx_definitions).get_runtime_config().get_fx_name().empty())
+        else if (get_app(fx_definitions).get_name().empty())
         {
             trace::error(_X("Failed to run as a self-contained app. If this should be a framework-dependent app, specify the appropriate framework in %s."),
                 get_app(fx_definitions).get_runtime_config().get_path().c_str());
@@ -367,7 +366,7 @@ fx_ver_t fx_muxer_t::resolve_framework_version(const std::vector<fx_ver_t>& vers
     {
         if (roll_fwd_on_no_candidate_fx != roll_fwd_on_no_candidate_fx_option::disabled)
         {
-            fx_ver_t next_lowest(-1, -1, -1);
+            fx_ver_t next_lowest;
 
             // Look for the least production version
             trace::verbose(_X("'Roll forward on no candidate fx' enabled with value [%d]. Looking for the least production greater than or equal to [%s]"),
@@ -385,11 +384,11 @@ fx_ver_t fx_muxer_t::resolve_framework_version(const std::vector<fx_ver_t>& vers
                             continue;
                         }
                     }
-                    next_lowest = (next_lowest == fx_ver_t(-1, -1, -1)) ? ver : std::min(next_lowest, ver);
+                    next_lowest = (next_lowest == fx_ver_t()) ? ver : std::min(next_lowest, ver);
                 }
             }
 
-            if (next_lowest == fx_ver_t(-1, -1, -1))
+            if (next_lowest == fx_ver_t())
             {
                 // Look for the least preview version
                 trace::verbose(_X("No production greater than or equal to [%s] found. Looking for the least preview greater than [%s]"),
@@ -407,12 +406,12 @@ fx_ver_t fx_muxer_t::resolve_framework_version(const std::vector<fx_ver_t>& vers
                                 continue;
                             }
                         }
-                        next_lowest = (next_lowest == fx_ver_t(-1, -1, -1)) ? ver : std::min(next_lowest, ver);
+                        next_lowest = (next_lowest == fx_ver_t()) ? ver : std::min(next_lowest, ver);
                     }
                 }
             }
 
-            if (next_lowest == fx_ver_t(-1, -1, -1))
+            if (next_lowest == fx_ver_t())
             {
                 trace::verbose(_X("No preview greater than or equal to [%s] found."), fx_ver.c_str());
             }
@@ -442,11 +441,11 @@ fx_ver_t fx_muxer_t::resolve_framework_version(const std::vector<fx_ver_t>& vers
     }
     else
     {
+        // pre-release has its own roll forward rules and ignores roll_fwd_on_no_candidate_fx and patch_roll_fwd
         for (const auto& ver : version_list)
         {
             trace::verbose(_X("Inspecting version... [%s]"), ver.as_str().c_str());
 
-            //both production and prerelease.
             if (ver.is_prerelease() && // prevent roll forward to production.
                 ver.get_major() == specified.get_major() &&
                 ver.get_minor() == specified.get_minor() &&
@@ -463,26 +462,21 @@ fx_ver_t fx_muxer_t::resolve_framework_version(const std::vector<fx_ver_t>& vers
 }
 
 fx_definition_t* fx_muxer_t::resolve_fx(
-    host_mode_t mode,
-    const runtime_config_t& config,
-    const pal::string_t& dotnet_dir,
-    const pal::string_t& specified_fx_version
+    const fx_reference_t& fx_ref,
+    const pal::string_t& oldest_requested_version,
+    const pal::string_t& dotnet_dir
 )
 {
-    // If invoking using FX dotnet.exe, use own directory.
-    if (mode == host_mode_t::split_fx)
-    {
-        return new fx_definition_t(config.get_fx_name(), dotnet_dir, pal::string_t(), pal::string_t());
-    }
-
-    assert(!config.get_fx_name().empty());
-    assert(!config.get_fx_version().empty());
+    assert(!fx_ref.get_fx_name().empty());
+    assert(!fx_ref.get_fx_version().empty());
+    assert(fx_ref.get_patch_roll_fwd() != nullptr);
+    assert(fx_ref.get_roll_fwd_on_no_candidate_fx() != nullptr);
 
     trace::verbose(_X("--- Resolving FX directory, name '%s' version '%s'"),
-        config.get_fx_name().c_str(), config.get_fx_version().c_str());
+        fx_ref.get_fx_name().c_str(), fx_ref.get_fx_version().c_str());
 
-    const auto fx_ver = specified_fx_version.empty() ? config.get_fx_version() : specified_fx_version;
-    fx_ver_t specified(-1, -1, -1);
+    const auto fx_ver = fx_ref.get_fx_version();
+    fx_ver_t specified;
     if (!fx_ver_t::parse(fx_ver, &specified, false))
     {
         trace::error(_X("The specified framework version '%s' could not be parsed"), fx_ver.c_str());
@@ -518,7 +512,7 @@ fx_definition_t* fx_muxer_t::resolve_fx(
 
     pal::string_t selected_fx_dir;
     pal::string_t selected_fx_version;
-    fx_ver_t selected_ver(-1, -1, -1);
+    fx_ver_t selected_ver;
 
     for (pal::string_t dir : hive_dir)
     {
@@ -526,15 +520,15 @@ fx_definition_t* fx_muxer_t::resolve_fx(
         trace::verbose(_X("Searching FX directory in [%s]"), fx_dir.c_str());
 
         append_path(&fx_dir, _X("shared"));
-        append_path(&fx_dir, config.get_fx_name().c_str());
+        append_path(&fx_dir, fx_ref.get_fx_name().c_str());
 
         bool do_roll_forward = false;
-        if (specified_fx_version.empty())
+        if (!fx_ref.get_use_exact_version())
         {
             if (!specified.is_prerelease())
             {
                 // If production and no roll forward use given version.
-                do_roll_forward = (config.get_patch_roll_fwd()) || (config.get_roll_fwd_on_no_candidate_fx() != roll_fwd_on_no_candidate_fx_option::disabled);
+                do_roll_forward = (*(fx_ref.get_patch_roll_fwd())) || (*(fx_ref.get_roll_fwd_on_no_candidate_fx()) != roll_fwd_on_no_candidate_fx_option::disabled);
             }
             else
             {
@@ -547,8 +541,8 @@ fx_definition_t* fx_muxer_t::resolve_fx(
 
         if (!do_roll_forward)
         {
-            trace::verbose(_X("Did not roll forward because specified version='%s', patch_roll_fwd=%d, roll_fwd_on_no_candidate_fx=%d, chose [%s]"),
-                specified_fx_version.c_str(), config.get_patch_roll_fwd(), config.get_roll_fwd_on_no_candidate_fx(), fx_ver.c_str());
+            trace::verbose(_X("Did not roll forward because patch_roll_fwd=%d, roll_fwd_on_no_candidate_fx=%d, use_exact_version=%d chose [%s]"),
+                *(fx_ref.get_patch_roll_fwd()), *(fx_ref.get_roll_fwd_on_no_candidate_fx()), fx_ref.get_use_exact_version(), fx_ver.c_str());
 
             append_path(&fx_dir, fx_ver.c_str());
             if (pal::directory_exists(fx_dir))
@@ -566,27 +560,27 @@ fx_definition_t* fx_muxer_t::resolve_fx(
 
             for (const auto& version : list)
             {
-                fx_ver_t ver(-1, -1, -1);
+                fx_ver_t ver;
                 if (fx_ver_t::parse(version, &ver, false))
                 {
                     version_list.push_back(ver);
                 }
             }
 
-            fx_ver_t resolved_ver = resolve_framework_version(version_list, fx_ver, specified, config.get_patch_roll_fwd(), config.get_roll_fwd_on_no_candidate_fx());
+            fx_ver_t resolved_ver = resolve_framework_version(version_list, fx_ver, specified, *(fx_ref.get_patch_roll_fwd()), *(fx_ref.get_roll_fwd_on_no_candidate_fx()));
 
             pal::string_t resolved_ver_str = resolved_ver.as_str();
             append_path(&fx_dir, resolved_ver_str.c_str());
 
             if (pal::directory_exists(fx_dir))
             {
-                if (selected_ver != fx_ver_t(-1, -1, -1))
+                if (selected_ver != fx_ver_t())
                 {
                     // Compare the previous hive_dir selection with the current hive_dir to see which one is the better match
                     std::vector<fx_ver_t> version_list;
                     version_list.push_back(resolved_ver);
                     version_list.push_back(selected_ver);
-                    resolved_ver = resolve_framework_version(version_list, fx_ver, specified, config.get_patch_roll_fwd(), config.get_roll_fwd_on_no_candidate_fx());
+                    resolved_ver = resolve_framework_version(version_list, fx_ver, specified, *(fx_ref.get_patch_roll_fwd()), *(fx_ref.get_roll_fwd_on_no_candidate_fx()));
                 }
 
                 if (resolved_ver != selected_ver)
@@ -608,7 +602,7 @@ fx_definition_t* fx_muxer_t::resolve_fx(
 
     trace::verbose(_X("Chose FX version [%s]"), selected_fx_dir.c_str());
 
-    return new fx_definition_t(config.get_fx_name(), selected_fx_dir, fx_ver, selected_fx_version);
+    return new fx_definition_t(fx_ref.get_fx_name(), selected_fx_dir, oldest_requested_version, selected_fx_version);
 }
 
 bool is_sdk_dir_present(const pal::string_t& dotnet_root)
@@ -828,7 +822,8 @@ int fx_muxer_t::parse_args(
 int read_config(
     fx_definition_t& app,
     const pal::string_t& app_candidate,
-    pal::string_t& runtime_config
+    pal::string_t& runtime_config,
+    const fx_reference_t& override_settings
 )
 {
     if (!runtime_config.empty() && !pal::realpath(&runtime_config))
@@ -850,7 +845,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, nullptr);
+    app.parse_runtime_config(config_file, dev_config_file, fx_reference_t(), override_settings);
     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());
@@ -860,6 +855,168 @@ int read_config(
     return 0;
 }
 
+int fx_muxer_t::soft_roll_forward_helper(
+    const fx_reference_t& newer,
+    const fx_reference_t& older,
+    bool older_is_hard_roll_forward,
+    fx_name_to_fx_reference_map_t& newest_references,
+    fx_name_to_fx_reference_map_t& oldest_references)
+{
+    const pal::string_t& fx_name = newer.get_fx_name();
+    fx_reference_t updated_newest = newer;
+
+    if (older.get_fx_version_number() == newer.get_fx_version_number())
+    {
+        updated_newest.merge_roll_forward_settings_from(older);
+        newest_references[fx_name] = updated_newest;
+        return 0;
+    }
+
+    if (older.is_roll_forward_compatible(newer.get_fx_version_number()))
+    {
+        updated_newest.merge_roll_forward_settings_from(older);
+        newest_references[fx_name] = updated_newest;
+
+        auto oldest = oldest_references[fx_name];
+        if (older.get_fx_version_number() < oldest.get_fx_version_number())
+        {
+            oldest_references[fx_name] = older;
+        }
+
+        if (older_is_hard_roll_forward)
+        {
+            display_retry_framework_trace(older, newer);
+            return FrameworkCompatRetry;
+        }
+
+        display_compatible_framework_trace(newer.get_fx_version(), older);
+        return 0;
+    }
+
+    // Error condition - not compatible with the other reference
+    display_incompatible_framework_error(newer.get_fx_version(), older);
+    return FrameworkCompatFailure;
+}
+
+// Peform a "soft" roll-forward meaning we don't read any physical framework folders
+// and just check if the older reference is compatible with the newer reference
+// with respect to roll-forward\applypatches.
+int fx_muxer_t::soft_roll_forward(
+    const fx_reference_t fx_ref, //byval to avoid side-effects with mutable newest_references and oldest_references
+    bool current_is_hard_roll_forward, // true if reference was obtained from a "real" roll-forward meaning it probed the disk to find the most compatible version
+    fx_name_to_fx_reference_map_t& newest_references,
+    fx_name_to_fx_reference_map_t& oldest_references)
+{
+    /*byval*/ fx_reference_t current_ref = newest_references[fx_ref.get_fx_name()];
+
+    // Perform soft "in-memory" roll-forwards
+    if (fx_ref.get_fx_version_number() >= current_ref.get_fx_version_number())
+    {
+        return soft_roll_forward_helper(fx_ref, current_ref, current_is_hard_roll_forward, newest_references, oldest_references);
+    }
+
+    assert(fx_ref.get_fx_version_number() < current_ref.get_fx_version_number());
+    return soft_roll_forward_helper(current_ref, fx_ref, false, newest_references, oldest_references);
+}
+
+int fx_muxer_t::read_framework(
+    const host_startup_info_t& host_info,
+    const fx_reference_t& override_settings,
+    const runtime_config_t& config,
+    fx_name_to_fx_reference_map_t& newest_references,
+    fx_name_to_fx_reference_map_t& oldest_references,
+    fx_definition_vector_t& fx_definitions)
+{
+    // Loop through each reference and update the list of newest references before we resolve_fx.
+    // This reconciles duplicate references to minimize the number of resolve retries.
+    for (const fx_reference_t& fx_ref : config.get_frameworks())
+    {
+        const pal::string_t& fx_name = fx_ref.get_fx_name();
+        auto temp_ref = newest_references.find(fx_name);
+        if (temp_ref == newest_references.end())
+        {
+            newest_references.insert({fx_name, fx_ref});
+            oldest_references.insert({fx_name, fx_ref});
+        }
+    }
+
+    int rc = 0;
+
+    // Loop through each reference and resolve the framework
+    for (const fx_reference_t& fx_ref : config.get_frameworks())
+    {
+        const pal::string_t& fx_name = fx_ref.get_fx_name();
+
+        auto existing_framework = std::find_if(
+            fx_definitions.begin(),
+            fx_definitions.end(),
+            [&](const std::unique_ptr<fx_definition_t>& fx) { return fx_name == fx->get_name(); });
+
+        if (existing_framework == fx_definitions.end())
+        {
+            // Perform a "soft" roll-forward meaning we don't read any physical framework folders yet
+            rc = soft_roll_forward(fx_ref, false, newest_references, oldest_references);
+            if (rc)
+            {
+                break; // Error case
+            }
+
+            const pal::string_t& oldest_requested_version = oldest_references[fx_name].get_fx_version();
+            fx_reference_t& newest_ref = newest_references[fx_name];
+
+            // Resolve the framwork against the the existing physical framework folders
+            fx_definition_t* fx = resolve_fx(newest_ref, oldest_requested_version, host_info.dotnet_root);
+            if (fx == nullptr)
+            {
+                display_missing_framework_error(fx_name, newest_ref.get_fx_version(), pal::string_t(), host_info.dotnet_root);
+                return FrameworkMissingFailure;
+            }
+
+            // Update the newest version based on the hard version found
+            newest_ref.set_fx_version(fx->get_found_version());
+
+            fx_definitions.push_back(std::unique_ptr<fx_definition_t>(fx));
+
+            // Recursively process the base frameworks
+            pal::string_t config_file;
+            pal::string_t dev_config_file;
+            get_runtime_config_paths(fx->get_dir(), fx_name, &config_file, &dev_config_file);
+            fx->parse_runtime_config(config_file, dev_config_file, newest_ref, override_settings);
+
+            runtime_config_t new_config = fx->get_runtime_config();
+            if (!new_config.is_valid())
+            {
+                trace::error(_X("Invalid framework config.json [%s]"), new_config.get_path().c_str());
+                return StatusCode::InvalidConfigFile;
+            }
+
+            rc = read_framework(host_info, override_settings, new_config, newest_references, oldest_references, fx_definitions);
+            if (rc)
+            {
+                break; // Error case
+            }
+        }
+        else
+        {
+            // Perform a "soft" roll-forward meaning we don't read any physical framework folders yet
+            rc = soft_roll_forward(fx_ref, true, newest_references, oldest_references);
+            if (rc)
+            {
+                break; // Error or retry case
+            }
+
+            fx_reference_t& newest_ref = newest_references[fx_name];
+            if (fx_ref.get_fx_version_number() == newest_ref.get_fx_version_number())
+            {
+                // Success but move it to the back (without calling dtors) so that lower-level frameworks come last including Microsoft.NetCore.App
+                std::rotate(existing_framework, existing_framework + 1, fx_definitions.end());
+            }
+        }
+    }
+
+    return rc;
+}
+
 int fx_muxer_t::read_config_and_execute(
     const pal::string_t& host_command,
     const host_startup_info_t& host_info,
@@ -879,7 +1036,6 @@ int fx_muxer_t::read_config_and_execute(
     pal::string_t opts_additional_deps = _X("--additional-deps");
     pal::string_t opts_runtime_config = _X("--runtimeconfig");
 
-    pal::string_t fx_version_specified;
     pal::string_t roll_fwd_on_no_candidate_fx;
     pal::string_t deps_file = get_last_known_arg(opts, opts_deps_file, _X(""));
     pal::string_t additional_deps;
@@ -892,12 +1048,26 @@ int fx_muxer_t::read_config_and_execute(
         return StatusCode::InvalidArgFailure;
     }
 
+    fx_reference_t override_settings;
+
+    // '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).
+    // 2. Runtimeconfig json file ('rollForwardOnNoCandidateFx' property in "framework" section).
+    // 3. Runtimeconfig json file ('rollForwardOnNoCandidateFx' property in a referencing "frameworks" section).
+    // 4. DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX env var.
+    // 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
+    roll_fwd_on_no_candidate_fx = get_last_known_arg(opts, opts_roll_fwd_on_no_candidate_fx, _X(""));
+    if (roll_fwd_on_no_candidate_fx.length() > 0)
+    {
+        override_settings.set_roll_fwd_on_no_candidate_fx(static_cast<roll_fwd_on_no_candidate_fx_option>(pal::xtoi(roll_fwd_on_no_candidate_fx.c_str())));
+    }
+
     // Read config
     fx_definition_vector_t fx_definitions;
     auto app = new fx_definition_t();
     fx_definitions.push_back(std::unique_ptr<fx_definition_t>(app));
-
-    int rc = read_config(*app, app_candidate, runtime_config);
+    int rc = read_config(*app, app_candidate, runtime_config, override_settings);
     if (rc)
     {
         return rc;
@@ -906,28 +1076,18 @@ int fx_muxer_t::read_config_and_execute(
     auto app_config = app->get_runtime_config();
     bool is_framework_dependent = app_config.get_is_framework_dependent();
 
-    // These settings are only valid for framework-dependent apps
+    // Apply the --fx-version option to the first framework
     if (is_framework_dependent)
     {
-        fx_version_specified = get_last_known_arg(opts, opts_fx_version, _X(""));
-        roll_fwd_on_no_candidate_fx = get_last_known_arg(opts, opts_roll_fwd_on_no_candidate_fx, _X(""));
-        additional_deps = get_last_known_arg(opts, opts_additional_deps, _X(""));
-    }
-
-    // '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).
-    // 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())
-    {
-        app_config.force_roll_fwd_on_no_candidate_fx(static_cast<roll_fwd_on_no_candidate_fx_option>(pal::xtoi(roll_fwd_on_no_candidate_fx.c_str())));
+        pal::string_t fx_version_specified = get_last_known_arg(opts, opts_fx_version, _X(""));
+        if (fx_version_specified.length() > 0)
+        {
+            // This will also set roll forward defaults on the ref
+            app_config.set_fx_version(fx_version_specified);
+        }
     }
 
-    auto config = app_config;
-
+    additional_deps = get_last_known_arg(opts, opts_additional_deps, _X(""));
     pal::string_t additional_deps_serialized;
     if (is_framework_dependent)
     {
@@ -939,34 +1099,34 @@ int fx_muxer_t::read_config_and_execute(
             pal::getenv(_X("DOTNET_ADDITIONAL_DEPS"), &additional_deps_serialized);
         }
 
-        // Obtain frameworks\platforms
-        auto version = fx_version_specified;
-        while (!config.get_fx_name().empty() && !config.get_fx_version().empty())
+        // If invoking using FX dotnet.exe, use own directory.
+        if (mode == host_mode_t::split_fx)
         {
-            fx_definition_t* fx = resolve_fx(mode, config, host_info.dotnet_root, version);
-            if (fx == nullptr)
-            {
-                pal::string_t searched_version = fx_version_specified.empty() ? config.get_fx_version() : fx_version_specified;
-                handle_missing_framework_error(mode, config.get_fx_name(), searched_version, pal::string_t(), host_info.dotnet_root);
-                return FrameworkMissingFailure;
-            }
-
+            auto fx = new fx_definition_t(app_config.get_frameworks()[0].get_fx_name(), host_info.dotnet_root, pal::string_t(), pal::string_t());
             fx_definitions.push_back(std::unique_ptr<fx_definition_t>(fx));
+        }
+        else
+        {
+            fx_name_to_fx_reference_map_t newest_references;
+            fx_name_to_fx_reference_map_t oldest_references;
 
-            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, &app_config);
+            // Read the shared frameworks; retry is necessary when a framework is already resolved, but then a newer compatible version is processed.
+            int rc = 0;
+            int retry_count = 0;
+            do
+            {
+                fx_definitions.resize(1); // Erase any existing frameworks for re-try
+                rc = read_framework(host_info, override_settings, app_config, newest_references, oldest_references, fx_definitions);
+            } while (rc == FrameworkCompatRetry && retry_count++ < Max_Framework_Resolve_Retries);
+
+            assert(retry_count < Max_Framework_Resolve_Retries);
 
-            config = fx->get_runtime_config();
-            if (!config.is_valid())
+            if (rc)
             {
-                trace::error(_X("Invalid framework config.json [%s]"), config.get_path().c_str());
-                return StatusCode::InvalidConfigFile;
+                return rc;
             }
 
-            // Only the first framework can have a specified version (through --fx-version)
-            version.clear();
+            display_summary_of_frameworks(fx_definitions, newest_references);
         }
     }
 
@@ -991,10 +1151,10 @@ int fx_muxer_t::read_config_and_execute(
     }
 
     trace::verbose(_X("Executing as a %s app as per config file [%s]"),
-        (is_framework_dependent ? _X("framework-dependent") : _X("self-contained")), config.get_path().c_str());
+        (is_framework_dependent ? _X("framework-dependent") : _X("self-contained")), app_config.get_path().c_str());
 
     pal::string_t impl_dir;
-    if (!resolve_hostpolicy_dir(mode, host_info.dotnet_root, fx_definitions, app_candidate, deps_file, fx_version_specified, probe_realpaths, &impl_dir))
+    if (!resolve_hostpolicy_dir(mode, host_info.dotnet_root, fx_definitions, app_candidate, deps_file, probe_realpaths, &impl_dir))
     {
         return CoreHostLibMissingFailure;
     }
index 3ad5c77..9d0a0b7 100644 (file)
@@ -9,6 +9,8 @@ struct host_startup_info_t;
 
 #include "libhost.h"
 
+const int Max_Framework_Resolve_Retries = 100;
+
 int execute_app(
     const pal::string_t& impl_dll_dir,
     corehost_init_t* init,
@@ -92,7 +94,6 @@ private:
         const fx_definition_vector_t& fx_definitions,
         const pal::string_t& app_candidate,
         const pal::string_t& specified_deps_file,
-        const pal::string_t& specified_fx_version,
         const std::vector<pal::string_t>& probe_realpaths,
         pal::string_t* impl_dir);
     static fx_ver_t resolve_framework_version(
@@ -101,10 +102,44 @@ private:
         const fx_ver_t& specified,
         bool patch_roll_fwd,
         roll_fwd_on_no_candidate_fx_option roll_fwd_on_no_candidate_fx);
-    static fx_definition_t* resolve_fx(
-        host_mode_t mode,
+    static int read_framework(
+        const host_startup_info_t& host_info,
+        const fx_reference_t& override_settings,
         const runtime_config_t& config,
-        const pal::string_t& dotnet_dir,
-        const pal::string_t& specified_fx_version);
+        fx_name_to_fx_reference_map_t& newest_references,
+        fx_name_to_fx_reference_map_t& oldest_references,
+        fx_definition_vector_t& fx_definitions);
+    static fx_definition_t* resolve_fx(
+        const fx_reference_t& config,
+        const pal::string_t& oldest_requested_version,
+        const pal::string_t& dotnet_dir);
     static void muxer_usage(bool is_sdk_present);
+    static int soft_roll_forward_helper(
+        const fx_reference_t& newer,
+        const fx_reference_t& older,
+        bool older_is_hard_roll_forward,
+        fx_name_to_fx_reference_map_t& newest_references,
+        fx_name_to_fx_reference_map_t& oldest_references);
+    static int soft_roll_forward(
+        const fx_reference_t existing_ref,
+        bool current_is_hard_roll_forward,
+        fx_name_to_fx_reference_map_t& newest_references,
+        fx_name_to_fx_reference_map_t& oldest_references);
+    static void display_missing_framework_error(
+        const pal::string_t& fx_name,
+        const pal::string_t& fx_version,
+        const pal::string_t& fx_dir,
+        const pal::string_t& dotnet_root);
+    static void display_incompatible_framework_error(
+        const pal::string_t& higher,
+        const fx_reference_t& lower);
+    static void display_compatible_framework_trace(
+        const pal::string_t& higher,
+        const fx_reference_t& lower);
+    static void display_retry_framework_trace(
+        const fx_reference_t& fx_existing,
+        const fx_reference_t& fx_new);
+    static void display_summary_of_frameworks(
+        const fx_definition_vector_t& fx_definitions,
+        const fx_name_to_fx_reference_map_t& newest_references);
 };
diff --git a/src/installer/corehost/cli/fxr/fx_muxer.messages.cpp b/src/installer/corehost/cli/fxr/fx_muxer.messages.cpp
new file mode 100644 (file)
index 0000000..5a39879
--- /dev/null
@@ -0,0 +1,102 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#include <cassert>
+#include "framework_info.h"
+#include "fx_definition.h"
+#include "fx_muxer.h"
+#include "fx_reference.h"
+#include "fx_ver.h"
+#include "pal.h"
+#include "trace.h"
+
+/**
+* When the framework is referenced more than once in a non-compatible way, display detailed error message
+*   about available frameworks and installation of new framework.
+*/
+void fx_muxer_t::display_incompatible_framework_error(
+    const pal::string_t& higher,
+    const fx_reference_t& lower)
+{
+    assert(lower.get_patch_roll_fwd() != nullptr);
+    assert(lower.get_roll_fwd_on_no_candidate_fx() != nullptr);
+
+    trace::error(_X("The specified framework '%s', version '%s', patch_roll_fwd=%d, roll_fwd_on_no_candidate_fx=%d cannot roll-forward to the previously referenced version '%s'."),
+        lower.get_fx_name().c_str(),
+        lower.get_fx_version().c_str(),
+        *lower.get_patch_roll_fwd(),
+        *lower.get_roll_fwd_on_no_candidate_fx(),
+        higher.c_str());
+}
+
+void fx_muxer_t::display_compatible_framework_trace(
+    const pal::string_t& higher,
+    const fx_reference_t& lower)
+{
+    if (trace::is_enabled())
+    {
+        assert(lower.get_patch_roll_fwd() != nullptr);
+        assert(lower.get_roll_fwd_on_no_candidate_fx() != nullptr);
+
+        trace::verbose(_X("--- The specified framework '%s', version '%s', patch_roll_fwd=%d, roll_fwd_on_no_candidate_fx=%d is compatible with the previously referenced version '%s'."),
+            lower.get_fx_name().c_str(),
+            lower.get_fx_version().c_str(),
+            *lower.get_patch_roll_fwd(),
+            *lower.get_roll_fwd_on_no_candidate_fx(),
+            higher.c_str());
+    }
+}
+
+void fx_muxer_t::display_retry_framework_trace(
+    const fx_reference_t& fx_existing,
+    const fx_reference_t& fx_new)
+{
+    if (trace::is_enabled())
+    {
+        assert(fx_new.get_patch_roll_fwd() != nullptr);
+        assert(fx_new.get_roll_fwd_on_no_candidate_fx() != nullptr);
+
+        trace::verbose(_X("--- Restarting all framework resolution because the previously resolved framework '%s', version '%s' must be re-resolved with the new version '%s', patch_roll_fwd=%d, roll_fwd_on_no_candidate_fx=%d ."),
+            fx_existing.get_fx_name().c_str(),
+            fx_existing.get_fx_version().c_str(),
+            fx_new.get_fx_version().c_str(),
+            *fx_new.get_patch_roll_fwd(),
+            *fx_new.get_roll_fwd_on_no_candidate_fx());
+    }
+}
+
+void fx_muxer_t::display_summary_of_frameworks(
+    const fx_definition_vector_t& fx_definitions,
+    const fx_name_to_fx_reference_map_t& newest_references
+)
+{
+    if (trace::is_enabled())
+    {
+        trace::verbose(_X("--- Summary of all frameworks:"));
+
+        bool is_app = true;
+        for (const auto& fx : fx_definitions)
+        {
+            if (is_app)
+            {
+                is_app = false; // skip the app
+            }
+            else
+            {
+                auto newest_ref = newest_references.find(fx->get_name());
+                assert(newest_ref != newest_references.end());
+                assert(newest_ref->second.get_fx_version() == fx->get_found_version());
+                assert(newest_ref->second.get_patch_roll_fwd() != nullptr);
+                assert(newest_ref->second.get_roll_fwd_on_no_candidate_fx() != nullptr);
+
+                trace::verbose(_X("     framework:'%s', lowest requested version='%s', found version='%s', patch_roll_fwd=%d, roll_fwd_on_no_candidate_fx=%d, folder=%s"),
+                    fx->get_name().c_str(),
+                    fx->get_requested_version().c_str(),
+                    fx->get_found_version().c_str(),
+                    *newest_ref->second.get_patch_roll_fwd(),
+                    *newest_ref->second.get_roll_fwd_on_no_candidate_fx(),
+                    fx->get_dir().c_str());
+            }
+        }
+    }
+}
index 8d9f19a..60bc2ee 100644 (file)
@@ -25,6 +25,11 @@ fx_ver_t::fx_ver_t(int major, int minor, int patch)
 {
 }
 
+fx_ver_t::fx_ver_t()
+    : fx_ver_t(-1, -1, -1, _X(""), _X(""))
+{
+}
+
 bool fx_ver_t::operator ==(const fx_ver_t& b) const
 {
     return compare(*this, b) == 0;
index b59b325..6331af6 100644 (file)
@@ -10,6 +10,7 @@
 // compare multiple dot separated identifiers individually.) ex: 1.0.0-beta.2 vs. 1.0.0-beta.11
 struct fx_ver_t
 {
+    fx_ver_t();
     fx_ver_t(int major, int minor, int patch);
     fx_ver_t(int major, int minor, int patch, const pal::string_t& pre);
     fx_ver_t(int major, int minor, int patch, const pal::string_t& pre, const pal::string_t& build);
@@ -24,6 +25,8 @@ struct fx_ver_t
 
     bool is_prerelease() const { return !m_pre.empty(); }
 
+    bool is_empty() const { return m_major == -1; }
+
     pal::string_t as_str() const;
     pal::string_t prerelease_glob() const;
     pal::string_t patch_glob() const;
index d927f13..78c2b20 100644 (file)
@@ -81,7 +81,7 @@ void sdk_info::get_all_sdk_infos(
             for (const auto& ver : versions)
             {
                 // Make sure we filter out any non-version folders.
-                fx_ver_t parsed(-1, -1, -1);
+                fx_ver_t parsed;
                 if (fx_ver_t::parse(ver, &parsed, false))
                 {
                     trace::verbose(_X("Found SDK version [%s]"), ver.c_str());
index 61b19e4..86e01ea 100644 (file)
@@ -66,7 +66,7 @@ pal::string_t resolve_cli_version(const pal::string_t& global_json)
 
 pal::string_t resolve_sdk_version(pal::string_t sdk_path, bool disallow_prerelease, pal::string_t global_cli_version)
 {
-    fx_ver_t specified(-1, -1, -1);
+    fx_ver_t specified;
 
     //   Validate the global cli version if specified
     if (!global_cli_version.empty())
@@ -90,12 +90,12 @@ pal::string_t resolve_sdk_version(pal::string_t sdk_path, bool disallow_prerelea
     std::vector<pal::string_t> versions;
 
     pal::readdir_onlydirectories(sdk_path, &versions);
-    fx_ver_t max_ver(-1, -1, -1);
+    fx_ver_t max_ver;
     for (const auto& version : versions)
     {
         trace::verbose(_X("Considering version... [%s]"), version.c_str());
 
-        fx_ver_t ver(-1, -1, -1);
+        fx_ver_t ver;
         if (fx_ver_t::parse(version, &ver, disallow_prerelease))
         {
             if (global_cli_version.empty() ||
@@ -140,8 +140,8 @@ bool higher_sdk_version(const pal::string_t& new_version, pal::string_t* version
 {
     bool disallow_prerelease = false;
     bool retval = false;
-    fx_ver_t ver(-1, -1, -1);
-    fx_ver_t new_ver(-1, -1, -1);
+    fx_ver_t ver;
+    fx_ver_t new_ver;
 
     if (fx_ver_t::parse(new_version, &new_ver, disallow_prerelease))
     {
index 21af6d6..50ca121 100644 (file)
@@ -45,6 +45,7 @@ set(SOURCES
     ../deps_format.cpp
     ../deps_entry.cpp
     ../fx_definition.cpp
+    ../fx_reference.cpp
     ../version.cpp
 )
 
index 82c4c33..3f7d1a5 100644 (file)
@@ -94,7 +94,7 @@ void try_patch_roll_forward_in_dir(const pal::string_t& cur_dir, const fx_ver_t&
     pal::readdir_onlydirectories(path, maj_min_star, &list);
 
     fx_ver_t max_ver = start_ver;
-    fx_ver_t ver(-1, -1, -1);
+    fx_ver_t ver;
     for (const auto& str : list)
     {
         trace::verbose(_X("Considering patch roll forward candidate version [%s]"), str.c_str());
@@ -128,7 +128,7 @@ void try_prerelease_roll_forward_in_dir(const pal::string_t& cur_dir, const fx_v
     pal::readdir_onlydirectories(path, maj_min_pat_star, &list);
 
     fx_ver_t max_ver = start_ver;
-    fx_ver_t ver(-1, -1, -1);
+    fx_ver_t ver;
     for (const auto& str : list)
     {
         trace::verbose(_X("Considering prerelease roll forward candidate version [%s]"), str.c_str());
diff --git a/src/installer/corehost/cli/roll_fwd_on_no_candidate_fx_option.h b/src/installer/corehost/cli/roll_fwd_on_no_candidate_fx_option.h
new file mode 100644 (file)
index 0000000..9050500
--- /dev/null
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#ifndef __ROLL_FWD_ON_NO_CANDIDATE_FX_OPTION_H_
+#define __ROLL_FWD_ON_NO_CANDIDATE_FX_OPTION_H_
+
+// Specifies the roll forward capability for finding the closest (most compatible) framework
+// Note that the "applyPatches" bool option is separate from this and occurs after roll forward.
+enum class roll_fwd_on_no_candidate_fx_option
+{
+    disabled = 0,
+    minor,          // also inludes patch
+    major           // also inludes minor and patch
+};
+
+#endif // __ROLL_FWD_ON_NO_CANDIDATE_FX_OPTION_H_
index 9a441f9..bfff336 100644 (file)
 
 // 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
+// 1) Apply the environment settings
+// 2) Apply the values in the current "runtimeOptions" section
+// 3) Apply the values in the referenced "frameworks" section
+// 4) Apply the overrides (from command line or other)
 
 runtime_config_t::runtime_config_t()
-    : m_patch_roll_fwd(true)
-    , m_roll_fwd_on_no_candidate_fx(roll_fwd_on_no_candidate_fx_option::minor)
-    , m_is_framework_dependent(false)
+    : m_is_framework_dependent(false)
     , m_valid(false)
 {
 }
 
-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)
+void runtime_config_t::parse(const pal::string_t& path, const pal::string_t& dev_path, const fx_reference_t& fx_ref, const fx_reference_t& override_settings)
 {
     m_path = path;
     m_dev_path = dev_path;
+    m_fx_ref = fx_ref;
+    m_fx_overrides = override_settings;
 
-    // Step #1: apply the defaults from the environment (for the app) or previous\higher layer (for a framework)
-    if (higher_layer_config != nullptr)
-    {
-        // 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);
+    // Step #1: set the defaults from the environment
+    m_fx_defaults.set_patch_roll_fwd(true);
 
-        // Apply the defaults
-        set_effective_values(m_fx_global);
-    }
-    else
+    roll_fwd_on_no_candidate_fx_option roll_fwd_option = roll_fwd_on_no_candidate_fx_option::minor;
+    pal::string_t env_no_candidate;
+    if (pal::getenv(_X("DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX"), &env_no_candidate))
     {
-        // Since there is no previous config, this is the app's config, so default m_roll_fwd_on_no_candidate_fx from the env variable.
-        // The value will be overwritten during parsing if the setting exists in the config file.
-        pal::string_t env_no_candidate;
-        if (pal::getenv(_X("DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX"), &env_no_candidate))
-        {
-            m_roll_fwd_on_no_candidate_fx = static_cast<roll_fwd_on_no_candidate_fx_option>(pal::xtoi(env_no_candidate.c_str()));
-            m_fx_global.set_roll_fwd_on_no_candidate_fx(m_roll_fwd_on_no_candidate_fx);
-        }
+        roll_fwd_option = static_cast<roll_fwd_on_no_candidate_fx_option>(pal::xtoi(env_no_candidate.c_str()));
     }
 
-    m_valid = ensure_parsed(app_config);
+    m_fx_defaults.set_roll_fwd_on_no_candidate_fx(roll_fwd_option);
 
-    if (m_valid)
-    {
-        // Step #4: apply the readonly values
-        if (app_config != nullptr)
-        {
-            m_tfm = app_config->m_tfm;
-            set_effective_values(app_config->m_fx_readonly);
-        }
-    }
+    // Parse the file
+    m_valid = ensure_parsed();
 
     trace::verbose(_X("Runtime config [%s] is valid=[%d]"), path.c_str(), m_valid);
 }
@@ -106,18 +87,18 @@ bool runtime_config_t::parse_opts(const json_value& opts)
         }
     }
 
+    // Step #2: set the defaults from the "runtimeOptions"
     auto patch_roll_fwd = opts_obj.find(_X("applyPatches"));
     if (patch_roll_fwd != opts_obj.end())
     {
-        m_patch_roll_fwd = patch_roll_fwd->second.as_bool();
-        m_fx_global.set_patch_roll_fwd(m_patch_roll_fwd);
+        m_fx_defaults.set_patch_roll_fwd(patch_roll_fwd->second.as_bool());
     }
 
     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_option>(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 val = static_cast<roll_fwd_on_no_candidate_fx_option>(roll_fwd_on_no_candidate_fx->second.as_integer());
+        m_fx_defaults.set_roll_fwd_on_no_candidate_fx(val);
     }
 
     auto tfm = opts_obj.find(_X("tfm"));
@@ -126,85 +107,68 @@ bool runtime_config_t::parse_opts(const json_value& opts)
         m_tfm = tfm->second.as_string();
     }
 
-    // Step #2: apply the "framework" section
-
+    // Step #3: read the "framework" and "frameworks" section
+    bool rc = true;
     auto framework =  opts_obj.find(_X("framework"));
-    if (framework == opts_obj.end())
+    if (framework != opts_obj.end())
     {
-        return true;
-    }
-
-    m_is_framework_dependent = true;
-
-    const auto& fx_obj = framework->second.as_object();
+        m_is_framework_dependent = true;
 
-    m_fx_name = fx_obj.at(_X("name")).as_string();
+        const auto& framework_obj = framework->second.as_object();
 
-    bool rc = parse_framework(fx_obj);
-    if (rc)
-    {
-        set_effective_values(m_fx);
+        fx_reference_t fx_out;
+        rc = parse_framework(framework_obj, fx_out);
+        if (rc)
+        {
+            m_frameworks.push_back(fx_out);
+        }
     }
 
-    return rc;
-}
-
-void runtime_config_t::set_effective_values(const runtime_config_framework_t& overrides)
-{
-    if (overrides.get_fx_ver() != nullptr)
+    if (rc)
     {
-        m_fx_ver = *overrides.get_fx_ver();
-    }
+        auto iter = opts_obj.find(_X("frameworks"));
+        if (iter != opts_obj.end())
+        {
+            m_is_framework_dependent = true;
 
-    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();
+            const auto& frameworks_obj = iter->second.as_array();
+            rc = read_framework_array(frameworks_obj);
+        }
     }
 
-    if (overrides.get_patch_roll_fwd() != nullptr)
-    {
-        m_patch_roll_fwd = *overrides.get_patch_roll_fwd();
-    }
+    return rc;
 }
 
-/*static*/ void runtime_config_t::copy_framework_settings_to(const runtime_config_framework_t& from, runtime_config_framework_t& to)
+bool runtime_config_t::parse_framework(const json_object& fx_obj, fx_reference_t& fx_out)
 {
-    if (from.get_fx_ver() != nullptr)
-    {
-        to.set_fx_ver(*from.get_fx_ver());
-    }
+    fx_out.apply_settings_from(m_fx_defaults);
 
-    if (from.get_roll_fwd_on_no_candidate_fx() != nullptr)
+    auto fx_name= fx_obj.find(_X("name"));
+    if (fx_name != fx_obj.end())
     {
-        to.set_roll_fwd_on_no_candidate_fx(*from.get_roll_fwd_on_no_candidate_fx());
+        fx_out.set_fx_name(fx_name->second.as_string());
     }
 
-    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());
+        fx_out.set_fx_version(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());
+        fx_out.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_option>(roll_fwd_on_no_candidate_fx->second.as_integer()));
+        fx_out.set_roll_fwd_on_no_candidate_fx(static_cast<roll_fwd_on_no_candidate_fx_option>(roll_fwd_on_no_candidate_fx->second.as_integer()));
     }
 
+    fx_out.apply_settings_from(m_fx_overrides);
+
     return true;
 }
 
@@ -252,7 +216,46 @@ bool runtime_config_t::ensure_dev_config_parsed()
     return true;
 }
 
-bool runtime_config_t::ensure_parsed(const runtime_config_t* app_config)
+bool runtime_config_t::read_framework_array(web::json::array frameworks_json)
+{
+    bool rc = true;
+
+    for (const auto& fx_json : frameworks_json)
+    {
+        const auto& fx_obj = fx_json.as_object();
+
+        fx_reference_t fx_out;
+        rc = parse_framework(fx_obj, fx_out);
+        if (!rc)
+        {
+            break;
+        }
+
+        if (fx_out.get_fx_name().length() == 0)
+        {
+            trace::verbose(_X("No framework name specified."));
+            rc = false;
+            break;
+        }
+
+        if (std::find_if(
+                m_frameworks.begin(),
+                m_frameworks.end(),
+                [&](const fx_reference_t& item) { return fx_out.get_fx_name() == item.get_fx_name(); })
+            != m_frameworks.end())
+        {
+            trace::verbose(_X("Framework %s already specified."), fx_out.get_fx_name().c_str());
+            rc = false;
+            break;
+        }
+
+        m_frameworks.push_back(fx_out);
+    }
+
+    return rc;
+}
+
+bool runtime_config_t::ensure_parsed()
 {
     trace::verbose(_X("Attempting to read runtime config: %s"), m_path.c_str());
     if (!ensure_dev_config_parsed())
@@ -288,51 +291,6 @@ bool runtime_config_t::ensure_parsed(const runtime_config_t* app_config)
         if (iter != json.end())
         {
             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)
@@ -352,37 +310,6 @@ const pal::string_t& runtime_config_t::get_tfm() const
     return m_tfm;
 }
 
-const pal::string_t& runtime_config_t::get_fx_name() const
-{
-    assert(m_valid);
-    return m_fx_name;
-}
-
-const pal::string_t& runtime_config_t::get_fx_version() const
-{
-    assert(m_valid);
-    return m_fx_ver;
-}
-
-bool runtime_config_t::get_patch_roll_fwd() const
-{
-    assert(m_valid);
-    return m_patch_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::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_is_framework_dependent() const
 {
     return m_is_framework_dependent;
@@ -405,3 +332,13 @@ void runtime_config_t::combine_properties(std::unordered_map<pal::string_t, pal:
         }
     }
 }
+
+void runtime_config_t::set_fx_version(pal::string_t version)
+{
+    assert(m_frameworks.size() > 0);
+
+    m_frameworks[0].set_fx_version(version);
+    m_frameworks[0].set_patch_roll_fwd(false);
+    m_frameworks[0].set_roll_fwd_on_no_candidate_fx(roll_fwd_on_no_candidate_fx_option::disabled);
+    m_frameworks[0].set_use_exact_version(true);
+}
index a1753e7..9604cfa 100644 (file)
 
 #include "pal.h"
 #include "cpprest/json.h"
+#include "fx_reference.h"
 
 typedef web::json::value json_value;
 typedef web::json::object json_object;
 
-enum class roll_fwd_on_no_candidate_fx_option
-{
-    disabled = 0,
-    minor,
-    major_or_minor
-};
-
-class runtime_config_framework_t
-{
-public:
-    // Uses a "nullable<T>" 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* higher_layer_config, const runtime_config_t* app_config);
+    void parse(const pal::string_t& path, const pal::string_t& dev_path, const fx_reference_t& fx_ref, const fx_reference_t& override_settings);
     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;
-    const pal::string_t& get_fx_name() const;
     const pal::string_t& get_tfm() const;
     const std::list<pal::string_t>& get_probe_paths() const;
-    bool get_patch_roll_fwd() const;
-    roll_fwd_on_no_candidate_fx_option get_roll_fwd_on_no_candidate_fx() const;
-    void force_roll_fwd_on_no_candidate_fx(roll_fwd_on_no_candidate_fx_option value);
     bool get_is_framework_dependent() const;
     bool parse_opts(const json_value& opts);
     void combine_properties(std::unordered_map<pal::string_t, pal::string_t>& combined_properties) const;
+    const fx_reference_vector_t& get_frameworks() const { return m_frameworks; }
+    void set_fx_version(pal::string_t version);
 
 private:
-    bool ensure_parsed(const runtime_config_t* defaults);
+    bool ensure_parsed(); //todo: const runtime_config_t* defaults
     bool ensure_dev_config_parsed();
 
     std::unordered_map<pal::string_t, pal::string_t> m_properties;
-    std::unordered_map<pal::string_t, runtime_config_framework_t> 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)
+    fx_reference_vector_t m_frameworks;
+    fx_reference_t m_fx_defaults;   // the default settings (Steps #1 and #2)
+    fx_reference_t m_fx_ref;        // the settings from the referenced "frameworks" section (Step #3)
+    fx_reference_t m_fx_overrides;  // the settings that can't be changed (Step #4)
     std::vector<std::string> m_prop_keys;
     std::vector<std::string> m_prop_values;
     std::list<pal::string_t> 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;
-    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;
@@ -119,9 +50,8 @@ private:
     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);
-
+    bool parse_framework(const json_object& fx_obj, fx_reference_t& fx_out);
+    bool read_framework_array(web::json::array frameworks);
+    static void copy_framework_settings_to(const fx_reference_t& from, fx_reference_t& to);
 };
-#endif // __RUNTIME_CONFIG_H__
\ No newline at end of file
+#endif // __RUNTIME_CONFIG_H__
index 476ec89..96c5adb 100644 (file)
@@ -143,21 +143,21 @@ bool resolve_fxr_path(const pal::string_t& host_path, const pal::string_t& app_r
     std::vector<pal::string_t> list;
     pal::readdir_onlydirectories(fxr_dir, &list);
 
-    fx_ver_t max_ver(-1, -1, -1);
+    fx_ver_t max_ver;
     for (const auto& dir : list)
     {
         trace::info(_X("Considering fxr version=[%s]..."), dir.c_str());
 
         pal::string_t ver = get_filename(dir);
 
-        fx_ver_t fx_ver(-1, -1, -1);
+        fx_ver_t fx_ver;
         if (fx_ver_t::parse(ver, &fx_ver, false))
         {
             max_ver = std::max(max_ver, fx_ver);
         }
     }
 
-    if (max_ver == fx_ver_t(-1, -1, -1))
+    if (max_ver == fx_ver_t())
     {
         trace::error(_X("A fatal error occurred, the folder [%s] does not contain any version-numbered child folders"), fxr_dir.c_str());
         return false;
index 995c193..f84d959 100644 (file)
@@ -33,5 +33,7 @@ enum StatusCode
     LibHostUnknownCommand       = 0x80008099,
     LibHostAppRootFindFailure   = 0x8000809a,
     SdkResolverResolveFailure   = 0x8000809b,
+    FrameworkCompatFailure      = 0x8000809c,
+    FrameworkCompatRetry        = 0x8000809d,
 };
 #endif // __ERROR_CODES_H__
index 9b4de2a..7167590 100644 (file)
@@ -426,7 +426,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.LightupApp
 
             // Add versions in the exe folder
             SharedFramework.AddAvailableSharedFxVersions(_builtSharedFxDir, _fxBaseDir, "9999.0.0");
-            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _uberFxBaseDir, "9999.0.0", null, "7777.0.0");
+            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _uberFxBaseDir, "9999.0.0", "7777.0.0");
 
             // Copy NetCoreApp's copy of the assembly to the app location
             string netcoreAssembly = Path.Combine(_fxBaseDir, "9999.0.0", "System.Collections.Immutable.dll");
@@ -492,7 +492,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.LightupApp
 
             // Add versions in the exe folder
             SharedFramework.AddAvailableSharedFxVersions(_builtSharedFxDir, _fxBaseDir, "9999.0.0");
-            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _uberFxBaseDir, "9999.0.0", null, "7777.0.0");
+            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _uberFxBaseDir, "9999.0.0", "7777.0.0");
 
             // Copy NetCoreApp's copy of the assembly to the app location
             string netcoreAssembly = Path.Combine(_fxBaseDir, "9999.0.0", "System.Collections.Immutable.dll");
index cc4b4ba..7427ad2 100644 (file)
@@ -121,7 +121,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
 
             // Add versions in the exe folder
             SharedFramework.AddAvailableSharedFxVersions(_builtSharedFxDir, _exeSharedFxBaseDir, "9999.0.0");
-            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _exeSharedUberFxBaseDir, "9999.0.0", null, uberFxProductVersion);
+            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _exeSharedUberFxBaseDir, "9999.0.0", uberFxProductVersion);
 
             // Copy NetCoreApp's copy of the assembly to the app location
             netcoreAssembly = Path.Combine(_exeSharedFxBaseDir, "9999.0.0", "System.Collections.Immutable.dll");
index 9fe33c2..919acf1 100644 (file)
@@ -9,6 +9,9 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
 {
     public partial class GivenThatICareAboutMultilevelSharedFxLookup : IDisposable
     {
+        private const string SystemCollectionsImmutableFileVersion = "88.2.3.4";
+        private const string SystemCollectionsImmutableAssemblyVersion = "88.0.1.2";
+
         private RepoDirectoriesProvider RepoDirectories;
         private TestProjectFixture PreviouslyBuiltAndRestoredPortableTestProjectFixture;
 
@@ -100,6 +103,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
             _sharedFxVersion = (new DirectoryInfo(greatestVersionSharedFxPath)).Name;
             _builtSharedFxDir = Path.Combine(_builtDotnet, "shared", "Microsoft.NETCore.App", _sharedFxVersion);
             _builtSharedUberFxDir = Path.Combine(_builtDotnet, "shared", "Microsoft.UberFramework", _sharedFxVersion);
+            SharedFramework.CreateUberFrameworkArtifacts(_builtSharedFxDir, _builtSharedUberFxDir, SystemCollectionsImmutableAssemblyVersion, SystemCollectionsImmutableFileVersion);
 
             // Trace messages used to identify from which folder the framework was picked
             _hostPolicyDllName = Path.GetFileName(fixture.TestProject.HostPolicyDll);
@@ -647,7 +651,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
 
             // Add versions in the exe folders
             SharedFramework.AddAvailableSharedFxVersions(_builtSharedFxDir, _exeSharedFxBaseDir, "9999.0.0");
-            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _exeSharedUberFxBaseDir, "9999.0.0", null, "7777.0.0");
+            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _exeSharedUberFxBaseDir, "9999.0.0", "7777.0.0");
 
             // Version: NetCoreApp 9999.0.0
             //          UberFramework 7777.0.0
@@ -672,7 +676,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
 
             // Add a newer version to verify roll-forward
             SharedFramework.AddAvailableSharedFxVersions(_builtSharedFxDir, _exeSharedFxBaseDir, "9999.0.1");
-            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _exeSharedUberFxBaseDir, "9999.0.0", null, "7777.0.1");
+            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _exeSharedUberFxBaseDir, "9999.0.0", "7777.0.1");
 
             // Version: NetCoreApp 9999.0.0
             //          UberFramework 7777.0.0
@@ -712,7 +716,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
         }
 
         [Fact]
-        public void Multiple_SharedFxLookup_Propagated_Global_RuntimeConfig_Values()
+        public void Multiple_SharedFxLookup_Do_Not_Propagate()
         {
             var fixture = PreviouslyBuiltAndRestoredPortableTestProjectFixture
                 .Copy();
@@ -725,7 +729,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
 
             // Add versions in the exe folders
             SharedFramework.AddAvailableSharedFxVersions(_builtSharedFxDir, _exeSharedFxBaseDir, "9999.1.0");
-            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _exeSharedUberFxBaseDir, "9999.0.0", "UberValue", "7777.0.0");
+            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _exeSharedUberFxBaseDir, "9999.0.0", "7777.0.0");
 
             // Version: NetCoreApp 9999.0.0
             //          UberFramework 7777.0.0
@@ -745,65 +749,160 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
                 .And
                 .HaveStdErrContaining("It was not possible to find any compatible framework version");
 
-            // Enable rollForwardOnNoCandidateFx on app's config, which will be used as the default for Uber's config
-            SharedFramework.SetRuntimeConfigJson(runtimeConfig, "7777.0.0", rollFwdOnNoCandidateFx: 1, testConfigPropertyValue: null, useUberFramework: true);
+            // Enable rollForwardOnNoCandidateFx on app's config, which will not be used as the default for Uber's config
+            SharedFramework.SetRuntimeConfigJson(runtimeConfig, "7777.0.0", rollFwdOnNoCandidateFx: 1, useUberFramework: true);
 
             // Version: NetCoreApp 9999.0.0
             //          UberFramework 7777.0.0
             //          'Roll forward on no candidate fx' enabled through config
             // Exe: NetCoreApp 9999.1.0
             //      UberFramework 7777.0.0
-            // Expected: 9999.1.0
-            //           7777.0.0
+            // Expected: no compatible version
             dotnet.Exec(appDll)
                 .WorkingDirectory(_currentWorkingDir)
                 .EnvironmentVariable("COREHOST_TRACE", "1")
                 .EnvironmentVariable("DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX", "0")
                 .CaptureStdOut()
                 .CaptureStdErr()
+                .Execute(fExpectedToFail: true)
+                .Should()
+                .Fail()
+                .And
+                .HaveStdErrContaining("It was not possible to find any compatible framework version");
+        }
+
+        [Fact]
+        public void Multiple_Fx_References_Cant_Roll_Forward_Because_Incompatible_Config()
+        {
+            var fixture = PreviouslyBuiltAndRestoredPortableTestProjectFixture
+                .Copy();
+
+            var dotnet = fixture.BuiltDotnet;
+            var appDll = fixture.TestProject.AppDll;
+
+            string runtimeConfig = Path.Combine(fixture.TestProject.OutputDirectory, "SharedFxLookupPortableApp.runtimeconfig.json");
+
+            var additionalfxs = new JArray();
+            additionalfxs.Add(GetAdditionalFramework("Microsoft.NETCore.App", "9999.1.0", applyPatches: false, rollForwardOnNoCandidateFx: 0));
+            SharedFramework.SetRuntimeConfigJson(runtimeConfig, "7777.0.0", null, useUberFramework: true, frameworks: additionalfxs);
+
+            // Add versions in the exe folders
+            SharedFramework.AddAvailableSharedFxVersions(_builtSharedFxDir, _exeSharedFxBaseDir, "9999.1.0", "9999.5.5");
+            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _exeSharedUberFxBaseDir, "9999.5.5", "7777.0.0");
+
+            // Verify that both 9999.1.0 and 9999.5.5 can't be selected with roll-forward disabled
+            // Version: NetCoreApp 9999.5.5 (in framework section)
+            //          NetCoreApp 9999.1.0 (in frameworks section)
+            //          UberFramework 7777.0.0
+            // Exe: NetCoreApp 9999.1.0 rollForwardOnNoCandidateFx:0 applyPatches:false
+            //      NetCoreApp 9999.5.5
+            //      UberFramework 7777.0.0
+            // Expected: no compatible version
+            dotnet.Exec(appDll)
+                .WorkingDirectory(_currentWorkingDir)
+                .EnvironmentVariable("COREHOST_TRACE", "1")
+                .CaptureStdOut()
+                .CaptureStdErr()
+                .Execute(fExpectedToFail: true)
+                .Should()
+                .Fail()
+                .And
+                .HaveStdErrContaining("cannot roll-forward to the previously referenced version '9999.5.5");
+        }
+
+        [Fact]
+        public void Multiple_Fx_References_Can_Roll_Forward_Without_Retry()
+        {
+            var fixture = PreviouslyBuiltAndRestoredPortableTestProjectFixture
+                .Copy();
+
+            var dotnet = fixture.BuiltDotnet;
+            var appDll = fixture.TestProject.AppDll;
+
+            string runtimeConfig = Path.Combine(fixture.TestProject.OutputDirectory, "SharedFxLookupPortableApp.runtimeconfig.json");
+
+            var additionalfxs = new JArray();
+            additionalfxs.Add(GetAdditionalFramework("Microsoft.NETCore.App", "9999.1.1", applyPatches: false, rollForwardOnNoCandidateFx: 1));
+            SharedFramework.SetRuntimeConfigJson(runtimeConfig, "7777.0.0", null, useUberFramework: true, frameworks: additionalfxs);
+
+            // Add versions in the exe folders
+            SharedFramework.AddAvailableSharedFxVersions(_builtSharedFxDir, _exeSharedFxBaseDir, "9999.1.0", "9999.5.5");
+            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _exeSharedUberFxBaseDir, "9999.5.5", "7777.0.0");
+
+            // Version: NetCoreApp 9999.5.5 (in framework section)
+            //          NetCoreApp 9999.1.0 (in frameworks section)
+            //          UberFramework 7777.0.0
+            // Exe: NetCoreApp 9999.1.0 rollForwardOnNoCandidateFx:1 applyPatches:false
+            //      NetCoreApp 9999.5.5
+            //      UberFramework 7777.0.0
+            // Expected: 9999.5.5
+            //           7777.0.0
+            dotnet.Exec(appDll)
+                .WorkingDirectory(_currentWorkingDir)
+                .EnvironmentVariable("COREHOST_TRACE", "1")
+                .CaptureStdOut()
+                .CaptureStdErr()
                 .Execute()
                 .Should()
                 .Pass()
                 .And
-                .HaveStdErrContaining(Path.Combine(_exeSelectedMessage, "9999.1.0"))
+                .HaveStdErrContaining(Path.Combine(_exeSelectedMessage, "9999.5.5"))
                 .And
-                .HaveStdOutContaining("Framework Version:9999.1.0")
+                .HaveStdOutContaining("Framework Version:9999.5.5")
                 .And
                 .HaveStdErrContaining(Path.Combine(_exeFoundUberFxMessage, "7777.0.0"))
                 .And
-                .HaveStdErrContaining("Property TestProperty = UberValue");
+                .NotHaveStdErrContaining("Restarting all framework resolution");
+        }
 
-            // Change the app's TestProperty value which should override the uber's config value
-            SharedFramework.SetRuntimeConfigJson(runtimeConfig, "7777.0.0", rollFwdOnNoCandidateFx: 1, testConfigPropertyValue: "AppValue", useUberFramework: true);
+        [Fact]
+        public void Multiple_Fx_References_Can_Roll_Forward_With_Retry()
+        {
+            var fixture = PreviouslyBuiltAndRestoredPortableTestProjectFixture
+                .Copy();
 
-            // Version: NetCoreApp 9999.0.0
-            //          UberFramework 7777.0.0
-            //          'Roll forward on no candidate fx' enabled through config
-            // Exe: NetCoreApp 9999.1.0
+            var dotnet = fixture.BuiltDotnet;
+            var appDll = fixture.TestProject.AppDll;
+
+            string runtimeConfig = Path.Combine(fixture.TestProject.OutputDirectory, "SharedFxLookupPortableApp.runtimeconfig.json");
+
+            var additionalfxs = new JArray();
+            additionalfxs.Add(GetAdditionalFramework("Microsoft.UberFramework", "7777.0.0", null, null));
+            // Specify Uber as additional fx so we find NetCoreApp 9999.1.1 and then need to do a re-try for 9999.5.5
+            SharedFramework.SetRuntimeConfigJson(runtimeConfig, "9999.1.1", null, null, frameworks: additionalfxs);
+
+            // Add versions in the exe folders
+            SharedFramework.AddAvailableSharedFxVersions(_builtSharedFxDir, _exeSharedFxBaseDir, "9999.1.1", "9999.5.5");
+            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _exeSharedUberFxBaseDir, "9999.5.5", "7777.0.0");
+
+            // Version: NetCoreApp 9999.1.1 (in framework section)
+            //          UberFramework 7777.0.0 (in frameworks section)
+            //          NetCoreApp 9999.5.5 (in uber's config)
+            // Exe: NetCoreApp 9999.1.1
+            //      NetCoreApp 9999.5.5
             //      UberFramework 7777.0.0
-            // Expected: 9999.1.0
+            // Expected: 9999.5.5
             //           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(_exeSelectedMessage, "9999.1.0"))
+                .HaveStdErrContaining(Path.Combine(_exeSelectedMessage, "9999.5.5"))
                 .And
-                .HaveStdOutContaining("Framework Version:9999.1.0")
+                .HaveStdOutContaining("Framework Version:9999.5.5")
                 .And
                 .HaveStdErrContaining(Path.Combine(_exeFoundUberFxMessage, "7777.0.0"))
                 .And
-                .HaveStdErrContaining("Property TestProperty = AppValue");
+                .HaveStdErrContaining("Restarting all framework resolution because the previously resolved framework 'Microsoft.NETCore.App', version '9999.1.1' must be re-resolved with the new version '9999.5.5'");
         }
 
         [Fact]
-        public void Multiple_SharedFxLookup_Propagated_Additional_Framework_RuntimeConfig_Values()
+        public void Multiple_Fx_References_Cant_Roll_Forward_Because_Disabled_Through_CommandLine()
         {
             var fixture = PreviouslyBuiltAndRestoredPortableTestProjectFixture
                 .Copy();
@@ -814,48 +913,63 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
             string runtimeConfig = Path.Combine(fixture.TestProject.OutputDirectory, "SharedFxLookupPortableApp.runtimeconfig.json");
 
             var additionalfxs = new JArray();
-            additionalfxs.Add(GetAdditionalFramework("Microsoft.NETCore.App", "9999.1.0", applyPatches: false, rollForwardOnNoCandidateFx: 0));
-            SharedFramework.SetRuntimeConfigJson(runtimeConfig, "7777.0.0", null, useUberFramework: true, additionalFrameworks: additionalfxs);
+            additionalfxs.Add(GetAdditionalFramework("Microsoft.NETCore.App", "9999.1.1", applyPatches: false, rollForwardOnNoCandidateFx: 1));
+            SharedFramework.SetRuntimeConfigJson(runtimeConfig, "7777.0.0", null, useUberFramework: true, frameworks: additionalfxs);
 
             // Add versions in the exe folders
-            SharedFramework.AddAvailableSharedFxVersions(_builtSharedFxDir, _exeSharedFxBaseDir, "9999.1.0");
-            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _exeSharedUberFxBaseDir, "9999.5.5", "UberValue", "7777.0.0");
+            SharedFramework.AddAvailableSharedFxVersions(_builtSharedFxDir, _exeSharedFxBaseDir, "9999.1.0", "9999.5.5");
+            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _exeSharedUberFxBaseDir, "9999.5.5", "7777.0.0");
 
             // Version: NetCoreApp 9999.5.5 (in framework section)
-            //          NetCoreApp 9999.1.0 (in app's additionalFrameworks section)
+            //          NetCoreApp 9999.1.0 (in frameworks section)
             //          UberFramework 7777.0.0
-            // Exe: NetCoreApp 9999.1.0
+            // Exe: NetCoreApp 9999.1.0 rollForwardOnNoCandidateFx:1 applyPatches:false
+            //      NetCoreApp 9999.5.5
             //      UberFramework 7777.0.0
-            // Expected: 9999.1.0
+            // --roll-forward-on-no-candidate-fx=0 should override config settings
+            // Expected: 9999.5.5
             //           7777.0.0
-            dotnet.Exec(appDll)
+
+            dotnet.Exec(
+                    "exec",
+                    "--roll-forward-on-no-candidate-fx", "0",
+                    appDll)
                 .WorkingDirectory(_currentWorkingDir)
                 .EnvironmentVariable("COREHOST_TRACE", "1")
                 .CaptureStdOut()
                 .CaptureStdErr()
-                .Execute()
+                .Execute(fExpectedToFail: true)
                 .Should()
-                .Pass()
-                .And
-                .HaveStdErrContaining(Path.Combine(_exeSelectedMessage, "9999.1.0"))
-                .And
-                .HaveStdOutContaining("Framework Version:9999.1.0")
+                .Fail()
                 .And
-                .HaveStdErrContaining(Path.Combine(_exeFoundUberFxMessage, "7777.0.0"));
+                .HaveStdErrContaining("cannot roll-forward to the previously referenced version '9999.5.5");
+        }
 
-            // 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));
-            SharedFramework.SetRuntimeConfigJson(runtimeConfig, "7777.0.0", rollFwdOnNoCandidateFx: 0, useUberFramework: true, additionalFrameworks: additionalfxs);
+        [Fact]
+        public void Multiple_SharedFxLookup_NetCoreApp_MinorRollForward_Wins_Over_UberFx()
+        {
+            var fixture = PreviouslyBuiltAndRestoredPortableTestProjectFixture
+                .Copy();
 
-            // Version: NetCoreApp 9999.5.5 (in framework section)
-            //          NetCoreApp 9999.0.0 (in app's additionalFrameworks section)
+            var dotnet = fixture.BuiltDotnet;
+            var appDll = fixture.TestProject.AppDll;
+
+            string runtimeConfig = Path.Combine(fixture.TestProject.OutputDirectory, "SharedFxLookupPortableApp.runtimeconfig.json");
+            SharedFramework.SetRuntimeConfigJson(runtimeConfig, "7777.0.0", null, useUberFramework: true);
+
+            // Modify the Uber values
+            SharedFramework.CreateUberFrameworkArtifacts(_builtSharedFxDir, _builtSharedUberFxDir, "0.0.0.1", "0.0.0.2");
+
+            // Add versions in the exe folders
+            SharedFramework.AddAvailableSharedFxVersions(_builtSharedFxDir, _exeSharedFxBaseDir, "9999.1.0");
+            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _exeSharedUberFxBaseDir, "9999.0.0", "7777.0.0");
+
+            string uberFile = Path.Combine(_exeSharedUberFxBaseDir, "7777.0.0", "System.Collections.Immutable.dll");
+            string netCoreAppFile = Path.Combine(_exeSharedFxBaseDir, "9999.1.0", "System.Collections.Immutable.dll");
+            // The System.Collections.Immutable.dll is located in the UberFramework and NetCoreApp
+            // Version: NetCoreApp 9999.0.0
             //          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
+            //          'Roll forward on no candidate fx' enabled through config
             // Exe: NetCoreApp 9999.1.0
             //      UberFramework 7777.0.0
             // Expected: 9999.1.0
@@ -863,35 +977,51 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
             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(_exeSelectedMessage, "9999.1.0"))
-                .And
-                .HaveStdOutContaining("Framework Version:9999.1.0")
-                .And
-                .HaveStdErrContaining(Path.Combine(_exeFoundUberFxMessage, "7777.0.0"));
+                .HaveStdErrContaining($"Replacing deps entry [{uberFile}, AssemblyVersion:0.0.0.1, FileVersion:0.0.0.2] with [{netCoreAppFile}");
+        }
 
-            // 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)
+        [Fact]
+        public void Multiple_SharedFxLookup_Uber_Wins_Over_NetCoreApp_On_PatchRollForward()
+        {
+            var fixture = PreviouslyBuiltAndRestoredPortableTestProjectFixture
+                .Copy();
+
+            var dotnet = fixture.BuiltDotnet;
+            var appDll = fixture.TestProject.AppDll;
+
+            string runtimeConfig = Path.Combine(fixture.TestProject.OutputDirectory, "SharedFxLookupPortableApp.runtimeconfig.json");
+            SharedFramework.SetRuntimeConfigJson(runtimeConfig, "7777.0.0", null, useUberFramework: true);
+
+            // Add versions in the exe folders
+            SharedFramework.AddAvailableSharedFxVersions(_builtSharedFxDir, _exeSharedFxBaseDir, "9999.0.1");
+            SharedFramework.AddAvailableSharedUberFxVersions(_builtSharedUberFxDir, _exeSharedUberFxBaseDir, "9999.0.0", "7777.0.0");
+
+            // The System.Collections.Immutable.dll is located in the UberFramework and NetCoreApp
+            // Version: NetCoreApp 9999.0.0
+            //          UberFramework 7777.0.0
+            //          'Roll forward on no candidate fx' enabled through config
+            // Exe: NetCoreApp 9999.0.1
+            //      UberFramework 7777.0.0
+            // Expected: 9999.0.1
+            //           7777.0.0
+            dotnet.Exec(appDll)
                 .WorkingDirectory(_currentWorkingDir)
                 .EnvironmentVariable("COREHOST_TRACE", "1")
-                .EnvironmentVariable("DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX", "1")
                 .CaptureStdOut()
                 .CaptureStdErr()
-                .Execute(fExpectedToFail: true)
+                .Execute()
                 .Should()
-                .Fail()
+                .Pass()
                 .And
-                .HaveStdErrContaining("It was not possible to find any compatible framework version");
+                .HaveStdErrContaining(Path.Combine("7777.0.0", "System.Collections.Immutable.dll"))
+                .And
+                .NotHaveStdErrContaining(Path.Combine("9999.1.0", "System.Collections.Immutable.dll"));
         }
 
         static private JObject GetAdditionalFramework(string fxName, string fxVersion, bool? applyPatches, int? rollForwardOnNoCandidateFx)
index 0f7d033..539efef 100644 (file)
@@ -61,7 +61,7 @@ namespace Microsoft.DotNet.CoreSetup.Test
         // This method adds a list of new framework version folders in the specified
         // sharedFxUberBaseDir. A runtimeconfig file is created that references
         // Microsoft.NETCore.App version=sharedFxBaseVersion
-        public static void AddAvailableSharedUberFxVersions(string sharedFxDir, string sharedUberFxBaseDir, string sharedFxBaseVersion, string testConfigPropertyValue = null, params string[] availableUberVersions)
+        public static void AddAvailableSharedUberFxVersions(string sharedFxDir, string sharedUberFxBaseDir, string sharedFxBaseVersion, params string[] availableUberVersions)
         {
             DirectoryInfo sharedFxUberBaseDirInfo = new DirectoryInfo(sharedUberFxBaseDir);
 
@@ -76,7 +76,7 @@ namespace Microsoft.DotNet.CoreSetup.Test
                 CopyDirectory(sharedFxDir, newSharedFxDir);
 
                 string runtimeBaseConfig = Path.Combine(newSharedFxDir, "Microsoft.UberFramework.runtimeconfig.json");
-                SharedFramework.SetRuntimeConfigJson(runtimeBaseConfig, sharedFxBaseVersion, null, testConfigPropertyValue);
+                SharedFramework.SetRuntimeConfigJson(runtimeBaseConfig, sharedFxBaseVersion, null);
             }
         }
 
@@ -92,7 +92,7 @@ namespace Microsoft.DotNet.CoreSetup.Test
          *   }
          * }
         */
-        public static void SetRuntimeConfigJson(string destFile, string version, int? rollFwdOnNoCandidateFx = null, string testConfigPropertyValue = null, bool? useUberFramework = false, JArray additionalFrameworks = null)
+        public static void SetRuntimeConfigJson(string destFile, string version, int? rollFwdOnNoCandidateFx = null, bool? useUberFramework = false, JArray frameworks = null)
         {
             string name = useUberFramework.HasValue && useUberFramework.Value ? "Microsoft.UberFramework" : "Microsoft.NETCore.App";
 
@@ -110,20 +110,9 @@ namespace Microsoft.DotNet.CoreSetup.Test
                 runtimeOptions.Add("rollForwardOnNoCandidateFx", rollFwdOnNoCandidateFx);
             }
 
-            if (testConfigPropertyValue != null)
+            if (frameworks != null)
             {
-                runtimeOptions.Add(
-                    new JProperty("configProperties",
-                        new JObject(
-                            new JProperty("TestProperty", testConfigPropertyValue)
-                        )
-                    )
-                );
-            }
-
-            if (additionalFrameworks != null)
-            {
-                runtimeOptions.Add("additionalFrameworks", additionalFrameworks);
+                runtimeOptions.Add("frameworks", frameworks);
             }
 
             FileInfo file = new FileInfo(destFile);