Add support for multiple shared frameworks (dotnet/core-setup#3460)
authorSteve Harter <steveharter@users.noreply.github.com>
Thu, 30 Nov 2017 15:39:29 +0000 (09:39 -0600)
committerGitHub <noreply@github.com>
Thu, 30 Nov 2017 15:39:29 +0000 (09:39 -0600)
Commit migrated from https://github.com/dotnet/core-setup/commit/94f2146cd810ae0762a699df210a65fb866e44cd

16 files changed:
src/installer/corehost/cli/deps_format.h
src/installer/corehost/cli/deps_resolver.cpp
src/installer/corehost/cli/deps_resolver.h
src/installer/corehost/cli/dll/CMakeLists.txt
src/installer/corehost/cli/fx_definition.cpp [new file with mode: 0644]
src/installer/corehost/cli/fx_definition.h [new file with mode: 0644]
src/installer/corehost/cli/fxr/CMakeLists.txt
src/installer/corehost/cli/fxr/fx_muxer.cpp
src/installer/corehost/cli/fxr/fx_muxer.h
src/installer/corehost/cli/hostpolicy.cpp
src/installer/corehost/cli/libhost.cpp
src/installer/corehost/cli/libhost.h
src/installer/corehost/cli/runtime_config.cpp
src/installer/corehost/cli/runtime_config.h
src/installer/corehost/error_codes.h
src/installer/test/HostActivationTests/GivenThatICareAboutMultilevelSharedFxLookup.cs

index 78b6a4c..b0b6235 100644 (file)
@@ -23,10 +23,10 @@ class deps_json_t
     struct rid_specific_assets_t { std::unordered_map<pal::string_t, rid_assets_t> libs; };
 
     typedef std::unordered_map<pal::string_t, std::vector<pal::string_t>> str_to_vector_map_t;
-    typedef str_to_vector_map_t rid_fallback_graph_t;
-
 
 public:
+    typedef str_to_vector_map_t rid_fallback_graph_t;
+
     deps_json_t()
         : m_valid(false)
         , m_file_exists(false)
@@ -44,7 +44,17 @@ public:
         m_valid = load(portable, deps_path, graph);
     }
 
-    const std::vector<deps_entry_t>& get_entries(deps_entry_t::asset_types type)
+    void parse(bool portable, const pal::string_t& deps_path)
+    {
+        m_valid = load(portable, deps_path, m_rid_fallback_graph /* dummy */);
+    }
+
+    void parse(bool portable, const pal::string_t& deps_path, const rid_fallback_graph_t& graph)
+    {
+        m_valid = load(portable, deps_path, graph);
+    }
+
+    const std::vector<deps_entry_t>& get_entries(deps_entry_t::asset_types type) const
     {
         assert(type < deps_entry_t::asset_types::count);
         return m_deps_entries[type];
@@ -52,22 +62,22 @@ public:
 
     bool has_package(const pal::string_t& name, const pal::string_t& ver) const;
 
-    bool exists()
+    bool exists() const
     {
         return m_file_exists;
     }
 
-    bool is_valid()
+    bool is_valid() const
     {
         return m_valid;
     }
 
-    const rid_fallback_graph_t& get_rid_fallback_graph()
+    const rid_fallback_graph_t& get_rid_fallback_graph() const
     {
         return m_rid_fallback_graph;
     }
 
-       const deps_entry_t& try_ni(const deps_entry_t& entry) const;
+    const deps_entry_t& try_ni(const deps_entry_t& entry) const;
 
 private:
     bool load_standalone(const pal::string_t& deps_path, const json_value& json, const pal::string_t& target_name);
index 5177137..7882463 100644 (file)
@@ -221,10 +221,12 @@ void deps_resolver_t::setup_probe_config(
         m_probes.push_back(probe_config_t::svc(ext_pkgs));
     }
 
-    if (pal::directory_exists(m_fx_dir))
+    for (int i = 1; i < init.fx_definitions.size(); ++i)
     {
-        // FX probe
-        m_probes.push_back(probe_config_t::fx(m_fx_dir, m_fx_deps.get()));
+        if (pal::directory_exists(init.fx_definitions[i]->get_dir()))
+        {
+            m_probes.push_back(probe_config_t::fx(init.fx_definitions[i]->get_dir(), &init.fx_definitions[i]->get_deps()));
+        }
     }
 
     // The published deps directory to be probed: either app or FX directory.
@@ -372,7 +374,7 @@ bool deps_resolver_t::resolve_tpa_list(
     const std::vector<deps_entry_t> empty(0);
     dir_assemblies_t items;
 
-    auto process_entry = [&](const pal::string_t& deps_dir, deps_json_t* deps, const deps_entry_t& entry) -> bool
+    auto process_entry = [&](const pal::string_t& deps_dir, const deps_entry_t& entry) -> bool
     {
         if (entry.is_serviceable)
         {
@@ -428,10 +430,11 @@ bool deps_resolver_t::resolve_tpa_list(
     // Workaround for: csc.deps.json doesn't have the csc.dll
     pal::string_t managed_app_asset = get_filename_without_ext(m_managed_app);
     add_tpa_asset(managed_app_asset, m_managed_app, &items);
-    const auto& deps_entries = m_deps->get_entries(deps_entry_t::asset_types::runtime);
+
+    const auto& deps_entries = get_deps().get_entries(deps_entry_t::asset_types::runtime);
     for (const auto& entry : deps_entries)
     {
-        if (!process_entry(m_app_dir, m_deps.get(), entry))
+        if (!process_entry(m_app_dir, entry))
         {
             return false;
         }
@@ -439,7 +442,7 @@ bool deps_resolver_t::resolve_tpa_list(
 
     // If the deps file wasn't present or has missing entries, then
     // add the app local assemblies to the TPA.
-    if (!m_deps->exists())
+    if (!get_deps().exists())
     {
         dir_assemblies_t local_assemblies;
 
@@ -458,7 +461,7 @@ bool deps_resolver_t::resolve_tpa_list(
         auto additional_deps_entries = additional_deps->get_entries(deps_entry_t::asset_types::runtime);
         for (auto entry : additional_deps_entries)
         {
-            if (!process_entry(m_app_dir, additional_deps.get(), entry))
+            if (!process_entry(m_app_dir, entry))
             {
                 return false;
             }
@@ -466,12 +469,15 @@ bool deps_resolver_t::resolve_tpa_list(
     }
 
     // Probe FX deps entries after app assemblies are added.
-    const auto& fx_entries = m_portable ? m_fx_deps->get_entries(deps_entry_t::asset_types::runtime) : empty;
-    for (const auto& entry : fx_entries)
+    for (int i = 1; i < m_fx_definitions.size(); ++i)
     {
-        if (!process_entry(m_fx_dir, m_fx_deps.get(), entry))
+        const auto& fx_entries = m_portable ? m_fx_definitions[i]->get_deps().get_entries(deps_entry_t::asset_types::runtime) : empty;
+        for (const auto& entry : fx_entries)
         {
-            return false;
+            if (!process_entry(m_fx_definitions[i]->get_dir(), entry))
+            {
+                return false;
+            }
         }
     }
 
@@ -523,8 +529,6 @@ void deps_resolver_t::resolve_additional_deps(const hostpolicy_init_t& init)
     }
 
     pal::string_t additional_deps_serialized = init.additional_deps_serialized;
-    pal::string_t fx_name = init.fx_name;
-    pal::string_t fx_ver = init.fx_ver;
 
     if (additional_deps_serialized.empty())
     {
@@ -544,7 +548,7 @@ void deps_resolver_t::resolve_additional_deps(const hostpolicy_init_t& init)
             {
                 trace::verbose(_X("Using specified additional deps.json: '%s'"), 
                     additional_deps_path.c_str());
-                    
+
                 m_additional_deps_files.push_back(additional_deps_path);
             }
             else
@@ -555,30 +559,35 @@ void deps_resolver_t::resolve_additional_deps(const hostpolicy_init_t& init)
         }
         else
         {
-            // We'll search deps files in 'base_dir'/shared/fx_name/fx_ver
-            append_path(&additional_deps_path, _X("shared"));
-            append_path(&additional_deps_path, fx_name.c_str());
-            append_path(&additional_deps_path, fx_ver.c_str());
-
-            // The resulting list will be empty if 'additional_deps_path' is not a valid directory path
-            std::vector<pal::string_t> list;
-            pal::readdir(additional_deps_path, _X("*.deps.json"), &list);
-            for (pal::string_t json_file : list)
+            for (int i = 1; i < m_fx_definitions.size(); ++i)
             {
-                pal::string_t json_full_path = additional_deps_path;
-                append_path(&json_full_path, json_file.c_str());
-                m_additional_deps_files.push_back(json_full_path);
-
-                trace::verbose(_X("Using specified additional deps.json: '%s'"), 
-                    json_full_path.c_str());
+                // We'll search deps files in 'base_dir'/shared/fx_name/fx_ver
+                pal::string_t additional_deps_path_fx = additional_deps_path;
+                append_path(&additional_deps_path_fx, _X("shared"));
+                append_path(&additional_deps_path_fx, m_fx_definitions[i]->get_name().c_str());
+                append_path(&additional_deps_path_fx, m_fx_definitions[i]->get_found_version().c_str());
+
+                // The resulting list will be empty if 'additional_deps_path_fx' is not a valid directory path
+                std::vector<pal::string_t> list;
+                pal::readdir(additional_deps_path_fx, _X("*.deps.json"), &list);
+                for (pal::string_t json_file : list)
+                {
+                    pal::string_t json_full_path = additional_deps_path_fx;
+                    append_path(&json_full_path, json_file.c_str());
+                    m_additional_deps_files.push_back(json_full_path);
+
+                    trace::verbose(_X("Using specified additional deps.json: '%s'"),
+                        json_full_path.c_str());
+                }
             }
         }
     }
 
+    auto rids = get_root_framework(m_fx_definitions).get_deps().get_rid_fallback_graph();
     for (pal::string_t json_file : m_additional_deps_files)
     {
         m_additional_deps.push_back(std::unique_ptr<deps_json_t>(
-            new deps_json_t(true, json_file, m_fx_deps->get_rid_fallback_graph())));
+            new deps_json_t(true, json_file, rids)));
     }
 }
 
@@ -615,8 +624,6 @@ bool deps_resolver_t::resolve_probe_dirs(
     pal::string_t non_serviced;
 
     std::vector<deps_entry_t> empty(0);
-    const auto& entries = m_deps->get_entries(asset_type);
-    const auto& fx_entries = m_portable ? m_fx_deps->get_entries(asset_type) : empty;
 
     pal::string_t candidate;
 
@@ -660,6 +667,8 @@ bool deps_resolver_t::resolve_probe_dirs(
         return true;
     };
 
+    // Add app entries
+    const auto& entries = get_deps().get_entries(asset_type);
     for (const auto& entry : entries)
     {
         if (!add_package_cache_entry(entry, m_app_dir))
@@ -669,7 +678,7 @@ bool deps_resolver_t::resolve_probe_dirs(
     }
 
     // If the deps file is missing add known locations.
-    if (!m_deps->exists())
+    if (!get_deps().exists())
     {
         // App local path
         add_unique_path(asset_type, m_app_dir, &items, output, &non_serviced, core_servicing);
@@ -692,11 +701,16 @@ bool deps_resolver_t::resolve_probe_dirs(
         }
     }
     
-    for (const auto& entry : fx_entries)
+    // Add fx package locations to fx_dir
+    for (int i = 1; i < m_fx_definitions.size(); ++i)
     {
-        if (!add_package_cache_entry(entry, m_fx_dir))
+        const auto& fx_entries = m_fx_definitions[i]->get_deps().get_entries(asset_type);
+        for (const auto& entry : fx_entries)
         {
-            return false;
+            if (!add_package_cache_entry(entry, m_fx_definitions[i]->get_dir()))
+            {
+                return false;
+            }
         }
     }
 
index b852f24..07d93a7 100644 (file)
@@ -9,6 +9,7 @@
 #include "pal.h"
 #include "args.h"
 #include "trace.h"
+#include "fx_definition.h"
 #include "deps_format.h"
 #include "deps_entry.h"
 #include "runtime_config.h"
@@ -26,27 +27,38 @@ struct probe_paths_t
 class deps_resolver_t
 {
 public:
-    deps_resolver_t(const hostpolicy_init_t& init, const arguments_t& args)
-        : m_fx_dir(init.fx_dir)
+    deps_resolver_t(hostpolicy_init_t& init, const arguments_t& args)
+        : m_fx_definitions(init.fx_definitions)
         , m_app_dir(args.app_dir)
         , m_managed_app(args.managed_application)
         , m_portable(init.is_portable)
-        , m_deps(nullptr)
-        , m_fx_deps(nullptr)
         , m_core_servicing(args.core_servicing)
     {
-        m_deps_file = args.deps_path;
-        if (m_portable)
-        {
-            m_fx_deps_file = get_fx_deps(m_fx_dir, init.fx_name);
-            trace::verbose(_X("Using %s FX deps file"), m_fx_deps_file.c_str());
-            trace::verbose(_X("Using %s deps file"), m_deps_file.c_str());
-            m_fx_deps = std::unique_ptr<deps_json_t>(new deps_json_t(false, m_fx_deps_file));
-            m_deps = std::unique_ptr<deps_json_t>(new deps_json_t(true, m_deps_file, m_fx_deps->get_rid_fallback_graph()));
-        }
-        else
+        int root_framework = m_fx_definitions.size() - 1;
+
+        for (int i = root_framework; i >= 0; --i)
         {
-            m_deps = std::unique_ptr<deps_json_t>(new deps_json_t(false, m_deps_file));
+            if (i == 0)
+            {
+                m_fx_definitions[i]->set_deps_file(args.deps_path);
+                trace::verbose(_X("Using %s deps file"), m_fx_definitions[i]->get_deps_file().c_str());
+            }
+            else
+            {
+                pal::string_t fx_deps_file = get_fx_deps(m_fx_definitions[i]->get_dir(), m_fx_definitions[i]->get_name());
+                m_fx_definitions[i]->set_deps_file(fx_deps_file);
+                trace::verbose(_X("Using Fx %s deps file"), fx_deps_file.c_str());
+            }
+
+            if (i == root_framework)
+            {
+                m_fx_definitions[i]->parse_deps();
+            }
+            else
+            {
+                // The rid graph is obtained from the root framework
+                m_fx_definitions[i]->parse_deps(m_fx_definitions[root_framework]->get_deps().get_rid_fallback_graph());
+            }
         }
 
         resolve_additional_deps(init);
@@ -57,21 +69,25 @@ public:
 
     bool valid(pal::string_t* errors)
     {
-        if (!m_deps->is_valid())
-        {
-            errors->assign(_X("An error occurred while parsing ") + m_deps_file);
-            return false;
-        }
-        if (m_portable && !m_fx_deps->exists())
+        for (int i = 0; i < m_fx_definitions.size(); ++i)
         {
-            errors->assign(_X("A fatal error was encountered, missing dependencies manifest at: ") + m_fx_deps_file);
-            return false;
-        }
-        if (m_portable && !m_fx_deps->is_valid())
-        {
-            errors->assign(_X("An error occurred while parsing ") + m_fx_deps_file);
-            return false;
+            // Verify the deps file exists. The app deps file does not need to exist
+            if (i != 0)
+            {
+                if (!m_fx_definitions[i]->get_deps().exists())
+                {
+                    errors->assign(_X("A fatal error was encountered, missing dependencies manifest at: ") + m_fx_definitions[i]->get_deps_file());
+                    return false;
+                }
+            }
+
+            if (!m_fx_definitions[i]->get_deps().is_valid())
+            {
+                errors->assign(_X("An error occurred while parsing: ") + m_fx_definitions[i]->get_deps_file());
+                return false;
+            }
         }
+
         errors->clear();
         return true;
     }
@@ -100,14 +116,19 @@ public:
     void resolve_additional_deps(
         const hostpolicy_init_t& init);
 
-    const pal::string_t& get_fx_deps_file() const
+    const deps_json_t& get_deps() const
     {
-        return m_fx_deps_file;
+        return get_app(m_fx_definitions).get_deps();
     }
-    
+
     const pal::string_t& get_deps_file() const
     {
-        return m_deps_file;
+        return get_app(m_fx_definitions).get_deps_file();
+    }
+
+    const fx_definition_vector_t& get_fx_definitions() const
+    {
+        return m_fx_definitions;
     }
 
 private:
@@ -143,8 +164,7 @@ private:
         const pal::string_t& deps_dir,
         pal::string_t* candidate);
 
-    // Framework deps file.
-    pal::string_t m_fx_dir;
+    fx_definition_vector_t& m_fx_definitions;
 
     pal::string_t m_app_dir;
 
@@ -156,40 +176,20 @@ private:
         const pal::string_t& asset_path,
         dir_assemblies_t* items);
 
-    std::unordered_map<pal::string_t, pal::string_t> m_patch_roll_forward_cache;
-    std::unordered_map<pal::string_t, pal::string_t> m_prerelease_roll_forward_cache;
-
-    pal::string_t m_package_cache;
-
     // The managed application the dependencies are being resolved for.
     pal::string_t m_managed_app;
 
     // Servicing root, could be empty on platforms that don't support or when errors occur.
     pal::string_t m_core_servicing;
 
-    // Special entry for api-sets
-    std::unordered_set<pal::string_t> m_api_set_paths;
-
     // Special entry for coreclr path
     pal::string_t m_coreclr_path;
 
     // Special entry for JIT path
     pal::string_t m_clrjit_path;
 
-    // The filepath for the app deps
-    pal::string_t m_deps_file;
-
     // The filepaths for the app custom deps
     std::vector<pal::string_t> m_additional_deps_files;
-    
-    // The filepath for the fx deps
-    pal::string_t m_fx_deps_file;
-
-    // Deps files for the fx
-    std::unique_ptr<deps_json_t> m_fx_deps;
-
-    // Deps files for the app
-    std::unique_ptr<deps_json_t>  m_deps;
 
     // Custom deps files for the app
     std::vector< std::unique_ptr<deps_json_t> > m_additional_deps;
@@ -197,9 +197,6 @@ private:
     // Various probe configurations.
     std::vector<probe_config_t> m_probes;
 
-    // Is the deps file valid
-    bool m_deps_valid;
-
     // Fallback probe dir
     std::vector<pal::string_t> m_additional_probes;
 
index 99bc30d..81c2192 100644 (file)
@@ -41,7 +41,9 @@ set(SOURCES
     ../coreclr.cpp
     ../deps_resolver.cpp
     ../deps_format.cpp
-    ../deps_entry.cpp)
+    ../deps_entry.cpp
+    ../fx_definition.cpp
+)
 
 
 if(WIN32)
diff --git a/src/installer/corehost/cli/fx_definition.cpp b/src/installer/corehost/cli/fx_definition.cpp
new file mode 100644 (file)
index 0000000..9044098
--- /dev/null
@@ -0,0 +1,42 @@
+// 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 "deps_format.h"
+#include "pal.h"
+#include "runtime_config.h"
+#include "fx_definition.h"
+
+fx_definition_t::fx_definition_t()
+{
+}
+
+fx_definition_t::fx_definition_t(
+    const pal::string_t& name,
+    const pal::string_t& dir,
+    const pal::string_t& requested_version,
+    const pal::string_t& found_version)
+    : m_name(name)
+    , m_dir(dir)
+    , m_requested_version(requested_version)
+    , m_found_version(found_version)
+{
+}
+
+void fx_definition_t::parse_runtime_config(
+    const pal::string_t& path,
+    const pal::string_t& dev_path,
+    const runtime_config_t* defaults
+)
+{
+    m_runtime_config.parse(path, dev_path, defaults);
+}
+
+void fx_definition_t::parse_deps()
+{
+    m_deps.parse(false, m_deps_file);
+}
+
+void fx_definition_t::parse_deps(const deps_json_t::rid_fallback_graph_t& graph)
+{
+    m_deps.parse(true, m_deps_file, graph);
+}
diff --git a/src/installer/corehost/cli/fx_definition.h b/src/installer/corehost/cli/fx_definition.h
new file mode 100644 (file)
index 0000000..dec0290
--- /dev/null
@@ -0,0 +1,57 @@
+// 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_DEFINITION_H__
+#define __FX_DEFINITION_H__
+
+#include "pal.h"
+#include "deps_format.h"
+#include "runtime_config.h"
+
+class fx_definition_t
+{
+public:
+    fx_definition_t();
+
+    fx_definition_t(
+        const pal::string_t& name,
+        const pal::string_t& dir,
+        const pal::string_t& requested_version,
+        const pal::string_t& found_version);
+
+    const pal::string_t& get_name() const { return m_name; }
+    const pal::string_t& get_requested_version() const { return m_requested_version; }
+    const pal::string_t& get_found_version() const { return m_found_version; }
+    const pal::string_t& get_dir() const { return m_dir; }
+    const runtime_config_t& get_runtime_config() const { return m_runtime_config; }
+    void parse_runtime_config(const pal::string_t& path, const pal::string_t& dev_path, const runtime_config_t* defaults);
+
+    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; }
+    const deps_json_t& get_deps() const { return m_deps; }
+    void parse_deps();
+    void parse_deps(const deps_json_t::rid_fallback_graph_t& graph);
+
+private:
+    pal::string_t m_name;
+    pal::string_t m_dir;
+    pal::string_t m_requested_version;
+    pal::string_t m_found_version;
+    runtime_config_t m_runtime_config;
+    pal::string_t m_deps_file;
+    deps_json_t m_deps;
+};
+
+typedef std::vector<std::unique_ptr<fx_definition_t>> fx_definition_vector_t;
+
+static const fx_definition_t& get_root_framework(const fx_definition_vector_t& fx_definitions)
+{
+    return *fx_definitions[fx_definitions.size() - 1];
+}
+
+static const fx_definition_t& get_app(const fx_definition_vector_t& fx_definitions)
+{
+    return *fx_definitions[0];
+}
+
+#endif // __FX_DEFINITION_H__
index a6dd7dc..1038287 100644 (file)
@@ -36,10 +36,11 @@ set(SOURCES
     ../json/casablanca/src/json/json_parsing.cpp
     ../json/casablanca/src/json/json_serialization.cpp
     ../json/casablanca/src/utilities/asyncrt_utils.cpp
+    ../fx_definition.cpp
     ./hostfxr.cpp
     ./fx_ver.cpp
-    ./fx_muxer.cpp)
-
+    ./fx_muxer.cpp
+)
 
 if(WIN32)
     list(APPEND SOURCES
index 750a9fb..ba6da20 100644 (file)
@@ -6,6 +6,7 @@
 #include "utils.h"
 #include "libhost.h"
 #include "args.h"
+#include "fx_definition.h"
 #include "fx_ver.h"
 #include "fx_muxer.h"
 #include "trace.h"
@@ -158,9 +159,9 @@ void get_all_sdk_versions(
 }
 
 /**
- * When the framework is not found, display detailed error message
- *   about available frameworks and installation of new framework.
- */
+* 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,
     const pal::string_t& fx_name,
@@ -220,9 +221,9 @@ void handle_missing_framework_error(
 }
 
 /**
- * Resolve the hostpolicy version from deps.
- *  - Scan the deps file's libraries section and find the hostpolicy version in the file.
- */
+* Resolve the hostpolicy version from deps.
+*  - Scan the deps file's libraries section and find the hostpolicy version in the file.
+*/
 pal::string_t resolve_hostpolicy_version_from_deps(const pal::string_t& deps_json)
 {
     trace::verbose(_X("--- Resolving %s version from deps json [%s]"), LIBHOSTPOLICY_NAME, deps_json.c_str());
@@ -276,9 +277,9 @@ pal::string_t resolve_hostpolicy_version_from_deps(const pal::string_t& deps_jso
 }
 
 /**
- * Given a directory and a version, find if the package relative
- *     dir under the given directory contains hostpolicy.dll
- */
+* Given a directory and a version, find if the package relative
+*     dir under the given directory contains hostpolicy.dll
+*/
 bool to_hostpolicy_package_dir(const pal::string_t& dir, const pal::string_t& version, pal::string_t* candidate)
 {
     assert(!version.empty());
@@ -313,9 +314,9 @@ bool to_hostpolicy_package_dir(const pal::string_t& dir, const pal::string_t& ve
 }
 
 /**
- * Given a nuget version, detect if a serviced hostpolicy is available at
- *   platform servicing location.
- */
+* Given a nuget version, detect if a serviced hostpolicy is available at
+*   platform servicing location.
+*/
 bool hostpolicy_exists_in_svc(const pal::string_t& version, pal::string_t* resolved_dir)
 {
     if (version.empty())
@@ -330,8 +331,8 @@ bool hostpolicy_exists_in_svc(const pal::string_t& version, pal::string_t* resol
 }
 
 /**
- * Given path to app binary, say app.dll or app.exe, retrieve the app.deps.json.
- */
+* Given path to app binary, say app.dll or app.exe, retrieve the app.deps.json.
+*/
 pal::string_t get_deps_from_app_binary(const pal::string_t& app)
 {
     assert(app.find(DIR_SEPARATOR) != pal::string_t::npos);
@@ -341,7 +342,6 @@ pal::string_t get_deps_from_app_binary(const pal::string_t& app)
     pal::string_t deps_file;
     deps_file.assign(get_directory(app));
 
-
     // Then the app name and the file extension
     pal::string_t app_name = get_filename(app);
     deps_file.append(app_name, 0, app_name.find_last_of(_X(".")));
@@ -350,9 +350,9 @@ pal::string_t get_deps_from_app_binary(const pal::string_t& app)
 }
 
 /**
- * Given a version and probing paths, find if package layout
- *    directory containing hostpolicy exists.
- */
+* Given a version and probing paths, find if package layout
+*    directory containing hostpolicy exists.
+*/
 bool resolve_hostpolicy_dir_from_probe_paths(const pal::string_t& version, const std::vector<pal::string_t>& probe_realpaths, pal::string_t* candidate)
 {
     if (probe_realpaths.empty() || version.empty())
@@ -381,24 +381,25 @@ bool resolve_hostpolicy_dir_from_probe_paths(const pal::string_t& version, const
 }
 
 /**
-* Given FX location, app binary and specified --depsfile, return deps that contains hostpolicy.dll
+* Return name of deps file for app.
 */
 pal::string_t get_deps_file(
-    const pal::string_t& fx_dir,
+    bool portable,
     const pal::string_t& app_candidate,
     const pal::string_t& specified_deps_file,
-    const runtime_config_t& config)
+    const fx_definition_vector_t& fx_definitions
+)
 {
-    if (config.get_portable())
+    if (portable)
     {
-        pal::string_t deps_file = fx_dir;
-
+        // The hostpolicy is resolved from the root framework's name and location.
+        pal::string_t deps_file = get_root_framework(fx_definitions).get_dir();
         if (!deps_file.empty() && deps_file.back() != DIR_SEPARATOR)
         {
             deps_file.push_back(DIR_SEPARATOR);
         }
-        // Portable app's hostpolicy is resolved from FX deps
-        return deps_file + config.get_fx_name() + _X(".deps.json");
+
+        return deps_file + get_root_framework(fx_definitions).get_name() + _X(".deps.json");
     }
     else
     {
@@ -408,27 +409,29 @@ pal::string_t get_deps_file(
 }
 
 /**
-* Given own location, FX location, app binary and specified --depsfile and probe paths
-*     return location that is expected to contain hostpolicy
+* Return location that is expected to contain hostpolicy
 */
-bool fx_muxer_t::resolve_hostpolicy_dir(host_mode_t mode,
+bool fx_muxer_t::resolve_hostpolicy_dir(
+    host_mode_t mode,
     const pal::string_t& own_dir,
-    const pal::string_t& fx_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,
-    const runtime_config_t& config,
     pal::string_t* impl_dir)
 {
+    bool portable = get_app(fx_definitions).get_runtime_config().get_portable();
+
     // Obtain deps file for the given configuration.
-    pal::string_t resolved_deps = get_deps_file(fx_dir, app_candidate, specified_deps_file, config);
+    pal::string_t resolved_deps = get_deps_file(portable, app_candidate, specified_deps_file, fx_definitions);
 
     // Resolve hostpolicy version out of the deps file.
     pal::string_t version = resolve_hostpolicy_version_from_deps(resolved_deps);
     if (trace::is_enabled() && version.empty() && pal::file_exists(resolved_deps))
     {
-        trace::warning(_X("Dependency manifest %s does not contain an entry for %s"), resolved_deps.c_str(), _STRINGIFY(HOST_POLICY_PKG_NAME));
+        trace::warning(_X("Dependency manifest %s does not contain an entry for %s"),
+            resolved_deps.c_str(), _STRINGIFY(HOST_POLICY_PKG_NAME));
     }
 
     // Check if the given version of the hostpolicy exists in servicing.
@@ -439,16 +442,11 @@ bool fx_muxer_t::resolve_hostpolicy_dir(host_mode_t mode,
 
     // Get the expected directory that would contain hostpolicy.
     pal::string_t expected;
-    if (config.get_portable())
+    if (portable)
     {
-        if (!pal::directory_exists(fx_dir))
-        {
-            pal::string_t fx_version = specified_fx_version.empty() ? config.get_fx_version() : specified_fx_version;
-            handle_missing_framework_error(mode, config.get_fx_name(), fx_version, fx_dir, own_dir);
-            return false;
-        }
-
-        expected = fx_dir;
+        // The hostpolicy is required to be in the root framework's location
+        expected.assign(get_root_framework(fx_definitions).get_dir());
+        assert(pal::directory_exists(expected));
     }
     else
     {
@@ -481,16 +479,19 @@ bool fx_muxer_t::resolve_hostpolicy_dir(host_mode_t mode,
     }
 
     // If it still couldn't be found, somebody upstack messed up. Flag an error for the "expected" location.
-    trace::error(_X("A fatal error was encountered. The library '%s' required to execute the application was not found in '%s'."), LIBHOSTPOLICY_NAME, expected.c_str());
-    if (mode == host_mode_t::muxer && !config.get_portable())
+    trace::error(_X("A fatal error was encountered. The library '%s' required to execute the application was not found in '%s'."),
+        LIBHOSTPOLICY_NAME, expected.c_str());
+    if (mode == host_mode_t::muxer && !portable)
     {
-        if (!pal::file_exists(config.get_path()))
+        if (!pal::file_exists(get_app(fx_definitions).get_runtime_config().get_path()))
         {
-            trace::error(_X("Failed to run as a standalone app because there is no '%s' file. If this should be a portable app instead, add that file specifying the appropriate framework."), config.get_path().c_str());
+            trace::error(_X("Failed to run as a standalone app because there is no '%s' file. If this should be a portable app instead, add that file specifying the appropriate framework."),
+                get_app(fx_definitions).get_runtime_config().get_path().c_str());
         }
-        else if (config.get_fx_name().empty())
+        else if (get_app(fx_definitions).get_runtime_config().get_fx_name().empty())
         {
-            trace::error(_X("Failed to run as a standalone app because there is no framework specified in '%s'. If this should be a portable app instead, specify the appropriate framework in that file."), config.get_path().c_str());
+            trace::error(_X("Failed to run as a standalone app because there is no framework specified in '%s'. If this should be a portable app instead, specify the appropriate framework in that file."),
+                get_app(fx_definitions).get_runtime_config().get_path().c_str());
         }
     }
     return false;
@@ -509,11 +510,11 @@ fx_ver_t fx_muxer_t::resolve_framework_version(const std::vector<fx_ver_t>& vers
     {
         if (roll_fwd_on_no_candidate_fx > 0)
         {
-            trace::verbose(_X("'Roll forward on no candidate fx' enabled. Looking for the least production greater than or equal to [%s]"), fx_ver.c_str());
+            trace::verbose(_X("'Roll forward on no candidate fx' enabled. Looking for the least production greater than or equal to [%s]"),
+                fx_ver.c_str());
             fx_ver_t next_lowest(-1, -1, -1);
             for (const auto& ver : version_list)
             {
-
                 if (!ver.is_prerelease() && ver >= specified)
                 {
                     next_lowest = (next_lowest == fx_ver_t(-1, -1, -1)) ? ver : std::min(next_lowest, ver);
@@ -568,11 +569,12 @@ fx_ver_t fx_muxer_t::resolve_framework_version(const std::vector<fx_ver_t>& vers
     return most_compatible;
 }
 
-pal::string_t fx_muxer_t::resolve_fx_dir(host_mode_t mode,
-    const pal::string_t& own_dir,
+fx_definition_t* fx_muxer_t::resolve_fx(
+    host_mode_t mode,
     const runtime_config_t& config,
-    const pal::string_t& specified_fx_version,
-    const int& roll_fwd_on_no_candidate_fx_val)
+    const pal::string_t& own_dir,
+    const pal::string_t& specified_fx_version
+)
 {
     // No FX resolution for standalone apps.
     assert(mode != host_mode_t::standalone);
@@ -580,19 +582,21 @@ pal::string_t fx_muxer_t::resolve_fx_dir(host_mode_t mode,
     // If invoking using FX dotnet.exe, use own directory.
     if (mode == host_mode_t::split_fx)
     {
-        return own_dir;
+        return new fx_definition_t(config.get_fx_name(), own_dir, pal::string_t(), pal::string_t());
     }
-    assert(mode == host_mode_t::muxer);
 
-    trace::verbose(_X("--- Resolving FX directory, specified '%s'"), specified_fx_version.c_str());
-    const auto fx_name = config.get_fx_name();
-    const auto fx_ver = specified_fx_version.empty() ? config.get_fx_version() : specified_fx_version;
+    assert(!config.get_fx_name().empty());
+    assert(!config.get_fx_version().empty());
+
+    trace::verbose(_X("--- Resolving FX directory, name '%s' version '%s'"),
+        config.get_fx_name().c_str(), config.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);
     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());
-        return pal::string_t();
+        return nullptr;
     }
 
     // Multi-level SharedFX lookup will look for the most appropriate version in several locations
@@ -614,8 +618,8 @@ pal::string_t fx_muxer_t::resolve_fx_dir(host_mode_t mode,
         }
     }
 
-    bool patch_roll_fwd_enabled = config.get_patch_roll_fwd();
     pal::string_t selected_fx_dir;
+    pal::string_t selected_fx_version;
     fx_ver_t selected_ver(-1, -1, -1);
 
     for (pal::string_t dir : hive_dir)
@@ -624,7 +628,7 @@ pal::string_t fx_muxer_t::resolve_fx_dir(host_mode_t mode,
         trace::verbose(_X("Searching FX directory in [%s]"), fx_dir.c_str());
 
         append_path(&fx_dir, _X("shared"));
-        append_path(&fx_dir, fx_name.c_str());
+        append_path(&fx_dir, config.get_fx_name().c_str());
 
         bool do_roll_forward = false;
         if (specified_fx_version.empty())
@@ -632,7 +636,7 @@ pal::string_t fx_muxer_t::resolve_fx_dir(host_mode_t mode,
             if (!specified.is_prerelease())
             {
                 // If production and no roll forward use given version.
-                do_roll_forward = (patch_roll_fwd_enabled) || (roll_fwd_on_no_candidate_fx_val > 0);
+                do_roll_forward = (config.get_patch_roll_fwd()) || (config.get_roll_fwd_on_no_candidate_fx() > 0);
             }
             else
             {
@@ -646,13 +650,14 @@ pal::string_t fx_muxer_t::resolve_fx_dir(host_mode_t mode,
         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(), patch_roll_fwd_enabled, roll_fwd_on_no_candidate_fx_val, fx_ver.c_str());
+                specified_fx_version.c_str(), config.get_patch_roll_fwd(), config.get_roll_fwd_on_no_candidate_fx(), fx_ver.c_str());
 
             append_path(&fx_dir, fx_ver.c_str());
             if (pal::directory_exists(fx_dir))
             {
-                trace::verbose(_X("Chose FX version [%s]"), fx_dir.c_str());
-                return fx_dir;
+                selected_fx_dir = fx_dir;
+                selected_fx_version = fx_ver;
+                break;
             }
         }
         else
@@ -670,7 +675,7 @@ pal::string_t fx_muxer_t::resolve_fx_dir(host_mode_t mode,
                 }
             }
 
-            fx_ver_t resolved_ver = resolve_framework_version(version_list, fx_ver, specified, patch_roll_fwd_enabled, roll_fwd_on_no_candidate_fx_val);
+            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());
 
             pal::string_t resolved_ver_str = resolved_ver.as_str();
             append_path(&fx_dir, resolved_ver_str.c_str());
@@ -681,13 +686,14 @@ pal::string_t fx_muxer_t::resolve_fx_dir(host_mode_t mode,
                 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, patch_roll_fwd_enabled, roll_fwd_on_no_candidate_fx_val);
+                resolved_ver = resolve_framework_version(version_list, fx_ver, specified, config.get_patch_roll_fwd(), config.get_roll_fwd_on_no_candidate_fx());
 
                 if (resolved_ver != selected_ver)
                 {
                     trace::verbose(_X("Changing Selected FX version from [%s] to [%s]"), selected_fx_dir.c_str(), fx_dir.c_str());
                     selected_ver = resolved_ver;
                     selected_fx_dir = fx_dir;
+                    selected_fx_version = resolved_ver_str;
                 }
             }
         }
@@ -696,11 +702,12 @@ pal::string_t fx_muxer_t::resolve_fx_dir(host_mode_t mode,
     if (selected_fx_dir.empty())
     {
         trace::error(_X("It was not possible to find any compatible framework version"));
-        return pal::string_t();
+        return nullptr;
     }
 
     trace::verbose(_X("Chose FX version [%s]"), selected_fx_dir.c_str());
-    return selected_fx_dir;
+
+    return new fx_definition_t(config.get_fx_name(), selected_fx_dir, fx_ver, selected_fx_version);
 }
 
 pal::string_t fx_muxer_t::resolve_cli_version(const pal::string_t& global_json)
@@ -969,7 +976,6 @@ int muxer_info()
 
 int muxer_usage(bool is_sdk_present)
 {
-
     std::vector<host_option> known_opts = fx_muxer_t::get_known_opts(true, host_mode_t::invalid, true);
 
     if (!is_sdk_present)
@@ -1170,31 +1176,12 @@ int fx_muxer_t::parse_args_and_execute(
     return read_config_and_execute(own_dir, app_candidate, opts, new_argc, new_argv, mode);
 }
 
-int fx_muxer_t::read_config_and_execute(
-    const pal::string_t& own_dir,
+int read_config(
+    fx_definition_t& app,
     const pal::string_t& app_candidate,
-    const std::unordered_map<pal::string_t, std::vector<pal::string_t>>& opts,
-    int new_argc, const pal::char_t** new_argv, host_mode_t mode)
+    pal::string_t& runtime_config
+)
 {
-    pal::string_t opts_fx_version = _X("--fx-version");
-    pal::string_t opts_roll_fwd_on_no_candidate_fx = _X("--roll-forward-on-no-candidate-fx");
-    pal::string_t opts_deps_file = _X("--depsfile");
-    pal::string_t opts_probe_path = _X("--additionalprobingpath");
-    pal::string_t opts_additional_deps = _X("--additional-deps");
-    pal::string_t opts_runtime_config = _X("--runtimeconfig");
-
-    pal::string_t fx_version = get_last_known_arg(opts, opts_fx_version, _X(""));
-    pal::string_t roll_fwd_on_no_candidate_fx = get_last_known_arg(opts, opts_roll_fwd_on_no_candidate_fx, _X(""));
-    pal::string_t deps_file = get_last_known_arg(opts, opts_deps_file, _X(""));
-    pal::string_t additional_deps = get_last_known_arg(opts, opts_additional_deps, _X(""));
-    pal::string_t runtime_config = get_last_known_arg(opts, opts_runtime_config, _X(""));
-    std::vector<pal::string_t> spec_probe_paths = opts.count(opts_probe_path) ? opts.find(opts_probe_path)->second : std::vector<pal::string_t>();
-
-    if (!deps_file.empty() && (!pal::realpath(&deps_file) || !pal::file_exists(deps_file)))
-    {
-        trace::error(_X("The specified deps.json [%s] does not exist"), deps_file.c_str());
-        return StatusCode::InvalidArgFailure;
-    }
     if (!runtime_config.empty() && (!pal::realpath(&runtime_config) || !pal::file_exists(runtime_config)))
     {
         trace::error(_X("The specified runtimeconfig.json [%s] does not exist"), runtime_config.c_str());
@@ -1214,67 +1201,146 @@ int fx_muxer_t::read_config_and_execute(
         get_runtime_config_paths_from_arg(runtime_config, &config_file, &dev_config_file);
     }
 
-    runtime_config_t config(config_file, dev_config_file);
-    if (!config.is_valid())
+    app.parse_runtime_config(config_file, dev_config_file, nullptr);
+    if (!app.get_runtime_config().is_valid())
     {
-        trace::error(_X("Invalid runtimeconfig.json [%s] [%s]"), config.get_path().c_str(), config.get_dev_path().c_str());
+        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());
         return StatusCode::InvalidConfigFile;
     }
 
-    // Append specified probe paths first and then config file probe paths into realpaths.
-    std::vector<pal::string_t> probe_realpaths;
-    for (const auto& path : spec_probe_paths)
+    return 0;
+}
+
+int fx_muxer_t::read_config_and_execute(
+    const pal::string_t& own_dir,
+    const pal::string_t& app_candidate,
+    const std::unordered_map<pal::string_t, std::vector<pal::string_t>>& opts,
+    int new_argc, const pal::char_t** new_argv, host_mode_t mode)
+{
+    pal::string_t opts_fx_version = _X("--fx-version");
+    pal::string_t opts_roll_fwd_on_no_candidate_fx = _X("--roll-forward-on-no-candidate-fx");
+    pal::string_t opts_deps_file = _X("--depsfile");
+    pal::string_t opts_probe_path = _X("--additionalprobingpath");
+    pal::string_t opts_additional_deps = _X("--additional-deps");
+    pal::string_t opts_runtime_config = _X("--runtimeconfig");
+
+    pal::string_t fx_version_specified = get_last_known_arg(opts, opts_fx_version, _X(""));
+    pal::string_t roll_fwd_on_no_candidate_fx = get_last_known_arg(opts, opts_roll_fwd_on_no_candidate_fx, _X(""));
+    pal::string_t deps_file = get_last_known_arg(opts, opts_deps_file, _X(""));
+    pal::string_t additional_deps = get_last_known_arg(opts, opts_additional_deps, _X(""));
+    pal::string_t runtime_config = get_last_known_arg(opts, opts_runtime_config, _X(""));
+    std::vector<pal::string_t> spec_probe_paths = opts.count(opts_probe_path) ? opts.find(opts_probe_path)->second : std::vector<pal::string_t>();
+
+    if (!deps_file.empty() && (!pal::realpath(&deps_file) || !pal::file_exists(deps_file)))
     {
-        append_probe_realpath(path, &probe_realpaths, config.get_tfm());
+        trace::error(_X("The specified deps.json [%s] does not exist"), deps_file.c_str());
+        return StatusCode::InvalidArgFailure;
     }
-    for (const auto& path : config.get_probe_paths())
+
+    // 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);
+    if (rc)
     {
-        append_probe_realpath(path, &probe_realpaths, config.get_tfm());
+        return rc;
     }
 
+    auto config = app->get_runtime_config();
+
     // 'Roll forward on no candidate fx' is disabled by default. It can be enabled through:
     // 1. Command line argument (--roll-forward-on-no-candidate-fx)
     // 2. Runtimeconfig json file ('rollForwardOnNoCandidateFx' property)
     // 3. DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX env var
     // The conflicts will be resolved by following the priority rank described above (from 1 to 3).
     // The env var condition is verified in the config file processing
-    int roll_fwd_on_no_candidate_fx_val = config.get_roll_fwd_on_no_candidate_fx();
     if (!roll_fwd_on_no_candidate_fx.empty())
     {
-        roll_fwd_on_no_candidate_fx_val = pal::xtoi(roll_fwd_on_no_candidate_fx.c_str());
+        config.set_roll_fwd_on_no_candidate_fx(pal::xtoi(roll_fwd_on_no_candidate_fx.c_str()));
     }
 
+    // Determine additional deps
     pal::string_t additional_deps_serialized;
-
     bool is_portable = config.get_portable();
-    if (is_portable) {
+    if (is_portable)
+    {
         additional_deps_serialized = additional_deps;
         if (additional_deps_serialized.empty())
         {
             // additional_deps_serialized stays empty if DOTNET_ADDITIONAL_DEPS env var is not defined
             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())
+        {
+            fx_definition_t* fx = resolve_fx(mode, config, own_dir, 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(), own_dir);
+                return FrameworkMissingFailure;
+            }
+
+            fx_definitions.push_back(std::unique_ptr<fx_definition_t>(fx));
+
+            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);
+
+            config = fx->get_runtime_config();
+            if (!config.is_valid())
+            {
+                trace::error(_X("Invalid framework config.json [%s]"), config.get_path().c_str());
+                return StatusCode::InvalidConfigFile;
+            }
+
+            // Only the first framework can have a specified version (through --fx-version)
+            version.clear();
+        }
     }
 
-    pal::string_t fx_dir = is_portable ? resolve_fx_dir(mode, own_dir, config, fx_version, roll_fwd_on_no_candidate_fx_val) : _X("");
+    // Append specified probe paths first and then config file probe paths into realpaths.
+    std::vector<pal::string_t> probe_realpaths;
+
+    // The tfm is taken from the app.
+    pal::string_t tfm = get_app(fx_definitions).get_runtime_config().get_tfm();
+
+    for (const auto& path : spec_probe_paths)
+    {
+        append_probe_realpath(path, &probe_realpaths, tfm);
+    }
+
+    // Each framework can add probe paths
+    for (const auto& fx : fx_definitions)
+    {
+        for (const auto& path : fx->get_runtime_config().get_probe_paths())
+        {
+            append_probe_realpath(path, &probe_realpaths, tfm);
+        }
+    }
 
     trace::verbose(_X("Executing as a %s app as per config file [%s]"),
-        (is_portable ? _X("portable") : _X("standalone")), config_file.c_str());
+        (is_portable ? _X("portable") : _X("standalone")), config.get_path().c_str());
 
     pal::string_t impl_dir;
-    if (!resolve_hostpolicy_dir(mode, own_dir, fx_dir, app_candidate, deps_file, fx_version, probe_realpaths, config, &impl_dir))
+    if (!resolve_hostpolicy_dir(mode, own_dir, fx_definitions, app_candidate, deps_file, fx_version_specified, probe_realpaths, &impl_dir))
     {
         return CoreHostLibMissingFailure;
     }
 
-    corehost_init_t init(deps_file, additional_deps_serialized, probe_realpaths, fx_dir, mode, config);
+    corehost_init_t init(deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions);
     return execute_app(impl_dir, &init, new_argc, new_argv);
 }
 
 /**
- *  Main entrypoint to detect operating mode and perform corehost, muxer,
- *  standalone application activation and the SDK activation.
- */
+*  Main entrypoint to detect operating mode and perform corehost, muxer,
+*  standalone application activation and the SDK activation.
+*/
 int fx_muxer_t::execute(const int argc, const pal::char_t* argv[])
 {
     pal::string_t own_path;
index 3b762ac..f58c95b 100644 (file)
@@ -3,6 +3,7 @@
 
 class corehost_init_t;
 class runtime_config_t;
+class fx_definition_t; 
 struct fx_ver_t;
 
 #include "libhost.h"
@@ -26,18 +27,21 @@ private:
         const std::unordered_map<pal::string_t, std::vector<pal::string_t>>& opts,
         int new_argc, const pal::char_t** new_argv, host_mode_t mode);
     static int parse_args_and_execute(const pal::string_t& own_dir, const pal::string_t& own_dll, int argoff, int argc, const pal::char_t* argv[], bool exec_mode, host_mode_t mode, bool* can_execute);
-    static bool resolve_hostpolicy_dir(host_mode_t mode,
+    static bool resolve_hostpolicy_dir(
+        host_mode_t mode,
         const pal::string_t& own_dir,
-        const pal::string_t& fx_dir,
-        const pal::string_t& app_or_deps_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,
-        const runtime_config_t& config,
         pal::string_t* impl_dir);
     static fx_ver_t resolve_framework_version(const std::vector<fx_ver_t>& version_list, const pal::string_t& fx_ver, const fx_ver_t& specified, const bool& patch_roll_fwd, const int& roll_fwd_on_no_candidate_fx);
-    static pal::string_t resolve_fx_dir(host_mode_t mode, const pal::string_t& own_dir, const runtime_config_t& config, const pal::string_t& specified_fx_version, const int& roll_fwd_on_no_candidate_fx_val);
+    static fx_definition_t* resolve_fx(
+        host_mode_t mode,
+        const runtime_config_t& config,
+        const pal::string_t& own_dir,
+        const pal::string_t& specified_fx_version);
     static pal::string_t resolve_cli_version(const pal::string_t& global);
     static bool resolve_sdk_dotnet_path(const pal::string_t& own_dir, pal::string_t* cli_sdk);
 };
-
index b8d92f6..3649e84 100644 (file)
@@ -100,8 +100,25 @@ int run(const arguments_t& args)
     pal::pal_clrstring(probe_paths.native, &native_dirs_cstr);
     pal::pal_clrstring(probe_paths.resources, &resources_dirs_cstr);
 
-    pal::pal_clrstring(resolver.get_fx_deps_file(), &fx_deps);
-    pal::pal_clrstring(resolver.get_deps_file() + _X(";") + resolver.get_fx_deps_file(), &deps);
+    pal::string_t fx_deps_str;
+    if (resolver.get_fx_definitions().size() >= 2)
+    {
+        // Use the root fx to define FX_DEPS_FILE
+        fx_deps_str = get_root_framework(resolver.get_fx_definitions()).get_deps_file();
+    }
+    pal::pal_clrstring(fx_deps_str, &fx_deps);
+
+    // Get all deps files
+    pal::string_t allDeps;
+    for (int i = 0; i < resolver.get_fx_definitions().size(); ++i)
+    {
+        allDeps += resolver.get_fx_definitions()[i]->get_deps_file();
+        if (i < resolver.get_fx_definitions().size() - 1)
+        {
+            allDeps += _X(";");
+        }
+    }
+    pal::pal_clrstring(allDeps, &deps);
 
     pal::pal_clrstring(resolver.get_lookup_probe_directories(), &probe_directories);
 
index f130e08..22e8b0c 100644 (file)
@@ -9,20 +9,9 @@
 void get_runtime_config_paths_from_app(const pal::string_t& app, pal::string_t* cfg, pal::string_t* dev_cfg)
 {
     auto name = get_filename_without_ext(app);
+    auto path = get_directory(app);
 
-    auto json_name = name + _X(".runtimeconfig.json");
-    auto dev_json_name = name + _X(".runtimeconfig.dev.json");
-
-    auto json_path = get_directory(app);
-    auto dev_json_path = json_path;
-
-    append_path(&json_path, json_name.c_str());
-    append_path(&dev_json_path, dev_json_name.c_str());
-
-    trace::verbose(_X("Runtime config is cfg=%s dev=%s"), json_path.c_str(), dev_json_path.c_str());
-
-    dev_cfg->assign(dev_json_path);
-    cfg->assign(json_path);
+    get_runtime_config_paths(path, name, cfg, dev_cfg);
 }
 
 void get_runtime_config_paths_from_arg(const pal::string_t& arg, pal::string_t* cfg, pal::string_t* dev_cfg)
@@ -44,6 +33,21 @@ void get_runtime_config_paths_from_arg(const pal::string_t& arg, pal::string_t*
     cfg->assign(json_path);
 }
 
+void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& name, pal::string_t* cfg, pal::string_t* dev_cfg)
+{
+    auto json_path = path;
+    auto json_name = name + _X(".runtimeconfig.json");
+    append_path(&json_path, json_name.c_str());
+    cfg->assign(json_path);
+
+    auto dev_json_path = path;
+    auto dev_json_name = name + _X(".runtimeconfig.dev.json");
+    append_path(&dev_json_path, dev_json_name.c_str());
+    dev_cfg->assign(dev_json_path);
+
+    trace::verbose(_X("Runtime config is cfg=%s dev=%s"), json_path.c_str(), dev_json_path.c_str());
+}
+
 host_mode_t detect_operating_mode(const pal::string_t& own_dir, const pal::string_t& own_dll, const pal::string_t& own_name)
 {
     if (coreclr_exists_in_dir(own_dir) || pal::file_exists(own_dll))
index 52c69f2..6ab995e 100644 (file)
@@ -6,6 +6,7 @@
 #include <stdint.h>
 #include "trace.h"
 #include "runtime_config.h"
+#include "fx_definition.h"
 #include "fx_ver.h"
 
 enum host_mode_t
@@ -51,6 +52,10 @@ struct host_interface_t
     const pal::char_t* tfm;
     const pal::char_t* additional_deps_serialized;
     const pal::char_t* fx_ver;
+    strarr_t fx_names;
+    strarr_t fx_dirs;
+    strarr_t fx_requested_versions;
+    strarr_t fx_found_versions;
     // !! WARNING / WARNING / WARNING / WARNING / WARNING / WARNING / WARNING / WARNING / WARNING
     // !! 1. Only append to this structure to maintain compat.
     // !! 2. Any nested structs should not use compiler specific padding (pack with _HOST_INTERFACE_PACK)
@@ -76,7 +81,11 @@ static_assert(offsetof(host_interface_t, host_mode) == 14 * sizeof(size_t), "Str
 static_assert(offsetof(host_interface_t, tfm) == 15 * sizeof(size_t), "Struct offset breaks backwards compatibility");
 static_assert(offsetof(host_interface_t, additional_deps_serialized) == 16 * sizeof(size_t), "Struct offset breaks backwards compatibility");
 static_assert(offsetof(host_interface_t, fx_ver) == 17 * sizeof(size_t), "Struct offset breaks backwards compatibility");
-static_assert(sizeof(host_interface_t) == 18 * sizeof(size_t), "Did you add static asserts for the newly added fields?");
+static_assert(offsetof(host_interface_t, fx_names) == 18 * sizeof(size_t), "Struct offset breaks backwards compatibility");
+static_assert(offsetof(host_interface_t, fx_dirs) == 20 * sizeof(size_t), "Struct offset breaks backwards compatibility");
+static_assert(offsetof(host_interface_t, fx_requested_versions) == 22 * sizeof(size_t), "Struct offset breaks backwards compatibility");
+static_assert(offsetof(host_interface_t, fx_found_versions) == 24 * sizeof(size_t), "Struct offset breaks backwards compatibility");
+static_assert(sizeof(host_interface_t) == 26 * sizeof(size_t), "Did you add static asserts for the newly added fields?");
 
 #define HOST_INTERFACE_LAYOUT_VERSION_HI 0x16041101 // YYMMDD:nn always increases when layout breaks compat.
 #define HOST_INTERFACE_LAYOUT_VERSION_LO sizeof(host_interface_t)
@@ -89,8 +98,6 @@ private:
     std::vector<const pal::char_t*> m_clr_keys_cstr;
     std::vector<const pal::char_t*> m_clr_values_cstr;
     const pal::string_t m_tfm;
-    const pal::string_t m_fx_dir;
-    const pal::string_t m_fx_name;
     const pal::string_t m_deps_file;
     const pal::string_t m_additional_deps_serialized;
     bool m_portable;
@@ -100,52 +107,65 @@ private:
     bool m_prerelease_roll_forward;
     host_mode_t m_host_mode;
     host_interface_t m_host_interface;
-    const pal::string_t m_fx_ver;
+    std::vector<pal::string_t> m_fx_names;
+    std::vector<const pal::char_t*> m_fx_names_cstr;
+    std::vector<pal::string_t> m_fx_dirs;
+    std::vector<const pal::char_t*> m_fx_dirs_cstr;
+    std::vector<pal::string_t> m_fx_requested_versions;
+    std::vector<const pal::char_t*> m_fx_requested_versions_cstr;
+    std::vector<pal::string_t> m_fx_found_versions;
+    std::vector<const pal::char_t*> m_fx_found_versions_cstr;
 public:
     corehost_init_t(
         const pal::string_t& deps_file,
         const pal::string_t& additional_deps_serialized,
         const std::vector<pal::string_t>& probe_paths,
-        const pal::string_t& fx_dir,
         const host_mode_t mode,
-        const runtime_config_t& runtime_config)
-        : m_fx_dir(fx_dir)
-        , m_fx_name(runtime_config.get_fx_name())
-        , m_deps_file(deps_file)
+        const fx_definition_vector_t& fx_definitions)
+        : m_deps_file(deps_file)
         , m_additional_deps_serialized(additional_deps_serialized)
-        , m_portable(runtime_config.get_portable())
+        , m_portable(get_app(fx_definitions).get_runtime_config().get_portable())
         , m_probe_paths(probe_paths)
-        , m_patch_roll_forward(runtime_config.get_patch_roll_fwd())
-        , m_prerelease_roll_forward(runtime_config.get_prerelease_roll_fwd())
         , m_host_mode(mode)
         , m_host_interface()
-        , m_fx_ver(runtime_config.get_fx_version())
-        , m_tfm(runtime_config.get_tfm())
+        , m_tfm(get_app(fx_definitions).get_runtime_config().get_tfm())
     {
-        runtime_config.config_kv(&m_clr_keys, &m_clr_values);
         make_cstr_arr(m_clr_keys, &m_clr_keys_cstr);
         make_cstr_arr(m_clr_values, &m_clr_values_cstr);
         make_cstr_arr(m_probe_paths, &m_probe_paths_cstr);
-    }
 
-    const pal::string_t& fx_dir() const
-    {
-        return m_fx_dir;
-    }
+        int fx_count = fx_definitions.size();
+        m_fx_names.reserve(fx_count);
+        m_fx_dirs.reserve(fx_count);
+        m_fx_requested_versions.reserve(fx_count);
+        m_fx_found_versions.reserve(fx_count);
 
-    const pal::string_t& tfm() const
-    {
-        return m_tfm;
-    }
+        std::unordered_map<pal::string_t, pal::string_t> combined_properties;
+        for (auto& fx : fx_definitions)
+        {
+            fx->get_runtime_config().combine_properties(combined_properties);
 
-    const pal::string_t& fx_name() const
-    {
-        return m_fx_name;
+            m_fx_names.push_back(fx->get_name());
+            m_fx_dirs.push_back(fx->get_dir());
+            m_fx_requested_versions.push_back(fx->get_requested_version());
+            m_fx_found_versions.push_back(fx->get_found_version());
+        }
+
+        for (const auto& kv : combined_properties)
+        {
+            m_clr_keys.push_back(kv.first);
+            m_clr_values.push_back(kv.second);
+        }
+
+        make_cstr_arr(m_fx_names, &m_fx_names_cstr);
+        make_cstr_arr(m_fx_dirs, &m_fx_dirs_cstr);
+        make_cstr_arr(m_fx_requested_versions, &m_fx_requested_versions_cstr);
+        make_cstr_arr(m_fx_found_versions, &m_fx_found_versions_cstr);
     }
 
-    const pal::string_t& fx_version() const
+    const pal::string_t& tfm() const
     {
-        return m_fx_ver;
+        return m_tfm;
     }
 
     const host_interface_t& get_host_init_data()
@@ -161,9 +181,20 @@ public:
         hi.config_values.len = m_clr_values_cstr.size();
         hi.config_values.arr = m_clr_values_cstr.data();
 
-        hi.fx_dir = m_fx_dir.c_str();
-        hi.fx_name = m_fx_name.c_str();
-        hi.fx_ver = m_fx_ver.c_str();
+        // Keep these for backwards compat
+        if (m_fx_names_cstr.size() > 1)
+        {
+            hi.fx_name = m_fx_names_cstr[1];
+            hi.fx_dir = m_fx_dirs_cstr[1];
+            hi.fx_ver = m_fx_found_versions_cstr[1];
+        }
+        else
+        {
+            hi.fx_name = _X("");
+            hi.fx_dir = _X("");
+            hi.fx_ver = _X("");
+        }
+
         hi.deps_file = m_deps_file.c_str();
         hi.additional_deps_serialized = m_additional_deps_serialized.c_str();
         hi.is_portable = m_portable;
@@ -176,7 +207,19 @@ public:
         hi.host_mode = m_host_mode;
 
         hi.tfm = m_tfm.c_str();
-        
+
+        hi.fx_names.len = m_fx_names_cstr.size();
+        hi.fx_names.arr = m_fx_names_cstr.data();
+
+        hi.fx_dirs.len = m_fx_dirs_cstr.size();
+        hi.fx_dirs.arr = m_fx_dirs_cstr.data();
+
+        hi.fx_requested_versions.len = m_fx_requested_versions_cstr.size();
+        hi.fx_requested_versions.arr = m_fx_requested_versions_cstr.data();
+
+        hi.fx_found_versions.len = m_fx_found_versions_cstr.size();
+        hi.fx_found_versions.arr = m_fx_found_versions_cstr.data();
+
         return hi;
     }
 
@@ -192,7 +235,6 @@ private:
     }
 };
 
-
 struct hostpolicy_init_t
 {
     std::vector<std::vector<char>> cfg_keys;
@@ -200,10 +242,8 @@ struct hostpolicy_init_t
     pal::string_t deps_file;
     pal::string_t additional_deps_serialized;
     std::vector<pal::string_t> probe_paths;
+    fx_definition_vector_t fx_definitions;
     pal::string_t tfm;
-    pal::string_t fx_dir;
-    pal::string_t fx_name;
-    pal::string_t fx_ver;
     host_mode_t host_mode;
     bool patch_roll_forward;
     bool prerelease_roll_forward;
@@ -220,15 +260,17 @@ struct hostpolicy_init_t
 
         trace::verbose(_X("Reading from host interface version: [0x%04x:%d] to initialize policy version: [0x%04x:%d]"), input->version_hi, input->version_lo, HOST_INTERFACE_LAYOUT_VERSION_HI, HOST_INTERFACE_LAYOUT_VERSION_LO);
 
-               //This check is to ensure is an old hostfxr can still load new hostpolicy.
-               //We should not read garbage due to potentially shorter struct size
+        
+        //This check is to ensure is an old hostfxr can still load new hostpolicy.
+        //We should not read garbage due to potentially shorter struct size
+
+        pal::string_t fx_requested_ver;
+
         if (input->version_lo >= offsetof(host_interface_t, host_mode) + sizeof(input->host_mode))
         {
             make_clrstr_arr(input->config_keys.len, input->config_keys.arr, &init->cfg_keys);
             make_clrstr_arr(input->config_values.len, input->config_values.arr, &init->cfg_values);
 
-            init->fx_dir = input->fx_dir;
-            init->fx_name = input->fx_name;
             init->deps_file = input->deps_file;
             init->is_portable = input->is_portable;
 
@@ -251,10 +293,63 @@ struct hostpolicy_init_t
         {
             init->tfm = input->tfm;
         }
+        
         if (input->version_lo >= offsetof(host_interface_t, fx_ver) + sizeof(input->fx_ver))
         {
             init->additional_deps_serialized = input->additional_deps_serialized;
-            init->fx_ver = input->fx_ver;
+            fx_requested_ver = input->fx_ver;
+        }
+
+        int fx_count = 0;
+        if (input->version_lo >= offsetof(host_interface_t, fx_names) + sizeof(input->fx_names))
+        {
+            int fx_count = input->fx_names.len;
+            assert(fx_count > 0);
+            assert(fx_count == input->fx_dirs.len);
+            assert(fx_count == input->fx_requested_versions.len);
+            assert(fx_count == input->fx_found_versions.len);
+
+            std::vector<pal::string_t> fx_names;
+            std::vector<pal::string_t> fx_dirs;
+            std::vector<pal::string_t> fx_requested_versions;
+            std::vector<pal::string_t> fx_found_versions;
+
+            make_palstr_arr(input->fx_names.len, input->fx_names.arr, &fx_names);
+            make_palstr_arr(input->fx_dirs.len, input->fx_dirs.arr, &fx_dirs);
+            make_palstr_arr(input->fx_requested_versions.len, input->fx_requested_versions.arr, &fx_requested_versions);
+            make_palstr_arr(input->fx_found_versions.len, input->fx_found_versions.arr, &fx_found_versions);
+
+            init->fx_definitions.reserve(fx_count);
+            for (int i = 0; i < fx_count; ++i)
+            {
+                auto fx = new fx_definition_t(fx_names[i], fx_dirs[i], fx_requested_versions[i], fx_found_versions[i]);
+                init->fx_definitions.push_back(std::unique_ptr<fx_definition_t>(fx));
+            }
+        }
+        else
+        {
+            // Backward compat; create the fx_definitions[0] and [1] from the previous information
+            init->fx_definitions.reserve(2);
+
+            auto fx = new fx_definition_t();
+            init->fx_definitions.push_back(std::unique_ptr<fx_definition_t>(fx));
+
+            if (init->is_portable)
+            {
+                pal::string_t fx_dir = input->fx_dir;
+                pal::string_t fx_name = input->fx_name;
+
+                // The found_ver was not passed previously, so obtain that from fx_dir
+                pal::string_t fx_found_ver;
+                int index = fx_dir.rfind(DIR_SEPARATOR);
+                if (index != pal::string_t::npos)
+                {
+                    fx_found_ver = fx_dir.substr(index + 1);
+                }
+
+                fx = new fx_definition_t(fx_name, fx_dir, fx_requested_ver, fx_found_ver);
+                init->fx_definitions.push_back(std::unique_ptr<fx_definition_t>(fx));
+            }
         }
 
         return true;
@@ -282,6 +377,7 @@ private:
 
 void get_runtime_config_paths_from_app(const pal::string_t& file, pal::string_t* config_file, pal::string_t* dev_config_file);
 void get_runtime_config_paths_from_arg(const pal::string_t& file, pal::string_t* config_file, pal::string_t* dev_config_file);
+void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& name, pal::string_t* config_file, pal::string_t* dev_config_file);
 
 host_mode_t detect_operating_mode(const pal::string_t& own_dir, const pal::string_t& own_dll, const pal::string_t& own_name);
 bool hostpolicy_exists_in_svc(pal::string_t* resolved_dir);
index b5205ba..e314607 100644 (file)
@@ -8,17 +8,38 @@
 #include "runtime_config.h"
 #include <cassert>
 
-runtime_config_t::runtime_config_t(const pal::string_t& path, const pal::string_t& dev_path)
+runtime_config_t::runtime_config_t()
     : m_patch_roll_fwd(true)
     , m_prerelease_roll_fwd(false)
     , m_roll_fwd_on_no_candidate_fx(0)
-    , m_path(path)
-    , m_dev_path(dev_path)
     , m_portable(false)
+    , m_valid(false)
 {
+}
+
+void runtime_config_t::parse(const pal::string_t& path, const pal::string_t& dev_path, const runtime_config_t* defaults)
+{
+    m_path = path;
+    m_dev_path = dev_path;
+
+    // Apply the defaults from previous config.
+    if (defaults)
+    {
+        m_patch_roll_fwd = defaults->m_patch_roll_fwd;
+        m_prerelease_roll_fwd = defaults->m_prerelease_roll_fwd;
+        m_roll_fwd_on_no_candidate_fx = defaults->m_roll_fwd_on_no_candidate_fx;
+    }
+
     m_valid = ensure_parsed();
+
+    // Overwrite values that can't be changed from previous config.
+    if (defaults)
+    {
+        m_tfm = defaults->m_tfm;
+    }
+
     trace::verbose(_X("Runtime config [%s] is valid=[%d]"), path.c_str(), m_valid);
-} 
+}
 
 bool runtime_config_t::parse_opts(const json_value& opts)
 {
@@ -30,7 +51,7 @@ bool runtime_config_t::parse_opts(const json_value& opts)
     }
 
     const auto& opts_obj = opts.as_object();
-    
+
     auto properties = opts_obj.find(_X("configProperties"));
     if (properties != opts_obj.end())
     {
@@ -103,6 +124,7 @@ bool runtime_config_t::parse_opts(const json_value& opts)
     const auto& fx_obj = framework->second.as_object();
     m_fx_name = fx_obj.at(_X("name")).as_string();
     m_fx_ver = fx_obj.at(_X("version")).as_string();
+
     return true;
 }
 
@@ -113,7 +135,7 @@ bool runtime_config_t::ensure_dev_config_parsed()
     pal::string_t retval;
     if (!pal::file_exists(m_dev_path))
     {
-        // Not existing is not an error.
+        // Not existing is valid.
         return true;
     }
 
@@ -233,6 +255,12 @@ int runtime_config_t::get_roll_fwd_on_no_candidate_fx() const
     return m_roll_fwd_on_no_candidate_fx;
 }
 
+void runtime_config_t::set_roll_fwd_on_no_candidate_fx(int value)
+{
+    assert(m_valid);
+    m_roll_fwd_on_no_candidate_fx = value;
+}
+
 bool runtime_config_t::get_portable() const
 {
     return m_portable;
@@ -243,11 +271,15 @@ const std::list<pal::string_t>& runtime_config_t::get_probe_paths() const
     return m_probe_paths;
 }
 
-void runtime_config_t::config_kv(std::vector<pal::string_t>* keys, std::vector<pal::string_t>* values) const
+// Add each property to combined_properties unless the property already exists.
+// The effect is the first value wins, which would typically be the app's value.
+void runtime_config_t::combine_properties(std::unordered_map<pal::string_t, pal::string_t>& combined_properties) const
 {
     for (const auto& kv : m_properties)
     {
-        keys->push_back(kv.first);
-        values->push_back(kv.second);
+        if (combined_properties.find(kv.first) == combined_properties.end())
+        {
+            combined_properties[kv.first] = kv.second;
+        }
     }
 }
index eb87ff5..e4072d2 100644 (file)
@@ -14,26 +14,28 @@ typedef web::json::value json_value;
 class runtime_config_t
 {
 public:
-    runtime_config_t(const pal::string_t& path, const pal::string_t& dev_path);
+    runtime_config_t();
+    void parse(const pal::string_t& path, const pal::string_t& dev_path, const runtime_config_t* defaults);
     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_gc_server() const;
     const pal::string_t& get_fx_version() const;
+    void set_fx_version(const pal::string_t& value);
     const pal::string_t& get_fx_name() const;
     const pal::string_t& get_tfm() const;
     const std::list<pal::string_t>& get_probe_paths() const;
     bool get_patch_roll_fwd() const;
     bool get_prerelease_roll_fwd() const;
     int get_roll_fwd_on_no_candidate_fx() const;
+    void set_roll_fwd_on_no_candidate_fx(int value);
     bool get_portable() const;
     bool parse_opts(const json_value& opts);
-    void config_kv(std::vector<pal::string_t>*, std::vector<pal::string_t>*) const;
+    void combine_properties(std::unordered_map<pal::string_t, pal::string_t>& combined_properties) const;
 
 private:
     bool ensure_parsed();
     bool ensure_dev_config_parsed();
+
     std::unordered_map<pal::string_t, pal::string_t> m_properties;
     std::vector<std::string> m_prop_keys;
     std::vector<std::string> m_prop_values;
index 79d2db6..604487b 100644 (file)
@@ -27,5 +27,6 @@ enum StatusCode
     InvalidConfigFile           = 0x80008093,
     AppArgNotRunnable           = 0x80008094,
     AppHostExeNotBoundFailure   = 0x80008095,
+    FrameworkMissingFailure     = 0x80008096,
 };
 #endif // __ERROR_CODES_H__
index ab991c6..29266da 100644 (file)
@@ -17,14 +17,26 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
         private string _executableDir;
         private string _globalDir;
         private string _cwdSharedFxBaseDir;
+        private string _cwdSharedUberFxBaseDir;
         private string _userSharedFxBaseDir;
+        private string _userSharedUberFxBaseDir;
         private string _exeSharedFxBaseDir;
+        private string _exeSharedUberFxBaseDir;
         private string _globalSharedFxBaseDir;
+        private string _globalSharedUberFxBaseDir;
         private string _builtSharedFxDir;
+        private string _builtSharedUberFxDir;
+
         private string _cwdSelectedMessage;
         private string _userSelectedMessage;
         private string _exeSelectedMessage;
         private string _globalSelectedMessage;
+
+        private string _cwdFoundUberFxMessage;
+        private string _userFoundUberFxMessage;
+        private string _exeFoundUberFxMessage;
+        private string _globalFoundUberFxMessage;
+
         private string _sharedFxVersion;
         private string _multilevelDir;
         private string _builtDotnet;
@@ -58,11 +70,19 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
             _exeSharedFxBaseDir = Path.Combine(_executableDir, "shared", "Microsoft.NETCore.App");
             _globalSharedFxBaseDir = Path.Combine(_globalDir, "shared", "Microsoft.NETCore.App");
 
+            _cwdSharedUberFxBaseDir = Path.Combine(_currentWorkingDir, "shared", "Microsoft.UberFramework");
+            _userSharedUberFxBaseDir = Path.Combine(_userDir, ".dotnet", RepoDirectories.BuildArchitecture, "shared", "Microsoft.UberFramework");
+            _exeSharedUberFxBaseDir = Path.Combine(_executableDir, "shared", "Microsoft.UberFramework");
+            _globalSharedUberFxBaseDir = Path.Combine(_globalDir, "shared", "Microsoft.UberFramework");
+
             // Create directories. It's necessary to copy the entire publish folder to the exe dir because
             // we'll need to build from it. The CopyDirectory method automatically creates the dest dir
             Directory.CreateDirectory(_cwdSharedFxBaseDir);
             Directory.CreateDirectory(_userSharedFxBaseDir);
             Directory.CreateDirectory(_globalSharedFxBaseDir);
+            Directory.CreateDirectory(_cwdSharedUberFxBaseDir);
+            Directory.CreateDirectory(_userSharedUberFxBaseDir);
+            Directory.CreateDirectory(_globalSharedUberFxBaseDir);
             CopyDirectory(_builtDotnet, _executableDir);
 
             //Copy dotnet to global directory
@@ -80,6 +100,10 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
             _sharedFxVersion = (new DirectoryInfo(greatestVersionSharedFxPath)).Name;
             _builtSharedFxDir = Path.Combine(_builtDotnet, "shared", "Microsoft.NETCore.App", _sharedFxVersion);
 
+            // The uber framework is a copy of the base framework, minus a few files
+            _builtSharedUberFxDir = Path.Combine(_builtDotnet, "shared", "Microsoft.UberFramework", _sharedFxVersion);
+            CreateUberFrameworkArtifacts(_builtSharedFxDir, _builtSharedUberFxDir);
+
             _hostPolicyDllName = Path.GetFileName(fixture.TestProject.HostPolicyDll);
 
             // Trace messages used to identify from which folder the framework was picked
@@ -87,6 +111,11 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
             _userSelectedMessage = $"The expected {_hostPolicyDllName} directory is [{_userSharedFxBaseDir}";
             _exeSelectedMessage = $"The expected {_hostPolicyDllName} directory is [{_exeSharedFxBaseDir}";
             _globalSelectedMessage = $"The expected {_hostPolicyDllName} directory is [{_globalSharedFxBaseDir}";
+
+            _cwdFoundUberFxMessage = $"Chose FX version [{_cwdSharedUberFxBaseDir}";
+            _userFoundUberFxMessage = $"Chose FX version [{_userSharedUberFxBaseDir}";
+            _exeFoundUberFxMessage = $"Chose FX version [{_exeSharedUberFxBaseDir}";
+            _globalFoundUberFxMessage = $"Chose FX version [{_globalSharedUberFxBaseDir}";
         }
 
         [Fact]
@@ -102,7 +131,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
             string runtimeConfig = Path.Combine(fixture.TestProject.OutputDirectory, "SharedFxLookupPortableApp.runtimeconfig.json");
             SetRuntimeConfigJson(runtimeConfig, "9999.0.0");
 
-            // Add a dummy version in the exe dir
+            // Add version in the exe dir
             AddAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.0");
 
             // Version: 9999.0.0
@@ -170,7 +199,10 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
                 .Should()
                 .Pass()
                 .And
-                .HaveStdOutContaining("9999.0.0");
+                .HaveStdOutContaining("Microsoft.NETCore.App 9999.0.0");
+
+            DeleteAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.0");
+            DeleteAvailableSharedFxVersions(_cwdSharedFxBaseDir, "9999.0.0");
         }
 
         [Fact]
@@ -183,8 +215,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
             var appDll = fixture.TestProject.AppDll;
 
             // Add some dummy versions
-            AddAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.2", "9999.0.0-dummy2", "9999.0.0", "9999.0.3", "9999.0.0-dummy3");
-            
+            AddAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.0", "9999.0.2", "9999.0.0-dummy2", "9999.0.3", "9999.0.0-dummy3");
 
             // Version: 9999.0.0 (through --fx-version arg)
             // Exe: 9999.0.2, 9999.0.0-dummy2, 9999.0.0, 9999.0.3, 9999.0.0-dummy3
@@ -224,15 +255,17 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
                 .Should()
                 .Pass()
                 .And
-                .HaveStdOutContaining("9999.0.0")
+                .HaveStdOutContaining("Microsoft.NETCore.App 9999.0.0")
+                .And
+                .HaveStdOutContaining("Microsoft.NETCore.App 9999.0.0-dummy2")
                 .And
-                .HaveStdOutContaining("9999.0.0-dummy2")
+                .HaveStdOutContaining("Microsoft.NETCore.App 9999.0.2")
                 .And
-                .HaveStdOutContaining("9999.0.2")
+                .HaveStdOutContaining("Microsoft.NETCore.App 9999.0.3")
                 .And
-                .HaveStdOutContaining("9999.0.3");
+                .HaveStdOutContaining("Microsoft.NETCore.App 9999.0.0-dummy3");
 
-            DeleteAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.0", "9999.0.3", "9999.0.0-dummy3", "9999.0.2", "9999.0.0-dummy2");
+            DeleteAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.0", "9999.0.2", "9999.0.0-dummy2", "9999.0.3", "9999.0.0-dummy3");
         }
 
         [Fact]
@@ -295,11 +328,13 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
                 .Should()
                 .Pass()
                 .And
-                .HaveStdOutContaining("9999.1.1")
+                .HaveStdOutContaining("Microsoft.NETCore.App 9999.1.1")
                 .And
-                .HaveStdOutContaining("10000.1.1")
+                .HaveStdOutContaining("Microsoft.NETCore.App 10000.1.1")
                 .And
-                .HaveStdOutContaining("10000.1.3");
+                .HaveStdOutContaining("Microsoft.NETCore.App 10000.1.3");
+
+            DeleteAvailableSharedFxVersions(_exeSharedFxBaseDir, "10000.1.1", "10000.1.3");
         }
 
         [Fact]
@@ -343,15 +378,190 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
                 .Should()
                 .Pass()
                 .And
-                .HaveStdOutContaining("9998.0.1")
+                .HaveStdOutContaining("Microsoft.NETCore.App 9998.0.1")
+                .And
+                .HaveStdOutContaining("Microsoft.NETCore.App 9998.1.0")
+                .And
+                .HaveStdOutContaining("Microsoft.NETCore.App 9999.0.0")
+                .And
+                .HaveStdOutContaining("Microsoft.NETCore.App 9999.0.1")
+                .And
+                .HaveStdOutContaining("Microsoft.NETCore.App 9999.1.0");
+
+            DeleteAvailableSharedFxVersions(_exeSharedFxBaseDir, "9998.0.1", "9998.1.0", "9999.0.0", "9999.0.1", "9999.1.0");
+        }
+
+        [Fact]
+        public void Multiple_SharedFxLookup_Independent_Roll_Forward()
+        {
+            var fixture = PreviouslyBuiltAndRestoredPortableTestProjectFixture
+                .Copy();
+
+            var dotnet = fixture.BuiltDotnet;
+            var appDll = fixture.TestProject.AppDll;
+
+            string runtimeConfig = Path.Combine(fixture.TestProject.OutputDirectory, "SharedFxLookupPortableApp.runtimeconfig.json");
+            SetRuntimeConfigJson(runtimeConfig, "7777.0.0", null, useUberFramework: true);
+
+            // Add versions in the exe folders
+            AddAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.0");
+            AddAvailableSharedUberFxVersions(_exeSharedUberFxBaseDir, "9999.0.0", "7777.0.0");
+
+            // Version: NetCoreApp 9999.0.0
+            //          UberFramework 7777.0.0
+            // Exe: NetCoreApp 9999.0.0
+            //      UberFramework 7777.0.0
+            // Expected: 9999.0.0
+            //           7777.0.0
+            dotnet.Exec(appDll)
+                .WorkingDirectory(_currentWorkingDir)
+                .EnvironmentVariable("COREHOST_TRACE", "1")
+                .CaptureStdOut()
+                .CaptureStdErr()
+                .Execute()
+                .Should()
+                .Pass()
+                .And
+                .HaveStdErrContaining(Path.Combine(_exeSelectedMessage, "9999.0.0"))
+                .And
+                .HaveStdErrContaining(Path.Combine(_exeFoundUberFxMessage, "7777.0.0"));
+
+            // Add a newer version to verify roll-forward
+            AddAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.1");
+            AddAvailableSharedUberFxVersions(_exeSharedUberFxBaseDir, "9999.0.0", "7777.0.1");
+
+            // Version: NetCoreApp 9999.0.0
+            //          UberFramework 7777.0.0
+            // Exe: NetCoreApp 9999.0.0, 9999.0.1
+            //      UberFramework 7777.0.0, 7777.0.1
+            // Expected: 9999.0.1
+            //           7777.0.1
+            dotnet.Exec(appDll)
+                .WorkingDirectory(_currentWorkingDir)
+                .EnvironmentVariable("COREHOST_TRACE", "1")
+                .CaptureStdOut()
+                .CaptureStdErr()
+                .Execute()
+                .Should()
+                .Pass()
+                .And
+                .HaveStdErrContaining(Path.Combine(_exeSelectedMessage, "9999.0.1"))
+                .And
+                .HaveStdErrContaining(Path.Combine(_exeFoundUberFxMessage, "7777.0.1"));
+
+            // Verify we have the expected runtime versions
+            dotnet.Exec("--list-runtimes")
+                .WorkingDirectory(_currentWorkingDir)
+                .EnvironmentVariable("COREHOST_TRACE", "1")
+                .WithUserProfile(_userDir)
+                .CaptureStdOut()
+                .Execute()
+                .Should()
+                .Pass()
                 .And
-                .HaveStdOutContaining("9998.1.0")
+                .HaveStdOutContaining("Microsoft.NETCore.App 9999.0.0")
                 .And
-                .HaveStdOutContaining("9999.0.0")
+                .HaveStdOutContaining("Microsoft.NETCore.App 9999.0.1")
                 .And
-                .HaveStdOutContaining("9999.0.1")
+                .HaveStdOutContaining("Microsoft.UberFramework 7777.0.0")
                 .And
-                .HaveStdOutContaining("9999.1.0");
+                .HaveStdOutContaining("Microsoft.UberFramework 7777.0.1");
+
+            DeleteAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.0", "9999.0.1");
+            DeleteAvailableSharedFxVersions(_exeSharedUberFxBaseDir, "7777.0.0", "7777.0.1");
+        }
+
+        [Fact]
+        public void Multiple_SharedFxLookup_Propagated_RuntimeConfig_Value()
+        {
+            var fixture = PreviouslyBuiltAndRestoredPortableTestProjectFixture
+                .Copy();
+
+            var dotnet = fixture.BuiltDotnet;
+            var appDll = fixture.TestProject.AppDll;
+
+            string runtimeConfig = Path.Combine(fixture.TestProject.OutputDirectory, "SharedFxLookupPortableApp.runtimeconfig.json");
+            SetRuntimeConfigJson(runtimeConfig, "7777.0.0", null, useUberFramework: true);
+
+            // Add versions in the exe folders
+            AddAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.1.0");
+            AddAvailableSharedUberFxVersions(_exeSharedUberFxBaseDir, "9999.0.0", "7777.0.0");
+
+            // Version: NetCoreApp 9999.0.0
+            //          UberFramework 7777.0.0
+            // Exe: NetCoreApp 9999.1.0
+            //      UberFramework 7777.0.0
+            // Expected: no compatible version
+            dotnet.Exec(appDll)
+                .WorkingDirectory(_currentWorkingDir)
+                .EnvironmentVariable("COREHOST_TRACE", "1")
+                .CaptureStdOut()
+                .CaptureStdErr()
+                .Execute()
+                .Should()
+                .Fail()
+                .And
+                .HaveStdErrContaining("It was not possible to find any compatible framework version");
+
+            // Add the rollForwardOnNoCandidateFx value to the Uber runtimeconfig which should carry over to NetCoreApp
+            SetRuntimeConfigJson(runtimeConfig, "7777.0.0", rollFwdOnNoCandidateFx: 1, useUberFramework: true);
+
+            // Version: NetCoreApp 9999.0.0
+            //          UberFramework 7777.0.0
+            // Exe: NetCoreApp 9999.1.0
+            //      UberFramework 7777.0.0
+            // Expected: 9999.1.0
+            //           7777.0.0
+            dotnet.Exec(appDll)
+                .WorkingDirectory(_currentWorkingDir)
+                .EnvironmentVariable("COREHOST_TRACE", "1")
+                .CaptureStdOut()
+                .CaptureStdErr()
+                .Execute()
+                .Should()
+                .Pass()
+                .And
+                .HaveStdErrContaining(Path.Combine(_exeSelectedMessage, "9999.1.0"))
+                .And
+                .HaveStdErrContaining(Path.Combine(_exeFoundUberFxMessage, "7777.0.0"));
+
+            DeleteAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.1.0");
+            DeleteAvailableSharedFxVersions(_exeSharedUberFxBaseDir, "7777.0.0");
+        }
+
+        [Fact]
+        public void Multiple_SharedFxLookup_UberFx_Deps_Overrides_NetCoreApp()
+        {
+            var fixture = PreviouslyBuiltAndRestoredPortableTestProjectFixture
+                .Copy();
+
+            var dotnet = fixture.BuiltDotnet;
+            var appDll = fixture.TestProject.AppDll;
+
+            string runtimeConfig = Path.Combine(fixture.TestProject.OutputDirectory, "SharedFxLookupPortableApp.runtimeconfig.json");
+            SetRuntimeConfigJson(runtimeConfig, "7777.0.0", null, useUberFramework: true);
+
+            // Add versions in the exe folders
+            AddAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.0");
+            AddAvailableSharedUberFxVersions(_exeSharedUberFxBaseDir, "9999.0.0", "7777.0.0");
+
+            // The System.Collections.Immutable.dll is located in the UberFramework and NetCoreApp
+            // The System.Collections.dll is only located in NetCoreApp
+            dotnet.Exec(appDll)
+                .WorkingDirectory(_currentWorkingDir)
+                .EnvironmentVariable("COREHOST_TRACE", "1")
+                .CaptureStdOut()
+                .CaptureStdErr()
+                .Execute()
+                .Should()
+                .Pass()
+                .And
+                .HaveStdErrContaining(Path.Combine("7777.0.0", "System.Collections.Immutable.dll"))
+                .And
+                .HaveStdErrContaining(Path.Combine("9999.0.0", "System.Collections.dll"));
+
+            DeleteAvailableSharedFxVersions(_exeSharedFxBaseDir, "9999.0.0");
+            DeleteAvailableSharedFxVersions(_exeSharedUberFxBaseDir, "7777.0.0");
         }
 
         // This method adds a list of new framework version folders in the specified
@@ -377,6 +587,28 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
             }
         }
 
+        // 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
+        private void AddAvailableSharedUberFxVersions(string sharedUberFxBaseDir, string sharedFxBaseVersion, params string[] availableUberVersions)
+        {
+            DirectoryInfo sharedFxUberBaseDirInfo = new DirectoryInfo(sharedUberFxBaseDir);
+
+            if (!sharedFxUberBaseDirInfo.Exists)
+            {
+                sharedFxUberBaseDirInfo.Create();
+            }
+
+            foreach (string version in availableUberVersions)
+            {
+                string newSharedFxDir = Path.Combine(sharedUberFxBaseDir, version);
+                CopyDirectory(_builtSharedUberFxDir, newSharedFxDir);
+
+                string runtimeBaseConfig = Path.Combine(newSharedFxDir, "Microsoft.UberFramework.runtimeconfig.json");
+                SetRuntimeConfigJson(runtimeBaseConfig, sharedFxBaseVersion);
+            }
+        }
+
         // This method removes a list of framework version folders from the specified
         // sharedFxBaseDir.
         // Remarks:
@@ -384,7 +616,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
         //   is thrown.
         // - If a specified version folder does not exist, then a DirectoryNotFoundException
         //   is thrown.
-        private void DeleteAvailableSharedFxVersions(string sharedFxBaseDir, params string[] availableVersions)
+        static private void DeleteAvailableSharedFxVersions(string sharedFxBaseDir, params string[] availableVersions)
         {
             DirectoryInfo sharedFxBaseDirInfo = new DirectoryInfo(sharedFxBaseDir);
 
@@ -411,7 +643,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
         //   (original files and subfolders are deleted).
         // - If the src dir does not exist, then a DirectoryNotFoundException
         //   is thrown.
-        private void CopyDirectory(string srcDir, string dstDir)
+        static private void CopyDirectory(string srcDir, string dstDir)
         {
             DirectoryInfo srcDirInfo = new DirectoryInfo(srcDir);
 
@@ -444,7 +676,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
 
         // MultilevelDirectory is %TEST_ARTIFACTS%\dotnetMultilevelSharedFxLookup\id.
         // We must locate the first non existing id.
-        private string CalculateMultilevelDirectory(string baseMultilevelDir)
+        static private string CalculateMultilevelDirectory(string baseMultilevelDir)
         {
             int count = 0;
             string multilevelDir;
@@ -470,12 +702,14 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
          *   }
          * }
         */
-        private void SetRuntimeConfigJson(string destFile, string version, int? rollFwdOnNoCandidateFx = null)
+        private void SetRuntimeConfigJson(string destFile, string version, int? rollFwdOnNoCandidateFx = null, bool? useUberFramework = false)
         {
+            string name = useUberFramework.HasValue && useUberFramework.Value ? "Microsoft.UberFramework" : "Microsoft.NETCore.App";
+
             JObject runtimeOptions = new JObject(
                 new JProperty("framework",
                     new JObject(
-                        new JProperty("name", "Microsoft.NETCore.App"),
+                        new JProperty("name", name),
                         new JProperty("version", version)
                     )
                 )
@@ -486,10 +720,100 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.MultilevelSharedFxLooku
                 runtimeOptions.Add("rollForwardOnNoCandidateFx", rollFwdOnNoCandidateFx);
             }
 
+            FileInfo file = new FileInfo(destFile);
+            if (!file.Directory.Exists)
+            {
+                file.Directory.Create();
+            }
+
             JObject json = new JObject();
             json.Add("runtimeOptions", runtimeOptions);
-
             File.WriteAllText(destFile, json.ToString());
         }
+
+        static private void CreateUberFrameworkArtifacts(string builtSharedFxDir, string builtSharedUberFxDir)
+        {
+            DirectoryInfo dir = new DirectoryInfo(builtSharedUberFxDir);
+            if (dir.Exists)
+            {
+                dir.Delete(true);
+            }
+
+            dir.Create();
+
+            string fxName = "UberFx";
+            string testAssembly = "System.Collections.Immutable";
+
+            // Create the deps.json. Generated file:
+            /*
+                {
+                  "runtimeTarget": {
+                    "name": "UberFx"
+                  },
+                  "targets": {
+                    "UberFx": {
+                      "System.Collections.Immutable": {
+                        "runtime": {
+                          "System.Collections.Immutable.dll": {}
+                        }
+                      }
+                    }
+                  },
+                  "libraries": {
+                    "System.Collections.Immutable": {
+                      "type": "assemblyreference",
+                      "serviceable": false,
+                      "sha512": ""
+                    }
+                  }
+                }
+             */
+            JObject depsjson = new JObject(
+                new JProperty("runtimeTarget",
+                    new JObject(
+                        new JProperty("name", fxName)
+                    )
+                ),
+                new JProperty("targets",
+                    new JObject(
+                      new JProperty(fxName,
+                          new JObject(
+                              new JProperty(testAssembly,
+                                  new JObject(
+                                      new JProperty("runtime",
+                                          new JObject(
+                                              new JProperty(testAssembly + ".dll",
+                                                  new JObject()
+                                              )
+                                          )
+                                      )
+                                  )
+                              )
+                          )
+                      )
+                  )
+              ),
+                  new JProperty("libraries",
+                      new JObject(
+                          new JProperty(testAssembly,
+                            new JObject(
+                                new JProperty("type", "assemblyreference"),
+                                new JProperty("serviceable", false),
+                                new JProperty("sha512", "")
+                            )
+                        )
+                    )
+                )
+            );
+
+            string depsFile = Path.Combine(builtSharedUberFxDir, "Microsoft.UberFramework.deps.json");
+
+            File.WriteAllText(depsFile, depsjson.ToString());
+
+            // Copy the test assembly
+            string fileSource = Path.Combine(builtSharedFxDir, testAssembly + ".dll");
+            string fileDest = Path.Combine(builtSharedUberFxDir, testAssembly + ".dll");
+            File.Copy(fileSource, fileDest);
+        }
     }
 }