From 4b628f43fc5989c8a3923cb074b905dbc458f11c Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 27 Mar 2018 11:05:20 -0500 Subject: [PATCH] Add support for framework-dependent apphost (#3888) Add support for framework-dependent apphost --- src/corehost/cli/args.cpp | 51 +--- src/corehost/cli/args.h | 8 +- src/corehost/cli/deps_format.cpp | 10 +- src/corehost/cli/deps_format.h | 22 +- src/corehost/cli/deps_resolver.cpp | 14 +- src/corehost/cli/deps_resolver.h | 8 +- src/corehost/cli/dll/CMakeLists.txt | 1 + src/corehost/cli/exe/apphost/CMakeLists.txt | 15 +- .../cli/exe/apphost/startup_config.cpp | 72 +++++ src/corehost/cli/exe/apphost/startup_config.h | 23 ++ src/corehost/cli/fxr/CMakeLists.txt | 1 + src/corehost/cli/fxr/framework_info.cpp | 9 +- src/corehost/cli/fxr/fx_muxer.cpp | 268 +++++++++--------- src/corehost/cli/fxr/fx_muxer.h | 43 +-- src/corehost/cli/fxr/hostfxr.cpp | 22 +- src/corehost/cli/host_startup_info.cpp | 92 ++++++ src/corehost/cli/host_startup_info.h | 32 +++ src/corehost/cli/hostpolicy.cpp | 17 +- src/corehost/cli/libhost.cpp | 17 +- src/corehost/cli/libhost.h | 59 ++-- src/corehost/cli/runtime_config.cpp | 8 +- src/corehost/cli/runtime_config.h | 4 +- src/corehost/common/pal.h | 5 + src/corehost/common/pal.unix.cpp | 15 + src/corehost/common/pal.windows.cpp | 52 ++-- src/corehost/common/utils.cpp | 31 +- src/corehost/common/utils.h | 6 +- src/corehost/corehost.cpp | 227 +++++++++++---- src/corehost/error_codes.h | 1 + ...enThatICareAboutStandaloneAppActivation.cs | 146 ++++++++-- 30 files changed, 907 insertions(+), 372 deletions(-) create mode 100644 src/corehost/cli/exe/apphost/startup_config.cpp create mode 100644 src/corehost/cli/exe/apphost/startup_config.h create mode 100644 src/corehost/cli/host_startup_info.cpp create mode 100644 src/corehost/cli/host_startup_info.h diff --git a/src/corehost/cli/args.cpp b/src/corehost/cli/args.cpp index c255cd06a6..9f0cc242ab 100644 --- a/src/corehost/cli/args.cpp +++ b/src/corehost/cli/args.cpp @@ -8,8 +8,8 @@ arguments_t::arguments_t() : managed_application(_X("")), - own_path(_X("")), - app_dir(_X("")), + host_path(_X("")), + app_root(_X("")), app_argc(0), app_argv(nullptr), core_servicing(_X("")), @@ -62,34 +62,11 @@ bool parse_arguments( { arguments_t& args = *arg_out; - // Try to use argv[0] as own_path to allow for hosts located elsewhere - if (argc >= 1) - { - args.own_path = argv[0]; - if (!args.own_path.empty()) - { - trace::info(_X("Attempting to use argv[0] as path [%s]"), args.own_path.c_str()); - if (!get_path_from_argv(&args.own_path)) - { - trace::warning(_X("Failed to resolve argv[0] as path [%s]. Using location of current executable instead."), args.own_path.c_str()); - args.own_path.clear(); - } - } - } + args.host_path = init.host_info.host_path; - // Get the full name of the application - if (args.own_path.empty() && (!pal::get_own_executable_path(&args.own_path) || !pal::realpath(&args.own_path))) + if (init.host_mode != host_mode_t::apphost) { - trace::error(_X("Failed to resolve full path of the current executable [%s]"), args.own_path.c_str()); - return false; - } - - auto own_name = get_filename(args.own_path); - auto own_dir = get_directory(args.own_path); - - if (init.host_mode != host_mode_t::standalone) - { - // corerun mode. First argument is managed app + // First argument is managed app if (argc < 2) { return false; @@ -100,24 +77,20 @@ bool parse_arguments( trace::error(_X("Failed to locate managed application [%s]"), args.managed_application.c_str()); return false; } - args.app_dir = get_directory(args.managed_application); + args.app_root = get_directory(args.managed_application); args.app_argc = argc - 2; args.app_argv = &argv[2]; } else { - // coreconsole mode. Find the managed app in the same directory - pal::string_t managed_app(own_dir); - - managed_app.append(get_executable(own_name)); - managed_app.append(_X(".dll")); - args.managed_application = managed_app; + // Find the managed app in the same directory + args.managed_application = init.host_info.app_path; if (!pal::realpath(&args.managed_application)) { trace::error(_X("Failed to locate managed application [%s]"), args.managed_application.c_str()); return false; } - args.app_dir = own_dir; + args.app_root = init.host_info.dotnet_root; args.app_argv = &argv[1]; args.app_argc = argc - 1; } @@ -125,7 +98,7 @@ bool parse_arguments( if (!init.deps_file.empty()) { args.deps_path = init.deps_file; - args.app_dir = get_directory(args.deps_path); + args.app_root = get_directory(args.deps_path); } for (const auto& probe : init.probe_paths) @@ -135,7 +108,7 @@ bool parse_arguments( if (args.deps_path.empty()) { - const auto& app_base = args.app_dir; + const auto& app_base = args.app_root; auto app_name = get_filename(args.managed_application); args.deps_path.reserve(app_base.length() + 1 + app_name.length() + 5); @@ -151,7 +124,7 @@ bool parse_arguments( pal::get_default_servicing_directory(&args.core_servicing); - setup_shared_store_paths(init, own_dir, &args); + setup_shared_store_paths(init, get_directory(args.host_path), &args); return true; } diff --git a/src/corehost/cli/args.h b/src/corehost/cli/args.h index 02316a63e6..e9208d13b2 100644 --- a/src/corehost/cli/args.h +++ b/src/corehost/cli/args.h @@ -90,8 +90,8 @@ struct probe_config_t struct arguments_t { - pal::string_t own_path; - pal::string_t app_dir; + pal::string_t host_path; + pal::string_t app_root; pal::string_t deps_path; pal::string_t core_servicing; std::vector probe_paths; @@ -108,8 +108,8 @@ struct arguments_t { if (trace::is_enabled()) { - trace::verbose(_X("-- arguments_t: own_path='%s' app_dir='%s' deps='%s' core_svc='%s' mgd_app='%s'"), - own_path.c_str(), app_dir.c_str(), deps_path.c_str(), core_servicing.c_str(), managed_application.c_str()); + trace::verbose(_X("-- arguments_t: host_path='%s' app_root='%s' deps='%s' core_svc='%s' mgd_app='%s'"), + host_path.c_str(), app_root.c_str(), deps_path.c_str(), core_servicing.c_str(), managed_application.c_str()); for (const auto& probe : probe_paths) { trace::verbose(_X("-- arguments_t: probe dir: '%s'"), probe.c_str()); diff --git a/src/corehost/cli/deps_format.cpp b/src/corehost/cli/deps_format.cpp index 192ea0aa18..c7523035b0 100644 --- a/src/corehost/cli/deps_format.cpp +++ b/src/corehost/cli/deps_format.cpp @@ -320,7 +320,7 @@ bool deps_json_t::process_targets(const json_value& json, const pal::string_t& t return true; } -bool deps_json_t::load_portable(const pal::string_t& deps_path, const json_value& json, const pal::string_t& target_name, const rid_fallback_graph_t& rid_fallback_graph) +bool deps_json_t::load_framework_dependent(const pal::string_t& deps_path, const json_value& json, const pal::string_t& target_name, const rid_fallback_graph_t& rid_fallback_graph) { if (!process_runtime_targets(json, target_name, rid_fallback_graph, &m_rid_assets)) { @@ -367,7 +367,7 @@ bool deps_json_t::load_portable(const pal::string_t& deps_path, const json_value return true; } -bool deps_json_t::load_standalone(const pal::string_t& deps_path, const json_value& json, const pal::string_t& target_name) +bool deps_json_t::load_self_contained(const pal::string_t& deps_path, const json_value& json, const pal::string_t& target_name) { if (!process_targets(json, target_name, &m_assets)) { @@ -438,7 +438,7 @@ bool deps_json_t::has_package(const pal::string_t& name, const pal::string_t& ve // Load the deps file and parse its "entry" lines which contain the "fields" of // the entry. Populate an array of these entries. // -bool deps_json_t::load(bool portable, const pal::string_t& deps_path, const rid_fallback_graph_t& rid_fallback_graph) +bool deps_json_t::load(bool is_framework_dependent, const pal::string_t& deps_path, const rid_fallback_graph_t& rid_fallback_graph) { m_deps_file = deps_path; m_file_exists = pal::file_exists(deps_path); @@ -473,9 +473,9 @@ bool deps_json_t::load(bool portable, const pal::string_t& deps_path, const rid_ runtime_target.as_string(): runtime_target.at(_X("name")).as_string(); - trace::verbose(_X("Loading deps file... %s as portable=[%d]"), deps_path.c_str(), portable); + trace::verbose(_X("Loading deps file... %s as framework dependent=[%d]"), deps_path.c_str(), is_framework_dependent); - return (portable) ? load_portable(deps_path, json, name, rid_fallback_graph) : load_standalone(deps_path, json, name); + return (is_framework_dependent) ? load_framework_dependent(deps_path, json, name, rid_fallback_graph) : load_self_contained(deps_path, json, name); } catch (const std::exception& je) { diff --git a/src/corehost/cli/deps_format.h b/src/corehost/cli/deps_format.h index c877227923..4e349a7b5f 100644 --- a/src/corehost/cli/deps_format.h +++ b/src/corehost/cli/deps_format.h @@ -33,25 +33,25 @@ class deps_json_t { } - deps_json_t(bool portable, const pal::string_t& deps_path) - : deps_json_t(portable, deps_path, m_rid_fallback_graph /* dummy */) + deps_json_t(bool is_framework_dependent, const pal::string_t& deps_path) + : deps_json_t(is_framework_dependent, deps_path, m_rid_fallback_graph /* dummy */) { } - deps_json_t(bool portable, const pal::string_t& deps_path, const rid_fallback_graph_t& graph) + deps_json_t(bool is_framework_dependent, const pal::string_t& deps_path, const rid_fallback_graph_t& graph) : deps_json_t() { - m_valid = load(portable, deps_path, graph); + m_valid = load(is_framework_dependent, deps_path, graph); } - void parse(bool portable, const pal::string_t& deps_path) + void parse(bool is_framework_dependent, const pal::string_t& deps_path) { - m_valid = load(portable, deps_path, m_rid_fallback_graph /* dummy */); + m_valid = load(is_framework_dependent, deps_path, m_rid_fallback_graph /* dummy */); } - void parse(bool portable, const pal::string_t& deps_path, const rid_fallback_graph_t& graph) + void parse(bool is_framework_dependent, const pal::string_t& deps_path, const rid_fallback_graph_t& graph) { - m_valid = load(portable, deps_path, graph); + m_valid = load(is_framework_dependent, deps_path, graph); } const std::vector& get_entries(deps_entry_t::asset_types type) const @@ -85,9 +85,9 @@ class deps_json_t } private: - bool load_standalone(const pal::string_t& deps_path, const json_value& json, const pal::string_t& target_name); - bool load_portable(const pal::string_t& deps_path, const json_value& json, const pal::string_t& target_name, const rid_fallback_graph_t& rid_fallback_graph); - bool load(bool portable, const pal::string_t& deps_path, const rid_fallback_graph_t& rid_fallback_graph); + bool load_self_contained(const pal::string_t& deps_path, const json_value& json, const pal::string_t& target_name); + bool load_framework_dependent(const pal::string_t& deps_path, const json_value& json, const pal::string_t& target_name, const rid_fallback_graph_t& rid_fallback_graph); + bool load(bool is_framework_dependent, const pal::string_t& deps_path, const rid_fallback_graph_t& rid_fallback_graph); bool process_runtime_targets(const json_value& json, const pal::string_t& target_name, const rid_fallback_graph_t& rid_fallback_graph, rid_specific_assets_t* p_assets); bool process_targets(const json_value& json, const pal::string_t& target_name, deps_assets_t* p_assets); diff --git a/src/corehost/cli/deps_resolver.cpp b/src/corehost/cli/deps_resolver.cpp index bb9c37c443..4550ccf160 100644 --- a/src/corehost/cli/deps_resolver.cpp +++ b/src/corehost/cli/deps_resolver.cpp @@ -542,7 +542,7 @@ bool deps_resolver_t::resolve_tpa_list( // A minor\major roll-forward affects which layer wins bool is_minor_or_major_roll_forward = m_fx_definitions[i]->did_minor_or_major_roll_forward_occur(); - const auto& deps_entries = m_portable ? m_fx_definitions[i]->get_deps().get_entries(deps_entry_t::asset_types::runtime) : empty; + const auto& deps_entries = m_is_framework_dependent ? m_fx_definitions[i]->get_deps().get_entries(deps_entry_t::asset_types::runtime) : empty; for (const auto& entry : deps_entries) { if (!process_entry(m_fx_definitions[i]->get_dir(), entry, i, is_minor_or_major_roll_forward)) @@ -588,12 +588,12 @@ void deps_resolver_t::init_known_entry_path(const deps_entry_t& entry, const pal void deps_resolver_t::resolve_additional_deps(const hostpolicy_init_t& init) { - if (!m_portable) + if (!m_is_framework_dependent) { - // Additional deps.json support is only available for portable apps due to the following constraints: + // Additional deps.json support is only available for framework-dependent apps due to the following constraints: // - // 1) Unlike Portable Apps, Standalone apps do not have details of the SharedFX and Version they target. - // 2) Unlike Portable Apps, Standalone apps do not have RID fallback graph that is required for looking up + // 1) Unlike framework-dependent Apps, self-contained apps do not have details of the SharedFX and Version they target. + // 2) Unlike framework-dependent Apps, self-contained apps do not have RID fallback graph that is required for looking up // the correct native assets from nuget packages. return; @@ -727,7 +727,7 @@ bool deps_resolver_t::resolve_probe_dirs( } else { - // For standalone apps, apphost.exe will be renamed. Do not use the full package name + // For self-contained apps do not use the full package name // because of rid-fallback could happen (ex: CentOS falling back to RHEL) if ((entry.asset.name == _X("apphost")) && ends_with(entry.library_name, _X(".Microsoft.NETCore.DotNetAppHost"), false)) { @@ -797,7 +797,7 @@ bool deps_resolver_t::resolve_probe_dirs( // Entrypoint to resolve TPA, native and resources path ordering to pass to CoreCLR. // // Parameters: -// app_dir - The application local directory +// app_root - The application local directory // package_dir - The directory path to where packages are restored // package_cache_dir - The directory path to secondary cache for packages // clr_dir - The directory where the host loads the CLR diff --git a/src/corehost/cli/deps_resolver.h b/src/corehost/cli/deps_resolver.h index 3067c7448c..6738b8711d 100644 --- a/src/corehost/cli/deps_resolver.h +++ b/src/corehost/cli/deps_resolver.h @@ -41,9 +41,9 @@ class deps_resolver_t public: deps_resolver_t(hostpolicy_init_t& init, const arguments_t& args) : m_fx_definitions(init.fx_definitions) - , m_app_dir(args.app_dir) + , m_app_dir(args.app_root) , m_managed_app(args.managed_application) - , m_portable(init.is_portable) + , m_is_framework_dependent(init.is_framework_dependent) , m_core_servicing(args.core_servicing) { int root_framework = m_fx_definitions.size() - 1; @@ -218,8 +218,8 @@ class deps_resolver_t // Fallback probe dir std::vector m_additional_probes; - // Is the deps file portable app? - bool m_portable; + // Is the deps file for an app using shared frameworks? + bool m_is_framework_dependent; }; #endif // DEPS_RESOLVER_H diff --git a/src/corehost/cli/dll/CMakeLists.txt b/src/corehost/cli/dll/CMakeLists.txt index f342313b78..95e33c08e7 100644 --- a/src/corehost/cli/dll/CMakeLists.txt +++ b/src/corehost/cli/dll/CMakeLists.txt @@ -40,6 +40,7 @@ set(SOURCES ../breadcrumbs.cpp ../args.cpp ../hostpolicy.cpp + ../host_startup_info.cpp ../coreclr.cpp ../deps_resolver.cpp ../deps_format.cpp diff --git a/src/corehost/cli/exe/apphost/CMakeLists.txt b/src/corehost/cli/exe/apphost/CMakeLists.txt index 617d88d113..3e142f554e 100644 --- a/src/corehost/cli/exe/apphost/CMakeLists.txt +++ b/src/corehost/cli/exe/apphost/CMakeLists.txt @@ -18,8 +18,21 @@ if (NOT CMAKE_SYSTEM_NAME STREQUAL Darwin) endif() set(SKIP_VERSIONING 1) + +include_directories(./) +include_directories(../../json/casablanca/include) + +set(SOURCES + startup_config.cpp + ../../json/casablanca/src/json/json.cpp + ../../json/casablanca/src/json/json_parsing.cpp + ../../json/casablanca/src/json/json_serialization.cpp + ../../json/casablanca/src/utilities/asyncrt_utils.cpp + ../../fxr/fx_ver.cpp +) + include(../exe.cmake) -set(SOURCES) + add_definitions(-DFEATURE_APPHOST=1) install_library_and_symbols (apphost) diff --git a/src/corehost/cli/exe/apphost/startup_config.cpp b/src/corehost/cli/exe/apphost/startup_config.cpp new file mode 100644 index 0000000000..852e37330a --- /dev/null +++ b/src/corehost/cli/exe/apphost/startup_config.cpp @@ -0,0 +1,72 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include "pal.h" +#include "trace.h" +#include "utils.h" +#include "cpprest/json.h" +#include "startup_config.h" + +startup_config_t::startup_config_t() + : m_valid(false) +{ +} + +void startup_config_t::parse(const pal::string_t& path) +{ + trace::verbose(_X("Attempting to read startup config: %s"), path.c_str()); + + m_valid = parse_internal(path); + if (!m_valid) + { + trace::verbose(_X("Did not successfully parse the startup.config.json")); + } +} + +bool startup_config_t::parse_internal(const pal::string_t& path) +{ + pal::string_t retval; + if (!pal::file_exists(path)) + { + // Not existing is not an error. + return true; + } + + pal::ifstream_t file(path); + if (!file.good()) + { + trace::verbose(_X("File stream not good %s"), path.c_str()); + return false; + } + + if (skip_utf8_bom(&file)) + { + trace::verbose(_X("UTF-8 BOM skipped while reading [%s]"), path.c_str()); + } + + try + { + const auto root = web::json::value::parse(file); + const auto& json = root.as_object(); + const auto options = json.find(_X("startupOptions")); + if (options != json.end()) + { + const auto& prop_obj = options->second.as_object(); + + auto appRoot = prop_obj.find(_X("appRoot")); + if (appRoot != prop_obj.end()) + { + m_app_root = appRoot->second.as_string(); + } + } + } + catch (const std::exception& je) + { + pal::string_t jes; + (void)pal::utf8_palstring(je.what(), &jes); + trace::error(_X("A JSON parsing exception occurred in [%s]: %s"), path.c_str(), jes.c_str()); + return false; + } + + return true; +} diff --git a/src/corehost/cli/exe/apphost/startup_config.h b/src/corehost/cli/exe/apphost/startup_config.h new file mode 100644 index 0000000000..27d7c684dc --- /dev/null +++ b/src/corehost/cli/exe/apphost/startup_config.h @@ -0,0 +1,23 @@ +// 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 __STARTUP_CONFIG_H__ +#define __STARTUP_CONFIG_H__ + +#include "pal.h" + +class startup_config_t +{ +public: + startup_config_t(); + void parse(const pal::string_t& path); + const bool is_valid() const { return m_valid; } + const pal::string_t& get_app_root() const { return m_app_root; } + +private: + bool parse_internal(const pal::string_t& path); + + pal::string_t m_app_root; + bool m_valid; +}; +#endif // __STARTUP_CONFIG_H__ diff --git a/src/corehost/cli/fxr/CMakeLists.txt b/src/corehost/cli/fxr/CMakeLists.txt index 822f2364a6..2e75a85179 100644 --- a/src/corehost/cli/fxr/CMakeLists.txt +++ b/src/corehost/cli/fxr/CMakeLists.txt @@ -33,6 +33,7 @@ set(SOURCES ../libhost.cpp ../deps_format.cpp ../deps_entry.cpp + ../host_startup_info.cpp ../runtime_config.cpp ../json/casablanca/src/json/json.cpp ../json/casablanca/src/json/json_parsing.cpp diff --git a/src/corehost/cli/fxr/framework_info.cpp b/src/corehost/cli/fxr/framework_info.cpp index 1afafc2250..c6d5cf9b83 100644 --- a/src/corehost/cli/fxr/framework_info.cpp +++ b/src/corehost/cli/fxr/framework_info.cpp @@ -28,14 +28,7 @@ bool compare_by_name_and_version(const framework_info &a, const framework_info & const pal::string_t& fx_name, std::vector* framework_infos) { - // No FX resolution for standalone apps - if (mode == host_mode_t::standalone) - { - trace::verbose(_X("Standalone mode detected. Not gathering shared FX locations")); - return; - } - - // No FX resolution for mixed apps + // No FX resolution for mixed apps if (mode == host_mode_t::split_fx) { trace::verbose(_X("Split/FX mode detected. Not gathering shared FX locations")); diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp index e10ed67291..99d1d77109 100644 --- a/src/corehost/cli/fxr/fx_muxer.cpp +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -10,6 +10,7 @@ #include "fx_definition.h" #include "fx_muxer.h" #include "fx_ver.h" +#include "host_startup_info.h" #include "libhost.h" #include "pal.h" #include "runtime_config.h" @@ -26,7 +27,7 @@ void handle_missing_framework_error( const pal::string_t& fx_name, const pal::string_t& fx_version, const pal::string_t& fx_dir, - const pal::string_t& own_dir) + const pal::string_t& dotnet_root) { std::vector framework_infos; pal::string_t fx_ver_dirs; @@ -37,10 +38,10 @@ void handle_missing_framework_error( } else { - fx_ver_dirs = own_dir; + fx_ver_dirs = dotnet_root; } - framework_info::get_all_framework_infos(mode, own_dir, fx_name, &framework_infos); + framework_info::get_all_framework_infos(mode, dotnet_root, fx_name, &framework_infos); // Display the error message about missing FX. if (fx_version.length()) @@ -239,13 +240,13 @@ bool resolve_hostpolicy_dir_from_probe_paths(const pal::string_t& version, const * Return name of deps file for app. */ pal::string_t get_deps_file( - bool portable, + bool is_framework_dependent, const pal::string_t& app_candidate, const pal::string_t& specified_deps_file, const fx_definition_vector_t& fx_definitions ) { - if (portable) + if (is_framework_dependent) { // The hostpolicy is resolved from the root framework's name and location. pal::string_t deps_file = get_root_framework(fx_definitions).get_dir(); @@ -258,7 +259,7 @@ pal::string_t get_deps_file( } else { - // Standalone app's hostpolicy is from specified deps or from app deps. + // Self-contained app's hostpolicy is from specified deps or from app deps. return !specified_deps_file.empty() ? specified_deps_file : get_deps_from_app_binary(app_candidate); } } @@ -268,7 +269,7 @@ pal::string_t get_deps_file( */ bool fx_muxer_t::resolve_hostpolicy_dir( host_mode_t mode, - const pal::string_t& own_dir, + const pal::string_t& dotnet_root, const fx_definition_vector_t& fx_definitions, const pal::string_t& app_candidate, const pal::string_t& specified_deps_file, @@ -276,10 +277,10 @@ bool fx_muxer_t::resolve_hostpolicy_dir( const std::vector& probe_realpaths, pal::string_t* impl_dir) { - bool portable = get_app(fx_definitions).get_runtime_config().get_portable(); + bool is_framework_dependent = get_app(fx_definitions).get_runtime_config().get_is_framework_dependent(); // Obtain deps file for the given configuration. - pal::string_t resolved_deps = get_deps_file(portable, app_candidate, specified_deps_file, fx_definitions); + pal::string_t resolved_deps = get_deps_file(is_framework_dependent, 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); @@ -297,7 +298,7 @@ bool fx_muxer_t::resolve_hostpolicy_dir( // Get the expected directory that would contain hostpolicy. pal::string_t expected; - if (portable) + if (is_framework_dependent) { // The hostpolicy is required to be in the root framework's location expected.assign(get_root_framework(fx_definitions).get_dir()); @@ -305,13 +306,13 @@ bool fx_muxer_t::resolve_hostpolicy_dir( } else { - // Standalone apps can be activated by muxer or by standalone host or "corehost" + // Native apps can be activated by muxer, native exe host or "corehost" // 1. When activated with dotnet.exe or corehost.exe, check for hostpolicy in the deps dir or // app dir. - // 2. When activated with app.exe, the standalone host, check own directory. - assert(mode == host_mode_t::muxer || mode == host_mode_t::standalone || mode == host_mode_t::split_fx); - expected = (mode == host_mode_t::standalone) - ? own_dir + // 2. When activated with native exe, the standalone host, check own directory. + assert(mode != host_mode_t::invalid); + expected = (mode == host_mode_t::apphost) + ? dotnet_root : get_directory(specified_deps_file.empty() ? app_candidate : specified_deps_file); } @@ -336,16 +337,16 @@ bool fx_muxer_t::resolve_hostpolicy_dir( // 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 && !portable) + if (mode == host_mode_t::muxer && !is_framework_dependent) { if (!pal::file_exists(get_app(fx_definitions).get_runtime_config().get_path())) { - trace::error(_X("Failed to run as a standalone app. If this should be a portable app, add the %s file specifying the appropriate framework."), + trace::error(_X("Failed to run as a self-contained app. If this should be a framework-dependent app, add the %s file specifying the appropriate framework."), get_app(fx_definitions).get_runtime_config().get_path().c_str()); } else if (get_app(fx_definitions).get_runtime_config().get_fx_name().empty()) { - trace::error(_X("Failed to run as a standalone app. If this should be a portable app, specify the appropriate framework in %s."), + trace::error(_X("Failed to run as a self-contained app. If this should be a framework-dependent app, specify the appropriate framework in %s."), get_app(fx_definitions).get_runtime_config().get_path().c_str()); } } @@ -463,17 +464,14 @@ fx_ver_t fx_muxer_t::resolve_framework_version(const std::vector& vers fx_definition_t* fx_muxer_t::resolve_fx( host_mode_t mode, const runtime_config_t& config, - const pal::string_t& own_dir, + const pal::string_t& dotnet_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 new fx_definition_t(config.get_fx_name(), own_dir, pal::string_t(), pal::string_t()); + return new fx_definition_t(config.get_fx_name(), dotnet_dir, pal::string_t(), pal::string_t()); } assert(!config.get_fx_name().empty()); @@ -500,17 +498,17 @@ fx_definition_t* fx_muxer_t::resolve_fx( std::vector global_dirs; bool multilevel_lookup = multilevel_lookup_enabled(); - // own_dir contains DIR_SEPARATOR appended that we need to remove. - pal::string_t own_dir_temp = own_dir; - remove_trailing_dir_seperator(&own_dir_temp); + // dotnet_dir contains DIR_SEPARATOR appended that we need to remove. + pal::string_t dotnet_dir_temp = dotnet_dir; + remove_trailing_dir_seperator(&dotnet_dir_temp); - hive_dir.push_back(own_dir_temp); + hive_dir.push_back(dotnet_dir_temp); if (multilevel_lookup && pal::get_global_dotnet_dirs(&global_dirs)) { for (pal::string_t dir : global_dirs) { - // Avoid duplicate of own_dir_temp - if (dir != own_dir_temp) + // Avoid duplicate of dotnet_dir_temp + if (dir != dotnet_dir_temp) { hive_dir.push_back(dir); } @@ -716,7 +714,7 @@ pal::string_t resolve_sdk_version(pal::string_t sdk_path, bool parse_only_produc return retval; } -bool fx_muxer_t::resolve_sdk_dotnet_path(const pal::string_t& own_dir, pal::string_t* cli_sdk) +bool fx_muxer_t::resolve_sdk_dotnet_path(const pal::string_t& dotnet_root, pal::string_t* cli_sdk) { trace::verbose(_X("--- Resolving dotnet from working dir")); pal::string_t cwd; @@ -726,7 +724,7 @@ bool fx_muxer_t::resolve_sdk_dotnet_path(const pal::string_t& own_dir, pal::stri assert(cwd.empty()); } - return resolve_sdk_dotnet_path(own_dir, cwd, cli_sdk); + return resolve_sdk_dotnet_path(dotnet_root, cwd, cli_sdk); } bool higher_sdk_version(const pal::string_t& new_version, pal::string_t* version, bool parse_only_production) @@ -747,7 +745,7 @@ bool higher_sdk_version(const pal::string_t& new_version, pal::string_t* version return retval; } -bool fx_muxer_t::resolve_sdk_dotnet_path(const pal::string_t& own_dir, const pal::string_t& cwd, pal::string_t* cli_sdk) +bool fx_muxer_t::resolve_sdk_dotnet_path(const pal::string_t& dotnet_root, const pal::string_t& cwd, pal::string_t* cli_sdk) { pal::string_t global; @@ -778,9 +776,9 @@ bool fx_muxer_t::resolve_sdk_dotnet_path(const pal::string_t& own_dir, const pal std::vector global_dirs; bool multilevel_lookup = multilevel_lookup_enabled(); - if (!own_dir.empty()) + if (!dotnet_root.empty()) { - hive_dir.push_back(own_dir); + hive_dir.push_back(dotnet_root); } if (multilevel_lookup && pal::get_global_dotnet_dirs(&global_dirs)) @@ -858,14 +856,14 @@ bool fx_muxer_t::resolve_sdk_dotnet_path(const pal::string_t& own_dir, const pal return false; } -bool is_sdk_dir_present(const pal::string_t& own_dir) +bool is_sdk_dir_present(const pal::string_t& dotnet_root) { - pal::string_t sdk_path = own_dir; + pal::string_t sdk_path = dotnet_root; append_path(&sdk_path, _X("sdk")); return pal::directory_exists(sdk_path); } -void muxer_info(pal::string_t own_dir) +void muxer_info(pal::string_t dotnet_root) { trace::println(); trace::println(_X("Host (useful for support):")); @@ -876,14 +874,14 @@ void muxer_info(pal::string_t own_dir) trace::println(); trace::println(_X(".NET Core SDKs installed:")); - if (!sdk_info::print_all_sdks(own_dir, _X(" "))) + if (!sdk_info::print_all_sdks(dotnet_root, _X(" "))) { trace::println(_X(" No SDKs were found.")); } trace::println(); trace::println(_X(".NET Core runtimes installed:")); - if (!framework_info::print_all_frameworks(own_dir, _X(" "))) + if (!framework_info::print_all_frameworks(dotnet_root, _X(" "))) { trace::println(_X(" No runtimes were found.")); } @@ -893,7 +891,7 @@ void muxer_info(pal::string_t own_dir) trace::println(_X(" %s"), DOTNET_CORE_DOWNLOAD_URL); } -void muxer_usage(bool is_sdk_present) +void fx_muxer_t::muxer_usage(bool is_sdk_present) { std::vector known_opts = fx_muxer_t::get_known_opts(true, host_mode_t::invalid, true); @@ -972,17 +970,18 @@ void append_probe_realpath(const pal::string_t& path, std::vector std::vector fx_muxer_t::get_known_opts(bool exec_mode, host_mode_t mode, bool get_all_options) { std::vector known_opts = { { _X("--additionalprobingpath"), _X(""), _X("Path containing probing policy and assemblies to probe for") } }; - if (get_all_options || exec_mode || mode == host_mode_t::split_fx || mode == host_mode_t::standalone) + if (get_all_options || exec_mode || mode == host_mode_t::split_fx || mode == host_mode_t::apphost) { - known_opts.push_back({ _X("--depsfile"), _X(""), _X("Path to .deps.json file") }); - known_opts.push_back({ _X("--runtimeconfig"), _X(""), _X("Path to .runtimeconfig.json file") }); + known_opts.push_back({ _X("--depsfile"), _X(""), _X("Path to .deps.json file")}); + known_opts.push_back({ _X("--runtimeconfig"), _X(""), _X("Path to .runtimeconfig.json file")}); } - if (get_all_options || mode == host_mode_t::muxer) + if (get_all_options || mode == host_mode_t::muxer || mode == host_mode_t::apphost) { - known_opts.push_back({ _X("--fx-version"), _X(""), _X("Version of the installed Shared Framework to use to run the application.") }); - known_opts.push_back({ _X("--roll-forward-on-no-candidate-fx"), _X(""), _X("Roll forward on no candidate shared framework is enabled") }); - known_opts.push_back({ _X("--additional-deps"), _X(""), _X("Path to additonal deps.json file") }); + // If mode=host_mode_t::apphost, these are only used when the app is framework-dependent. + known_opts.push_back({ _X("--fx-version"), _X(""), _X("Version of the installed Shared Framework to use to run the application.")}); + known_opts.push_back({ _X("--roll-forward-on-no-candidate-fx"), _X(""), _X("Roll forward on no candidate shared framework is enabled")}); + known_opts.push_back({ _X("--additional-deps"), _X(""), _X("Path to additonal deps.json file")}); } return known_opts; @@ -990,8 +989,7 @@ std::vector fx_muxer_t::get_known_opts(bool exec_mode, host_mode_t // Returns '0' on success, 'AppArgNotRunnable' if should be routed to CLI, otherwise error code. int fx_muxer_t::parse_args( - const pal::string_t& own_dir, - const pal::string_t& own_dll, + const host_startup_info_t& host_info, int argoff, int argc, const pal::char_t* argv[], @@ -1015,19 +1013,19 @@ int fx_muxer_t::parse_args( return InvalidArgFailure; } - app_candidate = own_dll; + app_candidate = host_info.app_path; *new_argoff = argoff + num_parsed; bool doesAppExist = false; - if (mode == host_mode_t::standalone) + if (mode == host_mode_t::apphost) { doesAppExist = pal::realpath(&app_candidate); } else { - trace::verbose(_X("Detected a non-standalone application, expecting app.dll to execute.")); + trace::verbose(_X("Using the provided arguments to determine the application to execute.")); if (*new_argoff >= argc) { - muxer_usage(!is_sdk_dir_present(own_dir)); + muxer_usage(!is_sdk_dir_present(host_info.dotnet_root)); return StatusCode::InvalidArgFailure; } @@ -1110,7 +1108,7 @@ int read_config( int fx_muxer_t::read_config_and_execute( const pal::string_t& host_command, - const pal::string_t& own_dir, + const host_startup_info_t& host_info, const pal::string_t& app_candidate, const opt_map_t& opts, int new_argc, @@ -1127,10 +1125,10 @@ int fx_muxer_t::read_config_and_execute( pal::string_t opts_additional_deps = _X("--additional-deps"); pal::string_t opts_runtime_config = _X("--runtimeconfig"); - pal::string_t fx_version_specified = 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 fx_version_specified; + pal::string_t roll_fwd_on_no_candidate_fx; pal::string_t deps_file = get_last_known_arg(opts, opts_deps_file, _X("")); - pal::string_t additional_deps = get_last_known_arg(opts, opts_additional_deps, _X("")); + pal::string_t additional_deps; pal::string_t runtime_config = get_last_known_arg(opts, opts_runtime_config, _X("")); std::vector spec_probe_paths = opts.count(opts_probe_path) ? opts.find(opts_probe_path)->second : std::vector(); @@ -1152,6 +1150,15 @@ int fx_muxer_t::read_config_and_execute( } auto app_config = app->get_runtime_config(); + bool is_framework_dependent = app_config.get_is_framework_dependent(); + + // These settings are only valid for framework-dependent apps + if (is_framework_dependent) + { + fx_version_specified = get_last_known_arg(opts, opts_fx_version, _X("")); + roll_fwd_on_no_candidate_fx = get_last_known_arg(opts, opts_roll_fwd_on_no_candidate_fx, _X("")); + additional_deps = get_last_known_arg(opts, opts_additional_deps, _X("")); + } // 'Roll forward on no candidate fx' is set to 1 (roll_fwd_on_no_candidate_fx_option::minor) by default. It can be changed through: // 1. Command line argument (--roll-forward-on-no-candidate-fx). @@ -1167,11 +1174,10 @@ int fx_muxer_t::read_config_and_execute( auto config = app_config; - // Determine additional deps pal::string_t additional_deps_serialized; - bool is_portable = config.get_portable(); - if (is_portable) + if (is_framework_dependent) { + // Determine additional deps additional_deps_serialized = additional_deps; if (additional_deps_serialized.empty()) { @@ -1183,11 +1189,11 @@ int fx_muxer_t::read_config_and_execute( 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); + fx_definition_t* fx = resolve_fx(mode, config, host_info.dotnet_root, version); if (fx == nullptr) { pal::string_t searched_version = fx_version_specified.empty() ? config.get_fx_version() : fx_version_specified; - handle_missing_framework_error(mode, config.get_fx_name(), searched_version, pal::string_t(), own_dir); + handle_missing_framework_error(mode, config.get_fx_name(), searched_version, pal::string_t(), host_info.dotnet_root); return FrameworkMissingFailure; } @@ -1231,15 +1237,15 @@ int fx_muxer_t::read_config_and_execute( } trace::verbose(_X("Executing as a %s app as per config file [%s]"), - (is_portable ? _X("portable") : _X("standalone")), config.get_path().c_str()); + (is_framework_dependent ? _X("framework-dependent") : _X("self-contained")), config.get_path().c_str()); pal::string_t impl_dir; - if (!resolve_hostpolicy_dir(mode, own_dir, fx_definitions, app_candidate, deps_file, fx_version_specified, probe_realpaths, &impl_dir)) + if (!resolve_hostpolicy_dir(mode, host_info.dotnet_root, fx_definitions, app_candidate, deps_file, fx_version_specified, probe_realpaths, &impl_dir)) { return CoreHostLibMissingFailure; } - corehost_init_t init(host_command, deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions); + corehost_init_t init(host_command, host_info, deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions); if (host_command.size() == 0) { @@ -1253,47 +1259,6 @@ int fx_muxer_t::read_config_and_execute( return rc; } -int fx_muxer_t::parse_path_args( - const int argc, - const pal::char_t* argv[], - pal::string_t& own_dir, - pal::string_t& own_dll, - pal::string_t& own_name) -{ - pal::string_t own_path; - - // Try to use argv[0] as own_path to allow for hosts located elsewhere - if (argc >= 1) - { - own_path = argv[0]; - if (!own_path.empty()) - { - trace::info(_X("Attempting to use argv[0] as path [%s]"), own_path.c_str()); - if (!get_path_from_argv(&own_path)) - { - trace::warning(_X("Failed to resolve argv[0] as path [%s]. Using location of current executable instead."), own_path.c_str()); - own_path.clear(); - } - } - } - - // Get the full name of the application - if (own_path.empty() && (!pal::get_own_executable_path(&own_path) || !pal::realpath(&own_path))) - { - trace::error(_X("Failed to resolve full path of the current executable [%s]"), own_path.c_str()); - return StatusCode::LibHostCurExeFindFailure; - } - - own_name = get_filename(own_path); - own_dir = get_directory(own_path); - pal::string_t own_dll_filename = get_executable(own_name) + _X(".dll"); - own_dll = own_dir; - append_path(&own_dll, own_dll_filename.c_str()); - - trace::info(_X("Own dll path '%s'"), own_dll.c_str()); - return 0; -} - /** * Main entrypoint to detect operating mode and perform corehost, muxer, * standalone application activation and the SDK activation. @@ -1302,38 +1267,30 @@ int fx_muxer_t::execute( const pal::string_t host_command, const int argc, const pal::char_t* argv[], + const host_startup_info_t& host_info, pal::char_t result_buffer[], int32_t buffer_size, int32_t* required_buffer_size) { - pal::string_t own_dir; - pal::string_t own_dll; - pal::string_t own_name; - - int result = parse_path_args(argc, argv, own_dir, own_dll, own_name); - if (result) - { - return result; - } - // Detect invocation mode - host_mode_t mode = detect_operating_mode(own_dir, own_dll, own_name); + host_mode_t mode = detect_operating_mode(host_info); int new_argoff; pal::string_t app_candidate; opt_map_t opts; + int result; if (mode == host_mode_t::split_fx) { // Invoked as corehost trace::verbose(_X("--- Executing in split/FX mode...")); - result = parse_args(own_dir, own_dll, 1, argc, argv, false, mode, &new_argoff, app_candidate, opts); + result = parse_args(host_info, 1, argc, argv, false, mode, &new_argoff, app_candidate, opts); } - else if (mode == host_mode_t::standalone) + else if (mode == host_mode_t::apphost) { // Invoked from the application base. - trace::verbose(_X("--- Executing in standalone mode...")); - result = parse_args(own_dir, own_dll, 1, argc, argv, false, mode, &new_argoff, app_candidate, opts); + trace::verbose(_X("--- Executing in a native executable mode...")); + result = parse_args(host_info, 1, argc, argv, false, mode, &new_argoff, app_candidate, opts); } else { @@ -1343,21 +1300,21 @@ int fx_muxer_t::execute( if (argc <= 1) { - muxer_usage(!is_sdk_dir_present(own_dir)); + muxer_usage(!is_sdk_dir_present(host_info.dotnet_root)); return StatusCode::InvalidArgFailure; } if (pal::strcasecmp(_X("exec"), argv[1]) == 0) { - result = parse_args(own_dir, own_dll, 2, argc, argv, true, mode, &new_argoff, app_candidate, opts); // arg offset 2 for dotnet, exec + result = parse_args(host_info, 2, argc, argv, true, mode, &new_argoff, app_candidate, opts); // arg offset 2 for dotnet, exec } else { - result = parse_args(own_dir, own_dll, 1, argc, argv, false, mode, &new_argoff, app_candidate, opts); // arg offset 1 for dotnet + result = parse_args(host_info, 1, argc, argv, false, mode, &new_argoff, app_candidate, opts); // arg offset 1 for dotnet if (result == AppArgNotRunnable) { - return handle_cli(own_dir, own_dll, argc, argv); + return handle_cli(host_info, argc, argv); } } } @@ -1365,14 +1322,25 @@ int fx_muxer_t::execute( if (!result) { // Transform dotnet [exec] [--additionalprobingpath path] [--depsfile file] [dll] [args] -> dotnet [dll] [args] - result = handle_exec_host_command(host_command, own_dir, app_candidate, opts, argc, argv, new_argoff, mode, result_buffer, buffer_size, required_buffer_size); + result = handle_exec_host_command( + host_command, + host_info, + app_candidate, + opts, + argc, + argv, + new_argoff, + mode, + result_buffer, + buffer_size, + required_buffer_size); } return result; } int fx_muxer_t::handle_exec( - const pal::string_t& own_dir, + const host_startup_info_t& host_info, const pal::string_t& app_candidate, const opt_map_t& opts, int argc, @@ -1380,12 +1348,23 @@ int fx_muxer_t::handle_exec( int argoff, host_mode_t mode) { - return handle_exec_host_command(pal::string_t(), own_dir, app_candidate, opts, argc, argv, argoff, mode, nullptr, 0, nullptr); + return handle_exec_host_command( + pal::string_t(), + host_info, + app_candidate, + opts, + argc, + argv, + argoff, + mode, + nullptr, + 0, + nullptr); } int fx_muxer_t::handle_exec_host_command( const pal::string_t& host_command, - const pal::string_t& own_dir, + const host_startup_info_t& host_info, const pal::string_t& app_candidate, const opt_map_t& opts, int argc, @@ -1409,25 +1388,36 @@ int fx_muxer_t::handle_exec_host_command( new_argc = vec_argv.size(); } + trace::info(_X("Using dotnet root path [%s]"), host_info.dotnet_root.c_str()); + // Transform dotnet [exec] [--additionalprobingpath path] [--depsfile file] [dll] [args] -> dotnet [dll] [args] - return read_config_and_execute(host_command, own_dir, app_candidate, opts, new_argc, new_argv, mode, result_buffer, buffer_size, required_buffer_size); + return read_config_and_execute( + host_command, + host_info, + app_candidate, + opts, + new_argc, + new_argv, + mode, + result_buffer, + buffer_size, + required_buffer_size); } int fx_muxer_t::handle_cli( - const pal::string_t& own_dir, - const pal::string_t& own_dll, + const host_startup_info_t& host_info, int argc, const pal::char_t* argv[]) { // Check for commands that don't depend on the CLI SDK to be loaded if (pal::strcasecmp(_X("--list-sdks"), argv[1]) == 0) { - sdk_info::print_all_sdks(own_dir, _X("")); + sdk_info::print_all_sdks(host_info.dotnet_root, _X("")); return StatusCode::Success; } else if (pal::strcasecmp(_X("--list-runtimes"), argv[1]) == 0) { - framework_info::print_all_frameworks(own_dir, _X("")); + framework_info::print_all_frameworks(host_info.dotnet_root, _X("")); return StatusCode::Success; } @@ -1436,7 +1426,7 @@ int fx_muxer_t::handle_cli( // pal::string_t sdk_dotnet; - if (!resolve_sdk_dotnet_path(own_dir, &sdk_dotnet)) + if (!resolve_sdk_dotnet_path(host_info.dotnet_root, &sdk_dotnet)) { assert(argc > 1); if (pal::strcasecmp(_X("-h"), argv[1]) == 0 || @@ -1447,7 +1437,7 @@ int fx_muxer_t::handle_cli( } else if (pal::strcasecmp(_X("--info"), argv[1]) == 0) { - muxer_info(own_dir); + muxer_info(host_info.dotnet_root); return StatusCode::Success; } @@ -1478,16 +1468,16 @@ int fx_muxer_t::handle_cli( pal::string_t app_candidate; opt_map_t opts; - int result = parse_args(own_dir, own_dll, 1, new_argv.size(), new_argv.data(), false, host_mode_t::muxer, &new_argoff, app_candidate, opts); // arg offset 1 for dotnet + int result = parse_args(host_info, 1, new_argv.size(), new_argv.data(), false, host_mode_t::muxer, &new_argoff, app_candidate, opts); // arg offset 1 for dotnet if (!result) { // Transform dotnet [exec] [--additionalprobingpath path] [--depsfile file] [dll] [args] -> dotnet [dll] [args] - result = handle_exec(own_dir, app_candidate, opts, new_argv.size(), new_argv.data(), new_argoff, host_mode_t::muxer); + result = handle_exec(host_info, app_candidate, opts, new_argv.size(), new_argv.data(), new_argoff, host_mode_t::muxer); } if (pal::strcasecmp(_X("--info"), argv[1]) == 0) { - muxer_info(own_dir); + muxer_info(host_info.dotnet_root); } return result; diff --git a/src/corehost/cli/fxr/fx_muxer.h b/src/corehost/cli/fxr/fx_muxer.h index 26114ba92a..cf59468c58 100644 --- a/src/corehost/cli/fxr/fx_muxer.h +++ b/src/corehost/cli/fxr/fx_muxer.h @@ -5,6 +5,7 @@ class corehost_init_t; class runtime_config_t; class fx_definition_t; struct fx_ver_t; +struct host_startup_info_t; #include "libhost.h" @@ -30,18 +31,14 @@ class fx_muxer_t const pal::string_t host_command, const int argc, const pal::char_t* argv[], + const host_startup_info_t& host_info, pal::char_t result_buffer[], int32_t buffer_size, int32_t* required_buffer_size); - static int parse_path_args( - const int argc, - const pal::char_t* argv[], - pal::string_t& own_dir, - pal::string_t& own_dll, - pal::string_t& own_name); + static bool resolve_sdk_dotnet_path(const pal::string_t& dotnet_root, const pal::string_t& cwd, pal::string_t* cli_sdk); +private: static int parse_args( - const pal::string_t& own_dir, - const pal::string_t& own_dll, + const host_startup_info_t& host_info, int argoff, int argc, const pal::char_t* argv[], @@ -51,7 +48,7 @@ class fx_muxer_t pal::string_t& app_candidate, opt_map_t& opts); static int handle_exec( - const pal::string_t& own_dir, + const host_startup_info_t& host_info, const pal::string_t& app_candidate, const opt_map_t& opts, int argc, @@ -60,7 +57,7 @@ class fx_muxer_t host_mode_t mode); static int handle_exec_host_command( const pal::string_t& host_command, - const pal::string_t& own_dir, + const host_startup_info_t& host_info, const pal::string_t& app_candidate, const opt_map_t& opts, int argc, @@ -71,16 +68,16 @@ class fx_muxer_t int32_t buffer_size, int32_t* required_buffer_size); static int handle_cli( - const pal::string_t& own_dir, - const pal::string_t& own_dll, + const host_startup_info_t& host_info, int argc, const pal::char_t* argv[]); - static std::vector get_known_opts(bool exec_mode, host_mode_t mode, bool get_all_options = false); - static bool resolve_sdk_dotnet_path(const pal::string_t& own_dir, const pal::string_t& cwd, pal::string_t* cli_sdk); -private: + static std::vector get_known_opts( + bool exec_mode, + host_mode_t mode, + bool get_all_options = false); static int read_config_and_execute( const pal::string_t& host_command, - const pal::string_t& own_dir, + const host_startup_info_t& host_info, const pal::string_t& app_candidate, const opt_map_t& opts, int new_argc, @@ -91,19 +88,25 @@ class fx_muxer_t int32_t* required_buffer_size); static bool resolve_hostpolicy_dir( host_mode_t mode, - const pal::string_t& own_dir, + const pal::string_t& dotnet_root, 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& probe_realpaths, pal::string_t* impl_dir); - static fx_ver_t resolve_framework_version(const std::vector& version_list, const pal::string_t& fx_ver, const fx_ver_t& specified, bool patch_roll_fwd, roll_fwd_on_no_candidate_fx_option roll_fwd_on_no_candidate_fx); + static fx_ver_t resolve_framework_version( + const std::vector& version_list, + const pal::string_t& fx_ver, + const fx_ver_t& specified, + bool patch_roll_fwd, + roll_fwd_on_no_candidate_fx_option roll_fwd_on_no_candidate_fx); static fx_definition_t* resolve_fx( host_mode_t mode, const runtime_config_t& config, - const pal::string_t& own_dir, + const pal::string_t& dotnet_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); + static bool resolve_sdk_dotnet_path(const pal::string_t& dotnet_root, pal::string_t* cli_sdk); + static void muxer_usage(bool is_sdk_present); }; diff --git a/src/corehost/cli/fxr/hostfxr.cpp b/src/corehost/cli/fxr/hostfxr.cpp index d37c0b6490..7b6fef840d 100644 --- a/src/corehost/cli/fxr/hostfxr.cpp +++ b/src/corehost/cli/fxr/hostfxr.cpp @@ -158,14 +158,29 @@ int execute_host_command( return code; } +SHARED_API int hostfxr_main_startupinfo(const int argc, const pal::char_t* argv[], const pal::char_t* host_path, const pal::char_t* dotnet_root, const pal::char_t* app_path) +{ + trace::setup(); + + trace::info(_X("--- Invoked hostfxr v2 [commit hash: %s] main"), _STRINGIFY(REPO_COMMIT_HASH)); + + host_startup_info_t startup_info(host_path, dotnet_root, app_path); + + fx_muxer_t muxer; + return muxer.execute(pal::string_t(), argc, argv, startup_info, nullptr, 0, nullptr); +} + SHARED_API int hostfxr_main(const int argc, const pal::char_t* argv[]) { trace::setup(); trace::info(_X("--- Invoked hostfxr [commit hash: %s] main"), _STRINGIFY(REPO_COMMIT_HASH)); + host_startup_info_t startup_info; + startup_info.parse(argc, argv); + fx_muxer_t muxer; - return muxer.execute(pal::string_t(), argc, argv, nullptr, 0, nullptr); + return muxer.execute(pal::string_t(), argc, argv, startup_info, nullptr, 0, nullptr); } // @@ -310,7 +325,10 @@ SHARED_API int32_t hostfxr_get_native_search_directories(const int argc, const p return InvalidArgFailure; } + host_startup_info_t startup_info; + startup_info.parse(argc, argv); + fx_muxer_t muxer; - int rc = muxer.execute(_X("get-native-search-directories"), argc, argv, buffer, buffer_size, required_buffer_size); + int rc = muxer.execute(_X("get-native-search-directories"), argc, argv, startup_info, buffer, buffer_size, required_buffer_size); return rc; } diff --git a/src/corehost/cli/host_startup_info.cpp b/src/corehost/cli/host_startup_info.cpp new file mode 100644 index 0000000000..9730b05a4b --- /dev/null +++ b/src/corehost/cli/host_startup_info.cpp @@ -0,0 +1,92 @@ +// 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 "error_codes.h" +#include "host_startup_info.h" +#include "pal.h" +#include "trace.h" +#include "utils.h" + +host_startup_info_t::host_startup_info_t( + const pal::char_t* host_path_value, + const pal::char_t* dotnet_root_value, + const pal::char_t* app_path_value) + : host_path(host_path_value) + , dotnet_root(dotnet_root_value) + , app_path(app_path_value) {} + +// Determine if string is a valid path, and if so then fix up by using realpath() +bool get_path_from_argv(pal::string_t *path) +{ + // Assume all paths will have at least one separator. We want to detect path vs. file before calling realpath + // because realpath will expand a filename into a full path containing the current directory which may be + // the wrong location when filename ends up being found in %PATH% and not the current directory. + if (path->find(DIR_SEPARATOR) != pal::string_t::npos) + { + return pal::realpath(path); + } + + return false; +} + +int host_startup_info_t::parse( + int argc, + const pal::char_t* argv[]) +{ + // Get host_path + get_host_path(argc, argv, &host_path); + + // Get dotnet_root + dotnet_root.assign(get_directory(host_path)); + + // Get app_path + app_path.assign(dotnet_root); + pal::string_t app_name = get_filename(strip_executable_ext(host_path)); + append_path(&app_path, app_name.c_str()); + app_path.append(_X(".dll")); + + trace::info(_X("Host path: [%s]"), host_path.c_str()); + trace::info(_X("Dotnet path: [%s]"), dotnet_root.c_str()); + trace::info(_X("App path: [%s]"), app_path.c_str()); + return 0; +} + +const bool host_startup_info_t::is_valid() const +{ + return ( + !host_path.empty() && + !dotnet_root.empty() && + !app_path.empty()); +} + +const pal::string_t host_startup_info_t::get_app_name() const +{ + return get_filename(strip_file_ext(app_path)); +} + +/*static*/ int host_startup_info_t::get_host_path(int argc, const pal::char_t* argv[], pal::string_t* host_path) +{ + // Attempt to get host_path from argv[0] as to allow for hosts located elsewhere + if (argc >= 1) + { + host_path->assign(argv[0]); + if (!host_path->empty()) + { + trace::info(_X("Attempting to use argv[0] as path [%s]"), host_path->c_str()); + if (!get_path_from_argv(host_path)) + { + trace::warning(_X("Failed to resolve argv[0] as path [%s]. Using location of current executable instead."), host_path->c_str()); + host_path->clear(); + } + } + } + + // If argv[0] did not work, get the executable name + if (host_path->empty() && (!pal::get_own_executable_path(host_path) || !pal::realpath(host_path))) + { + trace::error(_X("Failed to resolve full path of the current executable [%s]"), host_path->c_str()); + return StatusCode::LibHostCurExeFindFailure; + } + + return 0; +} diff --git a/src/corehost/cli/host_startup_info.h b/src/corehost/cli/host_startup_info.h new file mode 100644 index 0000000000..97bef5a5f8 --- /dev/null +++ b/src/corehost/cli/host_startup_info.h @@ -0,0 +1,32 @@ +// 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 __HOST_STARTUP_INFO_H_ +#define __HOST_STARTUP_INFO_H_ + +#include "pal.h" + +struct host_startup_info_t +{ + host_startup_info_t() {} + host_startup_info_t( + const pal::char_t* host_path_value, + const pal::char_t* dotnet_root_value, + const pal::char_t* app_path_value); + + int parse( + int argc, + const pal::char_t* argv[]); + + const bool is_valid() const; + + const pal::string_t get_app_name() const; + + static int get_host_path(int argc, const pal::char_t* argv[], pal::string_t* host_path); + + pal::string_t host_path; // The path to the current executable. + pal::string_t dotnet_root; // The path to the framework. + pal::string_t app_path; // For apphost, the path to the app dll; for muxer, not applicable as this information is not yet parsed. +}; + +#endif // __HOST_STARTUP_INFO_H_ diff --git a/src/corehost/cli/hostpolicy.cpp b/src/corehost/cli/hostpolicy.cpp index 3637196821..9ebba3ef36 100644 --- a/src/corehost/cli/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy.cpp @@ -12,6 +12,7 @@ #include "libhost.h" #include "error_codes.h" #include "breadcrumbs.h" +#include "host_startup_info.h" hostpolicy_init_t g_init; @@ -108,7 +109,7 @@ int run(const arguments_t& args, pal::string_t* out_host_command_result = nullpt // Note: these variables' lifetime should be longer than coreclr_initialize. std::vector tpa_paths_cstr, app_base_cstr, native_dirs_cstr, resources_dirs_cstr, fx_deps, deps, clrjit_path_cstr, probe_directories; pal::pal_clrstring(probe_paths.tpa, &tpa_paths_cstr); - pal::pal_clrstring(args.app_dir, &app_base_cstr); + pal::pal_clrstring(args.app_root, &app_base_cstr); pal::pal_clrstring(probe_paths.native, &native_dirs_cstr); pal::pal_clrstring(probe_paths.resources, &resources_dirs_cstr); @@ -228,14 +229,14 @@ int run(const arguments_t& args, pal::string_t* out_host_command_result = nullpt } } - std::vector own_path; - pal::pal_clrstring(args.own_path, &own_path); + std::vector host_path; + pal::pal_clrstring(args.host_path, &host_path); // Initialize CoreCLR coreclr::host_handle_t host_handle; coreclr::domain_id_t domain_id; auto hr = coreclr::initialize( - own_path.data(), + host_path.data(), "clrhost", property_keys.data(), property_values.data(), @@ -267,7 +268,7 @@ int run(const arguments_t& args, pal::string_t* out_host_command_result = nullpt arg_str.append(cur); arg_str.append(_X(",")); } - trace::info(_X("Launch host: %s, app: %s, argc: %d, args: %s"), args.own_path.c_str(), + trace::info(_X("Launch host: %s, app: %s, argc: %d, args: %s"), args.host_path.c_str(), args.managed_application.c_str(), args.app_argc, arg_str.c_str()); } @@ -358,6 +359,12 @@ int corehost_main_init(const int argc, const pal::char_t* argv[], const pal::str } // Take care of arguments + if (!g_init.host_info.is_valid()) + { + // For backwards compat (older hostfxr), default the host_info + g_init.host_info.parse(argc, argv); + } + if (!parse_arguments(g_init, argc, argv, &args)) { return StatusCode::LibHostInvalidArgs; diff --git a/src/corehost/cli/libhost.cpp b/src/corehost/cli/libhost.cpp index 22e8b0ca00..1fe63e6738 100644 --- a/src/corehost/cli/libhost.cpp +++ b/src/corehost/cli/libhost.cpp @@ -5,6 +5,7 @@ #include "utils.h" #include "trace.h" #include "libhost.h" +#include "host_startup_info.h" void get_runtime_config_paths_from_app(const pal::string_t& app, pal::string_t* cfg, pal::string_t* dev_cfg) { @@ -48,20 +49,20 @@ void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& na 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) +host_mode_t detect_operating_mode(const host_startup_info_t& host_info) { - if (coreclr_exists_in_dir(own_dir) || pal::file_exists(own_dll)) + if (coreclr_exists_in_dir(host_info.dotnet_root) || pal::file_exists(host_info.app_path)) { - pal::string_t own_deps_json = own_dir; - pal::string_t own_deps_filename = strip_file_ext(own_name) + _X(".deps.json"); - pal::string_t own_config_filename = strip_file_ext(own_name) + _X(".runtimeconfig.json"); + pal::string_t own_deps_json = host_info.dotnet_root; + pal::string_t own_deps_filename = host_info.get_app_name() + _X(".deps.json"); + pal::string_t own_config_filename = host_info.get_app_name() + _X(".runtimeconfig.json"); append_path(&own_deps_json, own_deps_filename.c_str()); if (trace::is_enabled()) { - trace::info(_X("Detecting mode... CoreCLR present in own dir [%s] and checking if [%s] file present=[%d]"), - own_dir.c_str(), own_deps_filename.c_str(), pal::file_exists(own_deps_json)); + trace::info(_X("Detecting mode... CoreCLR present in dotnet root [%s] and checking if [%s] file present=[%d]"), + host_info.dotnet_root.c_str(), own_deps_filename.c_str(), pal::file_exists(own_deps_json)); } - return ((pal::file_exists(own_deps_json) || !pal::file_exists(own_config_filename)) && pal::file_exists(own_dll)) ? host_mode_t::standalone : host_mode_t::split_fx; + return ((pal::file_exists(own_deps_json) || !pal::file_exists(own_config_filename)) && pal::file_exists(host_info.app_path)) ? host_mode_t::apphost : host_mode_t::split_fx; } else { diff --git a/src/corehost/cli/libhost.h b/src/corehost/cli/libhost.h index 5be16db5ac..568dc7eae4 100644 --- a/src/corehost/cli/libhost.h +++ b/src/corehost/cli/libhost.h @@ -5,6 +5,7 @@ #define __LIBHOST_H__ #include #include "trace.h" +#include "host_startup_info.h" #include "runtime_config.h" #include "fx_definition.h" #include "fx_ver.h" @@ -12,14 +13,14 @@ enum host_mode_t { invalid = 0, - + muxer, // Invoked as "dotnet.exe". - - standalone, // Invoked as "appname.exe" from the application base: either "standalone" or "branded". When implementing branded exes, rename this to "apphost" - split_fx // Invoked as "corehost.exe" for xunit scenarios -- this has to be fixed by the CLI to not use this executable and this mode should not be supported. - // Split FX means, the host is operating like "corerun.exe" in a split location from the application base (CORE_ROOT equivalent), but it has its "hostfxr.dll" - // next to it. + apphost, // Invoked as .exe from the application base; this is the renamed "apphost.exe". + + split_fx // Invoked as "corehost.exe" for xunit scenarios. Supported for backwards compat for 1.x apps. + // Split FX means, the host is operating like "corerun.exe" in a split location from the application base (CORE_ROOT equivalent), + // but it has its "hostfxr.dll" next to it. }; class fx_ver_t; @@ -44,7 +45,7 @@ struct host_interface_t const pal::char_t* fx_dir; const pal::char_t* fx_name; const pal::char_t* deps_file; - size_t is_portable; + size_t is_framework_dependent; strarr_t probe_paths; size_t patch_roll_forward; size_t prerelease_roll_forward; @@ -57,6 +58,9 @@ struct host_interface_t strarr_t fx_requested_versions; strarr_t fx_found_versions; const pal::char_t* host_command; + const pal::char_t* host_info_host_path; + const pal::char_t* host_info_dotnet_root; + const pal::char_t* host_info_app_path; // !! 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) @@ -74,7 +78,7 @@ static_assert(offsetof(host_interface_t, config_values) == 4 * sizeof(size_t), " static_assert(offsetof(host_interface_t, fx_dir) == 6 * sizeof(size_t), "Struct offset breaks backwards compatibility"); static_assert(offsetof(host_interface_t, fx_name) == 7 * sizeof(size_t), "Struct offset breaks backwards compatibility"); static_assert(offsetof(host_interface_t, deps_file) == 8 * sizeof(size_t), "Struct offset breaks backwards compatibility"); -static_assert(offsetof(host_interface_t, is_portable) == 9 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +static_assert(offsetof(host_interface_t, is_framework_dependent) == 9 * sizeof(size_t), "Struct offset breaks backwards compatibility"); static_assert(offsetof(host_interface_t, probe_paths) == 10 * sizeof(size_t), "Struct offset breaks backwards compatibility"); static_assert(offsetof(host_interface_t, patch_roll_forward) == 12 * sizeof(size_t), "Struct offset breaks backwards compatibility"); static_assert(offsetof(host_interface_t, prerelease_roll_forward) == 13 * sizeof(size_t), "Struct offset breaks backwards compatibility"); @@ -87,7 +91,10 @@ static_assert(offsetof(host_interface_t, fx_dirs) == 20 * sizeof(size_t), "Struc 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(offsetof(host_interface_t, host_command) == 26 * sizeof(size_t), "Struct offset breaks backwards compatibility"); -static_assert(sizeof(host_interface_t) == 27 * sizeof(size_t), "Did you add static asserts for the newly added fields?"); +static_assert(offsetof(host_interface_t, host_info_host_path) == 27 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +static_assert(offsetof(host_interface_t, host_info_dotnet_root) == 28 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +static_assert(offsetof(host_interface_t, host_info_app_path) == 29 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +static_assert(sizeof(host_interface_t) == 30 * 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) @@ -102,7 +109,7 @@ class corehost_init_t const pal::string_t m_tfm; const pal::string_t m_deps_file; const pal::string_t m_additional_deps_serialized; - bool m_portable; + bool m_is_framework_dependent; std::vector m_probe_paths; std::vector m_probe_paths_cstr; bool m_patch_roll_forward; @@ -118,18 +125,25 @@ class corehost_init_t std::vector m_fx_found_versions; std::vector m_fx_found_versions_cstr; const pal::string_t m_host_command; + const pal::string_t m_host_info_host_path; + const pal::string_t m_host_info_dotnet_root; + const pal::string_t m_host_info_app_path; public: corehost_init_t( const pal::string_t& host_command, + const host_startup_info_t& host_info, const pal::string_t& deps_file, const pal::string_t& additional_deps_serialized, const std::vector& probe_paths, const host_mode_t mode, const fx_definition_vector_t& fx_definitions) : m_host_command(host_command) + , m_host_info_host_path(host_info.host_path) + , m_host_info_dotnet_root(host_info.dotnet_root) + , m_host_info_app_path(host_info.app_path) , m_deps_file(deps_file) , m_additional_deps_serialized(additional_deps_serialized) - , m_portable(get_app(fx_definitions).get_runtime_config().get_portable()) + , m_is_framework_dependent(get_app(fx_definitions).get_runtime_config().get_is_framework_dependent()) , m_probe_paths(probe_paths) , m_host_mode(mode) , m_host_interface() @@ -202,7 +216,7 @@ class corehost_init_t hi.deps_file = m_deps_file.c_str(); hi.additional_deps_serialized = m_additional_deps_serialized.c_str(); - hi.is_portable = m_portable; + hi.is_framework_dependent = m_is_framework_dependent; hi.probe_paths.len = m_probe_paths_cstr.size(); hi.probe_paths.arr = m_probe_paths_cstr.data(); @@ -227,6 +241,10 @@ class corehost_init_t hi.host_command = m_host_command.c_str(); + hi.host_info_host_path = m_host_info_host_path.c_str(); + hi.host_info_dotnet_root = m_host_info_dotnet_root.c_str(); + hi.host_info_app_path = m_host_info_app_path.c_str(); + return hi; } @@ -254,8 +272,9 @@ struct hostpolicy_init_t host_mode_t host_mode; bool patch_roll_forward; bool prerelease_roll_forward; - bool is_portable; + bool is_framework_dependent; pal::string_t host_command; + host_startup_info_t host_info; static bool init(host_interface_t* input, hostpolicy_init_t* init) { @@ -279,7 +298,7 @@ struct hostpolicy_init_t make_clrstr_arr(input->config_values.len, input->config_values.arr, &init->cfg_values); init->deps_file = input->deps_file; - init->is_portable = input->is_portable; + init->is_framework_dependent = input->is_framework_dependent; make_palstr_arr(input->probe_paths.len, input->probe_paths.arr, &init->probe_paths); @@ -341,7 +360,7 @@ struct hostpolicy_init_t auto fx = new fx_definition_t(); init->fx_definitions.push_back(std::unique_ptr(fx)); - if (init->is_portable) + if (init->is_framework_dependent) { pal::string_t fx_dir = input->fx_dir; pal::string_t fx_name = input->fx_name; @@ -364,6 +383,14 @@ struct hostpolicy_init_t init->host_command = input->host_command; } + if (input->version_lo >= offsetof(host_interface_t, host_info_host_path) + sizeof(input->host_info_host_path)) + { + init->host_info.host_path = input->host_info_host_path; + init->host_info.dotnet_root = input->host_info_dotnet_root; + init->host_info.app_path = input->host_info_app_path; + // For the backwards compat case, this will be later initialized with argv[0] + } + return true; } @@ -391,7 +418,7 @@ void get_runtime_config_paths_from_app(const pal::string_t& file, pal::string_t* 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); +host_mode_t detect_operating_mode(const host_startup_info_t& host_info); bool hostpolicy_exists_in_svc(pal::string_t* resolved_dir); void try_patch_roll_forward_in_dir(const pal::string_t& cur_dir, const fx_ver_t& start_ver, pal::string_t* max_str); diff --git a/src/corehost/cli/runtime_config.cpp b/src/corehost/cli/runtime_config.cpp index 020b4e23e2..48198cfc3f 100644 --- a/src/corehost/cli/runtime_config.cpp +++ b/src/corehost/cli/runtime_config.cpp @@ -20,7 +20,7 @@ runtime_config_t::runtime_config_t() : m_patch_roll_fwd(true) , m_roll_fwd_on_no_candidate_fx(roll_fwd_on_no_candidate_fx_option::minor) - , m_portable(false) + , m_is_framework_dependent(false) , m_valid(false) { } @@ -134,7 +134,7 @@ bool runtime_config_t::parse_opts(const json_value& opts) return true; } - m_portable = true; + m_is_framework_dependent = true; const auto& fx_obj = framework->second.as_object(); @@ -383,9 +383,9 @@ void runtime_config_t::force_roll_fwd_on_no_candidate_fx(roll_fwd_on_no_candidat m_fx_readonly.set_roll_fwd_on_no_candidate_fx(value); } -bool runtime_config_t::get_portable() const +bool runtime_config_t::get_is_framework_dependent() const { - return m_portable; + return m_is_framework_dependent; } const std::list& runtime_config_t::get_probe_paths() const diff --git a/src/corehost/cli/runtime_config.h b/src/corehost/cli/runtime_config.h index 3204717efe..a1753e78e0 100644 --- a/src/corehost/cli/runtime_config.h +++ b/src/corehost/cli/runtime_config.h @@ -88,7 +88,7 @@ class runtime_config_t bool get_patch_roll_fwd() const; roll_fwd_on_no_candidate_fx_option get_roll_fwd_on_no_candidate_fx() const; void force_roll_fwd_on_no_candidate_fx(roll_fwd_on_no_candidate_fx_option value); - bool get_portable() const; + bool get_is_framework_dependent() const; bool parse_opts(const json_value& opts); void combine_properties(std::unordered_map& combined_properties) const; @@ -115,7 +115,7 @@ class runtime_config_t pal::string_t m_dev_path; pal::string_t m_path; - bool m_portable; + bool m_is_framework_dependent; bool m_valid; private: diff --git a/src/corehost/common/pal.h b/src/corehost/common/pal.h index 7cdb2ff6bc..9b5980e99f 100644 --- a/src/corehost/common/pal.h +++ b/src/corehost/common/pal.h @@ -212,9 +212,12 @@ namespace pal bool get_own_executable_path(string_t* recv); bool getenv(const char_t* name, string_t* recv); bool get_default_servicing_directory(string_t* recv); + //On Linux, there are no global locations //On Windows there will be only one global location bool get_global_dotnet_dirs(std::vector* recv); + + bool get_default_installation_dir(pal::string_t* recv); bool get_default_breadcrumb_store(string_t* recv); bool is_path_rooted(const string_t& path); @@ -223,6 +226,8 @@ namespace pal bool load_library(const string_t* path, dll_t* dll); proc_t get_symbol(dll_t library, const char* name); void unload_library(dll_t library); + + bool is_running_in_wow64(); } #endif // PAL_H diff --git a/src/corehost/common/pal.unix.cpp b/src/corehost/common/pal.unix.cpp index e8ca81552e..34a36dfb8b 100644 --- a/src/corehost/common/pal.unix.cpp +++ b/src/corehost/common/pal.unix.cpp @@ -188,6 +188,16 @@ bool is_executable(const pal::string_t& file_path) return false; } + bool pal::get_default_installation_dir(pal::string_t* recv) +{ +#if defined(__APPLE__) + recv->assign(_X("/usr/local/share/dotnet")); +#else + recv->assign(_X("/usr/share/dotnet")); +#endif + return true; +} + pal::string_t trim_quotes(pal::string_t stringToCleanup) { pal::char_t quote_array[2] = {'\"', '\''}; @@ -619,3 +629,8 @@ void pal::readdir_onlydirectories(const pal::string_t& path, std::vectorclear(); - pal::string_t file_path; - if (!(pal::getenv(env_key, &file_path) && pal::realpath(&file_path))) - { - // We should have the path in file_path. - trace::verbose(_X("Failed to obtain [%s] directory,[%s]"),env_key, file_path.c_str()); - return false; - } - - recv->assign(file_path); - return true; -} - static bool get_wow_mode_program_files(pal::string_t* recv) { @@ -198,16 +182,36 @@ bool pal::get_default_servicing_directory(string_t* recv) bool pal::get_global_dotnet_dirs(std::vector* dirs) { pal::string_t dir; - if (!get_file_path_from_env(_X("ProgramFiles"), &dir)) + if (!get_default_installation_dir(&dir)) { return false; } - append_path(&dir, _X("dotnet")); dirs->push_back(dir); return true; } +bool pal::get_default_installation_dir(pal::string_t* recv) +{ + pal::char_t* program_files_dir; + if (pal::is_running_in_wow64()) + { + program_files_dir = _X("ProgramFiles(x86)"); + } + else + { + program_files_dir = _X("ProgramFiles"); + } + + if (!get_file_path_from_env(program_files_dir, recv)) + { + return false; + } + + append_path(recv, _X("dotnet")); + return true; +} + // To determine the OS version, we are going to use RtlGetVersion API // since GetVersion call can be shimmed on Win8.1+. typedef NTSTATUS (WINAPI *pFuncRtlGetVersion)(RTL_OSVERSIONINFOW *); @@ -493,3 +497,13 @@ void pal::readdir_onlydirectories(const pal::string_t& path, std::vector dot_pos) { - return path; + return path; } return path.substr(0, dot_pos); } @@ -338,15 +338,18 @@ bool multilevel_lookup_enabled() return multilevel_lookup; } -// Determine if string is a valid path, and if so then fix up by using realpath() -bool get_path_from_argv(pal::string_t *path) +bool get_file_path_from_env(const pal::char_t* env_key, pal::string_t* recv) { - // Assume all paths will have at least one separator. We want to detect path vs. file before calling realpath - // because realpath will expand a filename into a full path containing the current directory which may be - // the wrong location when filename is ends up being found in %PATH% and not the current directory. - if (path->find(DIR_SEPARATOR) != pal::string_t::npos) + recv->clear(); + pal::string_t file_path; + if (pal::getenv(env_key, &file_path)) { - return pal::realpath(path); + if (pal::realpath(&file_path)) + { + recv->assign(file_path); + return true; + } + trace::verbose(_X("Did not find [%s] directory [%s]"), env_key, file_path.c_str()); } return false; @@ -370,3 +373,13 @@ bool try_stou(const pal::string_t& str, unsigned* num) *num = (unsigned)std::stoul(str); return true; } + +pal::string_t get_dotnet_root_env_var_name() +{ + if (pal::is_running_in_wow64()) + { + return pal::string_t(_X("DOTNET_ROOT(x86)")); + } + + return pal::string_t(_X("DOTNET_ROOT")); +} diff --git a/src/corehost/common/utils.h b/src/corehost/common/utils.h index a09a51e00f..dd01bf86ed 100644 --- a/src/corehost/common/utils.h +++ b/src/corehost/common/utils.h @@ -10,6 +10,7 @@ struct host_option pal::string_t option; pal::string_t argument; pal::string_t description; + bool framework_dependent; }; #define _STRINGIFY(s) _X(s) @@ -23,7 +24,7 @@ typedef std::unordered_map> opt_map_t; bool ends_with(const pal::string_t& value, const pal::string_t& suffix, bool match_case); bool starts_with(const pal::string_t& value, const pal::string_t& prefix, bool match_case); -pal::string_t get_executable(const pal::string_t& filename); +pal::string_t strip_executable_ext(const pal::string_t& filename); pal::string_t get_directory(const pal::string_t& path); pal::string_t strip_file_ext(const pal::string_t& path); pal::string_t get_filename(const pal::string_t& path); @@ -49,7 +50,8 @@ bool skip_utf8_bom(pal::ifstream_t* stream); bool get_env_shared_store_dirs(std::vector* dirs, const pal::string_t& arch, const pal::string_t& tfm); bool get_global_shared_store_dirs(std::vector* dirs, const pal::string_t& arch, const pal::string_t& tfm); bool multilevel_lookup_enabled(); -bool get_path_from_argv(pal::string_t *path); +bool get_file_path_from_env(const pal::char_t* env_key, pal::string_t* recv); size_t index_of_non_numeric(const pal::string_t& str, unsigned i); bool try_stou(const pal::string_t& str, unsigned* num); +pal::string_t get_dotnet_root_env_var_name(); #endif diff --git a/src/corehost/corehost.cpp b/src/corehost/corehost.cpp index 457c4479a9..8f287a26d9 100644 --- a/src/corehost/corehost.cpp +++ b/src/corehost/corehost.cpp @@ -1,13 +1,14 @@ // 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 "error_codes.h" +#include "fx_ver.h" +#include "pal.h" #include "trace.h" #include "utils.h" -#include "pal.h" -#include "fx_ver.h" -#include "error_codes.h" #if FEATURE_APPHOST +#include "startup_config.h" #define CURHOST_TYPE _X("apphost") #define CUREXE_PKG_VER APPHOST_PKG_VER #else // !FEATURE_APPHOST @@ -16,6 +17,7 @@ #endif // !FEATURE_APPHOST typedef int(*hostfxr_main_fn) (const int argc, const pal::char_t* argv[]); +typedef int(*hostfxr_main_startupinfo_fn) (const int argc, const pal::char_t* argv[], const pal::char_t* host_path, const pal::char_t* dotnet_root, const pal::char_t* app_path); #if FEATURE_APPHOST @@ -35,7 +37,7 @@ typedef int(*hostfxr_main_fn) (const int argc, const pal::char_t* argv[]); #define EMBED_HASH_HI_PART_UTF8 "c3ab8ff13720e8ad9047dd39466b3c89" // SHA-256 of "foobar" in UTF-8 #define EMBED_HASH_LO_PART_UTF8 "74e592c2fa383d4a3960714caef0c4f2" #define EMBED_HASH_FULL_UTF8 (EMBED_HASH_HI_PART_UTF8 EMBED_HASH_LO_PART_UTF8) // NUL terminated -bool is_exe_enabled_for_execution(const pal::string_t& own_path) +bool is_exe_enabled_for_execution(const pal::string_t& host_path, pal::string_t* app_dll) { constexpr int EMBED_SZ = sizeof(EMBED_HASH_FULL_UTF8) / sizeof(EMBED_HASH_FULL_UTF8[0]); constexpr int EMBED_MAX = (EMBED_SZ > 1025 ? EMBED_SZ : 1025); // 1024 DLL name length, 1 NUL @@ -49,8 +51,7 @@ bool is_exe_enabled_for_execution(const pal::string_t& own_path) static const char lo_part[] = EMBED_HASH_LO_PART_UTF8; std::string binding(&embed[0]); - pal::string_t pal_binding; - if (!pal::utf8_palstring(binding, &pal_binding)) + if (!pal::utf8_palstring(binding, app_dll)) { trace::error(_X("The managed DLL bound to this executable could not be retrieved from the executable image.")); return false; @@ -65,44 +66,115 @@ bool is_exe_enabled_for_execution(const pal::string_t& own_path) binding.compare(0, hi_len, &hi_part[0]) == 0 && binding.compare(hi_len, lo_len, &lo_part[0]) == 0) { - trace::error(_X("This executable is not bound to a managed DLL to execute. The binding value is: '%s'"), pal_binding.c_str()); + trace::error(_X("This executable is not bound to a managed DLL to execute. The binding value is: '%s'"), app_dll->c_str()); return false; } - pal::string_t own_name = get_filename(own_path); - pal::string_t own_dll_filename = get_executable(own_name) + _X(".dll"); + trace::info(_X("The managed DLL bound to this executable is: '%s'"), app_dll->c_str()); + return true; +} - if (pal::strcasecmp(own_dll_filename.c_str(), pal_binding.c_str()) != 0) +bool resolve_app_root(const pal::string_t& host_path, pal::string_t* out_app_root, bool* requires_v2_hostfxr_interface) +{ + // For self-contained, the startupconfig.json specifies app_root which is used later to assign dotnet_root + // For framework-dependent, the startupconfig.json specifies app_root which does not affect dotnet_root + pal::string_t config_path = strip_executable_ext(host_path); + config_path += _X(".startupconfig.json"); + startup_config_t startup_config; + startup_config.parse(config_path); + if (!startup_config.is_valid()) { - trace::error(_X("The managed DLL bound to this executable: '%s', did not match own name '%s'."), pal_binding.c_str(), own_dll_filename.c_str()); return false; } - trace::info(_X("The managed DLL bound to this executable is: '%s'"), pal_binding.c_str()); + if (startup_config.get_app_root().empty()) + { + out_app_root->assign(get_directory(host_path)); + } + else + { + *requires_v2_hostfxr_interface = true; + if (pal::is_path_rooted(startup_config.get_app_root())) + { + out_app_root->assign(startup_config.get_app_root()); + } + else + { + out_app_root->assign(get_directory(host_path)); + append_path(out_app_root, startup_config.get_app_root().c_str()); + } + if (!pal::realpath(out_app_root)) + { + trace::error(_X("The app root [%s] specified in [%s] does not exist."), + out_app_root->c_str(), config_path.c_str()); + return false; + } + } + return true; } -#endif // FEATURE_APPHOST +#endif -pal::string_t resolve_fxr_path(const pal::string_t& own_dir) +bool resolve_fxr_path(const pal::string_t& host_path, const pal::string_t& app_root, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path) { + pal::string_t host_dir; + host_dir.assign(get_directory(host_path)); + #if FEATURE_APPHOST - pal::string_t fxr_path; - if (library_exists_in_dir(own_dir, LIBFXR_NAME, &fxr_path)) + // If a hostfxr exists in app_root, then assumed self-contained. + if (library_exists_in_dir(app_root, LIBFXR_NAME, out_fxr_path)) { - trace::info(_X("Resolved fxr [%s]..."), fxr_path.c_str()); - return fxr_path; + trace::info(_X("Resolved fxr [%s]..."), out_fxr_path->c_str()); + out_dotnet_root->assign(app_root); + return true; } - trace::error(_X("A fatal error occurred, the required library %s could not be found at [%s]"), LIBFXR_NAME, own_dir.c_str()); - return pal::string_t(); + // For framework-dependent apps, use DOTNET_ROOT + + pal::string_t default_install_location; + pal::string_t dotnet_root_env_var_name = get_dotnet_root_env_var_name(); + if (get_file_path_from_env(dotnet_root_env_var_name.c_str(), out_dotnet_root)) + { + trace::info(_X("Using environment variable %s=[%s] as runtime location."), dotnet_root_env_var_name.c_str(), out_dotnet_root->c_str()); + } + else + { + // Check default installation root as fallback + if (!pal::get_default_installation_dir(&default_install_location)) + { + trace::error(_X("A fatal error occurred, the default install location cannot be obtained.")); + return false; + } + trace::info(_X("Using default installation location [%s] as runtime location."), default_install_location.c_str()); + out_dotnet_root->assign(default_install_location); + } + + pal::string_t fxr_dir = *out_dotnet_root; #else - pal::string_t fxr_dir = own_dir; + out_dotnet_root->assign(host_dir); + pal::string_t fxr_dir = host_dir; +#endif append_path(&fxr_dir, _X("host")); append_path(&fxr_dir, _X("fxr")); if (!pal::directory_exists(fxr_dir)) { - trace::error(_X("A fatal error occurred, the folder [%s] does not exist"), fxr_dir.c_str()); - return pal::string_t(); +#if FEATURE_APPHOST + if (default_install_location.empty()) + { + pal::get_default_installation_dir(&default_install_location); + } + + trace::error(_X("A fatal error occurred, the required library %s could not be found.\n" + "If this is a self-contained application, that library should exist in [%s].\n" + "If this is a framework-dependent application, install the runtime in the default location [%s] or use the %s environment variable to specify the runtime location."), + LIBFXR_NAME, + app_root.c_str(), + default_install_location.c_str(), + dotnet_root_env_var_name.c_str()); +#else + trace::error(_X("A fatal error occurred, the folder [%s] does not exist"), fxr_dir.c_str()); +#endif + return false; } trace::info(_X("Reading fx resolver directory=[%s]"), fxr_dir.c_str()); @@ -127,43 +199,54 @@ pal::string_t resolve_fxr_path(const pal::string_t& own_dir) if (max_ver == fx_ver_t(-1, -1, -1)) { trace::error(_X("A fatal error occurred, the folder [%s] does not contain any version-numbered child folders"), fxr_dir.c_str()); - return pal::string_t(); + return false; } pal::string_t max_ver_str = max_ver.as_str(); append_path(&fxr_dir, max_ver_str.c_str()); trace::info(_X("Detected latest fxr version=[%s]..."), fxr_dir.c_str()); - pal::string_t ret_path; - if (library_exists_in_dir(fxr_dir, LIBFXR_NAME, &ret_path)) + if (library_exists_in_dir(fxr_dir, LIBFXR_NAME, out_fxr_path)) { - trace::info(_X("Resolved fxr [%s]..."), ret_path.c_str()); - return ret_path; + trace::info(_X("Resolved fxr [%s]..."), out_fxr_path ->c_str()); + return true; } trace::error(_X("A fatal error occurred, the required library %s could not be found in [%s]"), LIBFXR_NAME, fxr_dir.c_str()); - return pal::string_t(); -#endif + return false; } int run(const int argc, const pal::char_t* argv[]) { - pal::string_t own_path; - if (!pal::get_own_executable_path(&own_path) || !pal::realpath(&own_path)) + pal::string_t host_path; + if (!pal::get_own_executable_path(&host_path) || !pal::realpath(&host_path)) { - trace::error(_X("Failed to resolve full path of the current executable [%s]"), own_path.c_str()); + trace::error(_X("Failed to resolve full path of the current executable [%s]"), host_path.c_str()); return StatusCode::CoreHostCurExeFindFailure; } -#ifdef FEATURE_APPHOST - if (!is_exe_enabled_for_execution(own_path)) + pal::string_t app_root; + pal::string_t app_path; + bool requires_v2_hostfxr_interface = false; + +#if FEATURE_APPHOST + pal::string_t app_dll_name; + if (!is_exe_enabled_for_execution(host_path, &app_dll_name)) { trace::error(_X("A fatal error was encountered. This executable was not bound to load a managed DLL.")); return StatusCode::AppHostExeNotBoundFailure; } + + if (!resolve_app_root(host_path, &app_root, &requires_v2_hostfxr_interface)) + { + return StatusCode::LibHostAppRootFindFailure; + } + + app_path.assign(app_root); + append_path(&app_path, app_dll_name.c_str()); #else - pal::string_t own_name = get_executable(get_filename(own_path)); + pal::string_t own_name = strip_executable_ext(get_filename(host_path)); if (pal::strcasecmp(own_name.c_str(), CURHOST_TYPE) != 0) { @@ -187,19 +270,22 @@ int run(const int argc, const pal::char_t* argv[]) trace::println(_X(" The path to an application .dll file to execute.")); return StatusCode::InvalidArgFailure; } -#endif - pal::dll_t fxr; - - pal::string_t own_dir = get_directory(own_path); + app_root.assign(host_path); + app_path.assign(app_root); + append_path(&app_path, own_name.c_str()); + app_path.append(_X(".dll")); +#endif - // Load library - pal::string_t fxr_path = resolve_fxr_path(own_dir); - if (fxr_path.empty()) + pal::string_t dotnet_root; + pal::string_t fxr_path; + if (!resolve_fxr_path(host_path, app_root, &dotnet_root, &fxr_path)) { return StatusCode::CoreHostLibMissingFailure; } + // Load library + pal::dll_t fxr; if (!pal::load_library(&fxr_path, &fxr)) { trace::error(_X("The library %s was found, but loading it from %s failed"), LIBFXR_NAME, fxr_path.c_str()); @@ -208,14 +294,56 @@ int run(const int argc, const pal::char_t* argv[]) return StatusCode::CoreHostLibLoadFailure; } - // Previous corehost trace messages must be printed before calling trace::setup in hostfxr - trace::flush(); - // Obtain the entrypoints. - hostfxr_main_fn main_fn = (hostfxr_main_fn) pal::get_symbol(fxr, "hostfxr_main"); - int code = main_fn(argc, argv); + int rc; + hostfxr_main_startupinfo_fn main_fn_v2 = (hostfxr_main_startupinfo_fn)pal::get_symbol(fxr, "hostfxr_main_startupinfo"); + if (main_fn_v2) + { + const pal::char_t* host_path_cstr = host_path.c_str(); + const pal::char_t* dotnet_root_cstr = dotnet_root.empty() ? nullptr : dotnet_root.c_str(); + const pal::char_t* app_path_cstr = app_path.empty() ? nullptr : app_path.c_str(); + + trace::info(_X("Invoking fx resolver [%s] v2"), fxr_path.c_str()); + trace::info(_X("Host path: [%s]"), host_path.c_str()); + trace::info(_X("Dotnet path: [%s]"), dotnet_root.c_str()); + trace::info(_X("App path: [%s]"), app_path.c_str()); + + // Previous corehost trace messages must be printed before calling trace::setup in hostfxr + trace::flush(); + + rc = main_fn_v2(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr); + } + else + { + if (requires_v2_hostfxr_interface) + { + trace::error(_X("The required library %s does not support startupconfig.json functionality."), fxr_path.c_str()); + rc = StatusCode::CoreHostEntryPointFailure; + } + else + { + trace::info(_X("Invoking fx resolver [%s] v1"), fxr_path.c_str()); + + // Previous corehost trace messages must be printed before calling trace::setup in hostfxr + trace::flush(); + + // For compat, use the v1 interface. This requires additional file I\O to re-parse parameters and + // for apphost, does not support DOTNET_ROOT or dll with different name for exe. + hostfxr_main_fn main_fn_v1 = (hostfxr_main_fn)pal::get_symbol(fxr, "hostfxr_main"); + if (main_fn_v1) + { + rc = main_fn_v1(argc, argv); + } + else + { + trace::error(_X("The required library %s does not contain the expected entry point."), fxr_path.c_str()); + rc = StatusCode::CoreHostEntryPointFailure; + } + } + } + pal::unload_library(fxr); - return code; + return rc; } #if defined(_WIN32) @@ -238,4 +366,3 @@ int main(const int argc, const pal::char_t* argv[]) return run(argc, argv); } - diff --git a/src/corehost/error_codes.h b/src/corehost/error_codes.h index 480c664188..220d2dec22 100644 --- a/src/corehost/error_codes.h +++ b/src/corehost/error_codes.h @@ -31,5 +31,6 @@ enum StatusCode HostApiFailed = 0x80008097, HostApiBufferTooSmall = 0x80008098, LibHostUnknownCommand = 0x80008099, + LibHostAppRootFindFailure = 0x8000809a, }; #endif // __ERROR_CODES_H__ diff --git a/src/test/HostActivationTests/GivenThatICareAboutStandaloneAppActivation.cs b/src/test/HostActivationTests/GivenThatICareAboutStandaloneAppActivation.cs index da94781322..fae20e4d4e 100644 --- a/src/test/HostActivationTests/GivenThatICareAboutStandaloneAppActivation.cs +++ b/src/test/HostActivationTests/GivenThatICareAboutStandaloneAppActivation.cs @@ -10,6 +10,7 @@ using FluentAssertions; using Microsoft.DotNet.CoreSetup.Test; using Microsoft.DotNet.Cli.Build.Framework; +using Newtonsoft.Json.Linq; using System.Security.Cryptography; using System.Text; using Microsoft.DotNet.InternalAbstractions; @@ -50,13 +51,6 @@ public void Running_Build_Output_Standalone_EXE_with_DepsJson_and_RuntimeConfig_ var appExe = fixture.TestProject.AppExe; - // TODO: Use FS.Chmod when build utility project is converted to csproj. - // See https://github.com/NuGet/Home/issues/4424 - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - Command.Create("chmod", "u+x", appExe).Execute().EnsureSuccessful(); - } - Command.Create(appExe) .CaptureStdErr() .CaptureStdOut() @@ -75,13 +69,6 @@ public void Running_Publish_Output_Standalone_EXE_with_DepsJson_and_RuntimeConfi var appExe = fixture.TestProject.AppExe; - // TODO: Use FS.Chmod when build utility project is converted to csproj. - // See https://github.com/NuGet/Home/issues/4424 - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - Command.Create("chmod", "u+x", appExe).Execute().EnsureSuccessful(); - } - Command.Create(appExe) .CaptureStdErr() .CaptureStdOut() @@ -107,7 +94,7 @@ public void Running_Publish_Output_Standalone_EXE_with_Unbound_AppHost_Fails() int exitCode = Command.Create(appExe) .CaptureStdErr() .CaptureStdOut() - .Execute(fExpectedToFail:true) + .Execute(fExpectedToFail: true) .ExitCode; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -136,7 +123,7 @@ public void Running_Publish_Output_Standalone_EXE_By_Renaming_dotnet_exe_Fails() int exitCode = Command.Create(appExe) .CaptureStdErr() .CaptureStdOut() - .Execute(fExpectedToFail:true) + .Execute(fExpectedToFail: true) .ExitCode; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -150,6 +137,103 @@ public void Running_Publish_Output_Standalone_EXE_By_Renaming_dotnet_exe_Fails() } } + [Fact] + public void Running_Publish_Output_Standalone_EXE_By_Renaming_apphost_exe_Succeeds() + { + var fixture = PreviouslyPublishedAndRestoredStandaloneTestProjectFixture + .Copy(); + + var appExe = fixture.TestProject.AppExe; + var renamedAppExe = fixture.TestProject.AppExe + $"renamed{Constants.ExeSuffix}"; + + File.Copy(appExe, renamedAppExe, true); + + Command.Create(renamedAppExe) + .CaptureStdErr() + .CaptureStdOut() + .Execute() + .Should() + .Pass() + .And + .HaveStdOutContaining("Hello World"); + } + + [Fact] + public void Running_Publish_Output_Standalone_EXE_With_Startupconfig_Succeeds() + { + var fixture = PreviouslyPublishedAndRestoredStandaloneTestProjectFixture + .Copy(); + + var appExe = fixture.TestProject.AppExe; + + // Move whole directory to a subdirectory + string currentOutDir = fixture.TestProject.OutputDirectory; + string relativeNewPath = ".."; + relativeNewPath = Path.Combine(relativeNewPath, "newDir"); + string newOutDir = Path.Combine(currentOutDir, relativeNewPath); + Directory.Move(currentOutDir, newOutDir); + + // Move the apphost exe back to original location + string appExeName = Path.GetFileName(appExe); + string sourceAppExePath = Path.Combine(newOutDir, appExeName); + Directory.CreateDirectory(Path.GetDirectoryName(appExe)); + File.Move(sourceAppExePath, appExe); + + // Create the startupConfig.json + string startupConfigFileName = Path.GetFileNameWithoutExtension(appExe) + ".startupconfig.json"; + string startupConfigPath = Path.Combine(currentOutDir, startupConfigFileName); + SetStartupConfigJson(startupConfigPath, relativeNewPath); + + Command.Create(appExe) + .CaptureStdErr() + .CaptureStdOut() + .Execute() + .Should() + .Pass() + .And + .HaveStdOutContaining("Hello World"); + } + + [Fact] + public void Running_Publish_Output_Standalone_EXE_With_DOTNET_ROOT_Fails() + { + var fixture = PreviouslyPublishedAndRestoredStandaloneTestProjectFixture + .Copy(); + + var appExe = fixture.TestProject.AppExe; + + // Move whole directory to a subdirectory + string currentOutDir = fixture.TestProject.OutputDirectory; + string relativeNewPath = ".."; + relativeNewPath = Path.Combine(relativeNewPath, "newDir2"); + string newOutDir = Path.Combine(currentOutDir, relativeNewPath); + Directory.Move(currentOutDir, newOutDir); + + // Move just the apphost exe back to original location + string appExeName = Path.GetFileName(appExe); + string sourceAppExePath = Path.Combine(newOutDir, appExeName); + Directory.CreateDirectory(Path.GetDirectoryName(appExe)); + File.Move(sourceAppExePath, appExe); + + // This verifies a self-contained apphost cannot use DOTNET_ROOT to reference a flat + // self-contained layout since a flat layout of the shared framework is not supported. + Command.Create(appExe) + .EnvironmentVariable("COREHOST_TRACE", "1") + .EnvironmentVariable("DOTNET_ROOT", newOutDir) + .EnvironmentVariable("DOTNET_ROOT(x86)", newOutDir) + .CaptureStdErr() + .CaptureStdOut() + .Execute(fExpectedToFail: true) + .Should() + .Fail() + .And + .HaveStdErrContaining($"Using environment variable DOTNET_ROOT") // use the first part avoiding "(x86)" if present + .And + .HaveStdErrContaining($"=[{Path.GetFullPath(newOutDir)}] as runtime location.") // use the last part + .And + .HaveStdErrContaining("A fatal error occurred"); + } + [Fact] public void Running_Publish_Output_Standalone_EXE_with_Bound_AppHost_Succeeds() { @@ -174,7 +258,7 @@ public void Running_Publish_Output_Standalone_EXE_with_Bound_AppHost_Succeeds() { // Replace the hash with the managed DLL name. var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes("foobar")); - var hashStr = BitConverter.ToString(hash).Replace("-", "").ToLower(); + var hashStr = BitConverter.ToString(hash).Replace("-", "").ToLower(); AppHostExtensions.SearchAndReplace(appDirHostExe, Encoding.UTF8.GetBytes(hashStr), Encoding.UTF8.GetBytes(appDll), true); } File.Copy(appDirHostExe, appExe, true); @@ -216,5 +300,33 @@ private static void ReplaceTestProjectOutputHostInTestProjectFixture(TestProject File.Copy(dotnetHostFxr, testProjectHostFxr, true); } } + + // Generated json file: + /* + { + "startupOptions": { + "appRoot": "${appRoot}" + } + } + */ + private void SetStartupConfigJson(string destFile, string appRoot) + { + JObject startupOptions = new JObject( + new JProperty("startupOptions", + new JObject( + new JProperty("appRoot", appRoot) + ) + ) + ); + + FileInfo file = new FileInfo(destFile); + if (!file.Directory.Exists) + { + file.Directory.Create(); + } + + File.WriteAllText(destFile, startupOptions.ToString()); + } } } +