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)
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];
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);
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.
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)
{
// 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;
}
// 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;
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;
}
}
// 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;
+ }
}
}
}
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())
{
{
trace::verbose(_X("Using specified additional deps.json: '%s'"),
additional_deps_path.c_str());
-
+
m_additional_deps_files.push_back(additional_deps_path);
}
else
}
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)));
}
}
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;
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))
}
// 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);
}
}
- 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;
+ }
}
}
#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"
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);
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;
}
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:
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;
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;
// 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;
../coreclr.cpp
../deps_resolver.cpp
../deps_format.cpp
- ../deps_entry.cpp)
+ ../deps_entry.cpp
+ ../fx_definition.cpp
+)
if(WIN32)
--- /dev/null
+// 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);
+}
--- /dev/null
+// 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__
../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
#include "utils.h"
#include "libhost.h"
#include "args.h"
+#include "fx_definition.h"
#include "fx_ver.h"
#include "fx_muxer.h"
#include "trace.h"
}
/**
- * 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,
}
/**
- * 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());
}
/**
- * 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());
}
/**
- * 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())
}
/**
- * 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);
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(".")));
}
/**
- * 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())
}
/**
-* 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
{
}
/**
-* 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.
// 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
{
}
// 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;
{
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);
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);
// 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
}
}
- 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)
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())
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
{
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
}
}
- 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());
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;
}
}
}
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)
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)
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());
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;
class corehost_init_t;
class runtime_config_t;
+class fx_definition_t;
struct fx_ver_t;
#include "libhost.h"
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);
};
-
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);
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)
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))
#include <stdint.h>
#include "trace.h"
#include "runtime_config.h"
+#include "fx_definition.h"
#include "fx_ver.h"
enum host_mode_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)
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)
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;
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()
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;
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;
}
}
};
-
struct hostpolicy_init_t
{
std::vector<std::vector<char>> cfg_keys;
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;
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;
{
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;
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);
#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)
{
}
const auto& opts_obj = opts.as_object();
-
+
auto properties = opts_obj.find(_X("configProperties"));
if (properties != opts_obj.end())
{
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;
}
pal::string_t retval;
if (!pal::file_exists(m_dev_path))
{
- // Not existing is not an error.
+ // Not existing is valid.
return true;
}
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;
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;
+ }
}
}
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;
InvalidConfigFile = 0x80008093,
AppArgNotRunnable = 0x80008094,
AppHostExeNotBoundFailure = 0x80008095,
+ FrameworkMissingFailure = 0x80008096,
};
#endif // __ERROR_CODES_H__
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;
_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
_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
_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]
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
.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]
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
.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]
.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]
.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
}
}
+ // 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:
// 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);
// (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);
// 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;
* }
* }
*/
- 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)
)
)
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);
+ }
}
}