From 78b303df8fbb242985d049a277d0d199cafd51b5 Mon Sep 17 00:00:00 2001 From: Swaroop Sridhar Date: Wed, 8 Apr 2020 11:35:03 -0700 Subject: [PATCH] Single-File: Process bundles in the framework (#34274) * Single-File: Process bundles in the framework This change implements the host changes proposed in the [design](https://github.com/dotnet/designs/blob/master/accepted/2020/single-file/design.md#startup) The main changes for single-file bundles are: * Bundle processing code is moved from apphost to hostpolicy * HostFxr and HostPolicy process deps.json and runtimeconfig.json files directly from the bundle. * HostPolicy performs verification wrt deps.json based on the contents of the single-file bundle. * AppContext.BaseDirectory is set as explained [here](https://github.com/dotnet/designs/blob/master/accepted/2020/single-file/design.md#appcontextbasedirectory) Currently, all files except deps.json and runtimeconfig.json are extracted to disk. Once the runtime is able to processing assemblies directly from the bundle, they will no longer be extracted. Notable details: * The bundle driver (formarly runner.cpp) is divided into two parts: * bundle::info which describes basic information about the bundle available from the headers only * bundle::runner which has information about all embedded files, and the ability to drive extraction This facilitates linking only parts of the bundle handling code with hostfxr, while all code is linked with hostpolicy. * The AppHost only links with bundle_marker to identify itself as a single-file bundle. * If the AppHost is a single-file bundle, it notifies hostfxr using the new hostfxr_main_bundle_startup_info() API * The HostFxr comminucates the single-file-information with HostPolicy using the host_interface_t structure. Fixes https://github.com/dotnet/runtime/issues/32821 --- src/installer/corehost/cli/apphost/CMakeLists.txt | 19 +-- .../corehost/cli/apphost/bundle/runner.cpp | 65 --------- src/installer/corehost/cli/apphost/bundle/runner.h | 40 ------ .../{bundle/marker.cpp => bundle_marker.cpp} | 8 +- .../apphost/{bundle/marker.h => bundle_marker.h} | 12 +- .../cli/{apphost => }/bundle/dir_utils.cpp | 0 .../corehost/cli/{apphost => }/bundle/dir_utils.h | 0 .../cli/{apphost => }/bundle/extractor.cpp | 10 +- .../corehost/cli/{apphost => }/bundle/extractor.h | 0 .../cli/{apphost => }/bundle/file_entry.cpp | 15 ++ .../corehost/cli/{apphost => }/bundle/file_entry.h | 1 + .../corehost/cli/{apphost => }/bundle/file_type.h | 0 .../corehost/cli/{apphost => }/bundle/header.cpp | 24 ++-- .../corehost/cli/{apphost => }/bundle/header.h | 24 ++-- src/installer/corehost/cli/bundle/info.cpp | 157 +++++++++++++++++++++ src/installer/corehost/cli/bundle/info.h | 91 ++++++++++++ .../corehost/cli/{apphost => }/bundle/manifest.cpp | 4 +- .../corehost/cli/{apphost => }/bundle/manifest.h | 8 ++ .../corehost/cli/{apphost => }/bundle/reader.cpp | 11 +- .../corehost/cli/{apphost => }/bundle/reader.h | 19 +-- src/installer/corehost/cli/bundle/runner.cpp | 83 +++++++++++ src/installer/corehost/cli/bundle/runner.h | 49 +++++++ src/installer/corehost/cli/deps_entry.cpp | 118 +++++++++++----- src/installer/corehost/cli/deps_entry.h | 14 +- src/installer/corehost/cli/deps_format.cpp | 7 +- src/installer/corehost/cli/fxr/command_line.cpp | 3 +- src/installer/corehost/cli/fxr/corehost_init.cpp | 3 + src/installer/corehost/cli/fxr/corehost_init.h | 3 +- src/installer/corehost/cli/fxr/fx_muxer.cpp | 8 ++ src/installer/corehost/cli/fxr/hostfxr.cpp | 18 +++ .../corehost/cli/fxr/hostpolicy_resolver.cpp | 1 - src/installer/corehost/cli/host_interface.h | 5 +- src/installer/corehost/cli/host_startup_info.h | 1 + .../corehost/cli/hostcommon/CMakeLists.txt | 6 + src/installer/corehost/cli/hostfxr.h | 7 + src/installer/corehost/cli/hostmisc/pal.h | 8 +- src/installer/corehost/cli/hostmisc/pal.unix.cpp | 30 ++-- .../corehost/cli/hostmisc/pal.windows.cpp | 31 ++-- src/installer/corehost/cli/hostmisc/utils.cpp | 19 ++- src/installer/corehost/cli/hostmisc/utils.h | 2 + .../corehost/cli/hostpolicy/CMakeLists.txt | 10 ++ src/installer/corehost/cli/hostpolicy/args.cpp | 46 +++++- .../corehost/cli/hostpolicy/deps_resolver.cpp | 6 +- .../corehost/cli/hostpolicy/deps_resolver.h | 12 ++ .../corehost/cli/hostpolicy/hostpolicy.cpp | 15 +- .../corehost/cli/hostpolicy/hostpolicy_init.cpp | 10 ++ .../corehost/cli/hostpolicy/hostpolicy_init.h | 1 + src/installer/corehost/cli/ijwhost/ijwthunk.cpp | 6 +- .../corehost/cli/json/rapidjson/document.h | 4 +- src/installer/corehost/cli/json_parser.cpp | 62 +++++--- src/installer/corehost/cli/json_parser.h | 20 ++- src/installer/corehost/cli/runtime_config.cpp | 5 +- .../cli/test/mockhostpolicy/mockhostpolicy.cpp | 1 + src/installer/corehost/corehost.cpp | 141 ++++++++++-------- .../Microsoft.NET.HostModel/Bundle/Bundler.cs | 18 +-- .../Microsoft.NET.HostModel/Bundle/Manifest.cs | 2 +- .../Microsoft.NET.HostModel/Bundle/TargetInfo.cs | 17 ++- .../Assets/TestProjects/AppWithSubDirs/Program.cs | 6 +- .../BundleExtractToSpecificPath.cs | 64 ++++----- .../AppHost.Bundle.Tests/BundleRename.cs | 4 +- .../AppHost.Bundle.Tests/BundledAppWithSubDirs.cs | 34 +++-- .../Helpers/BundleHelper.cs | 97 +++++++++++-- .../BundleAndRun.cs | 5 +- .../BundleLegacy.cs | 1 - .../BundlerConsistencyTests.cs | 69 +++------ src/installer/test/TestUtils/TestApp.cs | 2 + src/installer/test/TestUtils/TestProject.cs | 1 + 67 files changed, 1104 insertions(+), 479 deletions(-) delete mode 100644 src/installer/corehost/cli/apphost/bundle/runner.cpp delete mode 100644 src/installer/corehost/cli/apphost/bundle/runner.h rename src/installer/corehost/cli/apphost/{bundle/marker.cpp => bundle_marker.cpp} (86%) rename src/installer/corehost/cli/apphost/{bundle/marker.h => bundle_marker.h} (83%) rename src/installer/corehost/cli/{apphost => }/bundle/dir_utils.cpp (100%) rename src/installer/corehost/cli/{apphost => }/bundle/dir_utils.h (100%) rename src/installer/corehost/cli/{apphost => }/bundle/extractor.cpp (98%) rename src/installer/corehost/cli/{apphost => }/bundle/extractor.h (100%) rename src/installer/corehost/cli/{apphost => }/bundle/file_entry.cpp (77%) rename src/installer/corehost/cli/{apphost => }/bundle/file_entry.h (98%) rename src/installer/corehost/cli/{apphost => }/bundle/file_type.h (100%) rename src/installer/corehost/cli/{apphost => }/bundle/header.cpp (53%) rename src/installer/corehost/cli/{apphost => }/bundle/header.h (76%) create mode 100644 src/installer/corehost/cli/bundle/info.cpp create mode 100644 src/installer/corehost/cli/bundle/info.h rename src/installer/corehost/cli/{apphost => }/bundle/manifest.cpp (71%) rename src/installer/corehost/cli/{apphost => }/bundle/manifest.h (76%) rename src/installer/corehost/cli/{apphost => }/bundle/reader.cpp (88%) rename src/installer/corehost/cli/{apphost => }/bundle/reader.h (74%) create mode 100644 src/installer/corehost/cli/bundle/runner.cpp create mode 100644 src/installer/corehost/cli/bundle/runner.h diff --git a/src/installer/corehost/cli/apphost/CMakeLists.txt b/src/installer/corehost/cli/apphost/CMakeLists.txt index d77f2bf..ba01e32 100644 --- a/src/installer/corehost/cli/apphost/CMakeLists.txt +++ b/src/installer/corehost/cli/apphost/CMakeLists.txt @@ -18,26 +18,11 @@ endif() set(SKIP_VERSIONING 1) set(SOURCES - ./bundle/file_entry.cpp - ./bundle/manifest.cpp - ./bundle/header.cpp - ./bundle/marker.cpp - ./bundle/reader.cpp - ./bundle/extractor.cpp - ./bundle/runner.cpp - ./bundle/dir_utils.cpp + ./bundle_marker.cpp ) set(HEADERS - ./bundle/file_type.h - ./bundle/file_entry.h - ./bundle/manifest.h - ./bundle/header.h - ./bundle/marker.h - ./bundle/reader.h - ./bundle/extractor.h - ./bundle/runner.h - ./bundle/dir_utils.h + ./bundle_marker.h ) if(CLR_CMAKE_TARGET_WIN32) diff --git a/src/installer/corehost/cli/apphost/bundle/runner.cpp b/src/installer/corehost/cli/apphost/bundle/runner.cpp deleted file mode 100644 index 50d7423..0000000 --- a/src/installer/corehost/cli/apphost/bundle/runner.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#include -#include "extractor.h" -#include "runner.h" -#include "trace.h" -#include "header.h" -#include "marker.h" -#include "manifest.h" - -using namespace bundle; - -void runner_t::map_host() -{ - m_bundle_map = (int8_t *) pal::map_file_readonly(m_bundle_path, m_bundle_length); - - if (m_bundle_map == nullptr) - { - trace::error(_X("Failure processing application bundle.")); - trace::error(_X("Couldn't memory map the bundle file for reading.")); - throw StatusCode::BundleExtractionIOError; - } -} - -void runner_t::unmap_host() -{ - if (!pal::unmap_file(m_bundle_map, m_bundle_length)) - { - trace::warning(_X("Failed to unmap bundle after extraction.")); - } -} - -// Current support for executing single-file bundles involves -// extraction of embedded files to actual files on disk. -// This method implements the file extraction functionality at startup. -StatusCode runner_t::extract() -{ - try - { - map_host(); - reader_t reader(m_bundle_map, m_bundle_length); - - // Read the bundle header - reader.set_offset(marker_t::header_offset()); - header_t header = header_t::read(reader, /* need_exact_version: */ true); - - // Read the bundle manifest - // Reader is at the correct offset - manifest_t manifest = manifest_t::read(reader, header.num_embedded_files()); - - // Extract the files - extractor_t extractor(header.bundle_id(), m_bundle_path, manifest); - m_extraction_dir = extractor.extract(reader); - - unmap_host(); - return StatusCode::Success; - } - catch (StatusCode e) - { - return e; - } -} - diff --git a/src/installer/corehost/cli/apphost/bundle/runner.h b/src/installer/corehost/cli/apphost/bundle/runner.h deleted file mode 100644 index 07daded..0000000 --- a/src/installer/corehost/cli/apphost/bundle/runner.h +++ /dev/null @@ -1,40 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#ifndef __RUNNER_H__ -#define __RUNNER_H__ - -#include "error_codes.h" - -namespace bundle -{ - class runner_t - { - public: - runner_t(const pal::string_t& bundle_path) - : m_bundle_path(bundle_path) - , m_bundle_map(nullptr) - , m_bundle_length(0) - { - } - - StatusCode extract(); - - pal::string_t extraction_dir() - { - return m_extraction_dir; - } - - private: - void map_host(); - void unmap_host(); - - pal::string_t m_bundle_path; - pal::string_t m_extraction_dir; - int8_t* m_bundle_map; - size_t m_bundle_length; - }; -} - -#endif // __RUNNER_H__ diff --git a/src/installer/corehost/cli/apphost/bundle/marker.cpp b/src/installer/corehost/cli/apphost/bundle_marker.cpp similarity index 86% rename from src/installer/corehost/cli/apphost/bundle/marker.cpp rename to src/installer/corehost/cli/apphost/bundle_marker.cpp index d230292..d8ea15b 100644 --- a/src/installer/corehost/cli/apphost/bundle/marker.cpp +++ b/src/installer/corehost/cli/apphost/bundle_marker.cpp @@ -2,14 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#include "marker.h" +#include "bundle_marker.h" #include "pal.h" #include "trace.h" #include "utils.h" -using namespace bundle; - -int64_t marker_t::header_offset() +int64_t bundle_marker_t::header_offset() { // Contains the bundle_placeholder default value at compile time. // If this is a single-file bundle, the last 8 bytes are replaced @@ -27,7 +25,7 @@ int64_t marker_t::header_offset() 0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae }; - volatile marker_t* marker = reinterpret_cast(placeholder); + volatile bundle_marker_t* marker = reinterpret_cast(placeholder); return marker->locator.bundle_header_offset; } diff --git a/src/installer/corehost/cli/apphost/bundle/marker.h b/src/installer/corehost/cli/apphost/bundle_marker.h similarity index 83% rename from src/installer/corehost/cli/apphost/bundle/marker.h rename to src/installer/corehost/cli/apphost/bundle_marker.h index 52e185f..c5065fd 100644 --- a/src/installer/corehost/cli/apphost/bundle/marker.h +++ b/src/installer/corehost/cli/apphost/bundle_marker.h @@ -2,15 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#ifndef __MARKER_H__ -#define __MARKER_H__ +#ifndef __BUNDLE_MARKER_H__ +#define __BUNDLE_MARKER_H__ #include -namespace bundle -{ #pragma pack(push, 1) - union marker_t + union bundle_marker_t { public: uint8_t placeholder[40]; @@ -28,5 +26,5 @@ namespace bundle }; #pragma pack(pop) -} -#endif // __MARKER_H__ + +#endif // __BUNDLE_MARKER_H__ diff --git a/src/installer/corehost/cli/apphost/bundle/dir_utils.cpp b/src/installer/corehost/cli/bundle/dir_utils.cpp similarity index 100% rename from src/installer/corehost/cli/apphost/bundle/dir_utils.cpp rename to src/installer/corehost/cli/bundle/dir_utils.cpp diff --git a/src/installer/corehost/cli/apphost/bundle/dir_utils.h b/src/installer/corehost/cli/bundle/dir_utils.h similarity index 100% rename from src/installer/corehost/cli/apphost/bundle/dir_utils.h rename to src/installer/corehost/cli/bundle/dir_utils.h diff --git a/src/installer/corehost/cli/apphost/bundle/extractor.cpp b/src/installer/corehost/cli/bundle/extractor.cpp similarity index 98% rename from src/installer/corehost/cli/apphost/bundle/extractor.cpp rename to src/installer/corehost/cli/bundle/extractor.cpp index 71bb225..04c5205 100644 --- a/src/installer/corehost/cli/apphost/bundle/extractor.cpp +++ b/src/installer/corehost/cli/bundle/extractor.cpp @@ -197,7 +197,10 @@ void extractor_t::extract_new(reader_t& reader) begin(); for (const file_entry_t& entry : m_manifest.files) { - extract(entry, reader); + if (entry.needs_extraction()) + { + extract(entry, reader); + } } commit_dir(); } @@ -211,6 +214,11 @@ void extractor_t::verify_recover_extraction(reader_t& reader) for (const file_entry_t& entry : m_manifest.files) { + if (!entry.needs_extraction()) + { + continue; + } + pal::string_t file_path = ext_dir; append_path(&file_path, entry.relative_path().c_str()); diff --git a/src/installer/corehost/cli/apphost/bundle/extractor.h b/src/installer/corehost/cli/bundle/extractor.h similarity index 100% rename from src/installer/corehost/cli/apphost/bundle/extractor.h rename to src/installer/corehost/cli/bundle/extractor.h diff --git a/src/installer/corehost/cli/apphost/bundle/file_entry.cpp b/src/installer/corehost/cli/bundle/file_entry.cpp similarity index 77% rename from src/installer/corehost/cli/apphost/bundle/file_entry.cpp rename to src/installer/corehost/cli/bundle/file_entry.cpp index ba4b235..775117f 100644 --- a/src/installer/corehost/cli/apphost/bundle/file_entry.cpp +++ b/src/installer/corehost/cli/bundle/file_entry.cpp @@ -33,3 +33,18 @@ file_entry_t file_entry_t::read(reader_t &reader) return entry; } + +bool file_entry_t::needs_extraction() const +{ + switch (m_type) + { + // Once the runtime can load assemblies from bundle, + // file_type_t::assembly should be in this category + case file_type_t::deps_json: + case file_type_t::runtime_config_json: + return false; + + default: + return true; + } +} diff --git a/src/installer/corehost/cli/apphost/bundle/file_entry.h b/src/installer/corehost/cli/bundle/file_entry.h similarity index 98% rename from src/installer/corehost/cli/apphost/bundle/file_entry.h rename to src/installer/corehost/cli/bundle/file_entry.h index efe405c..c89d3ce 100644 --- a/src/installer/corehost/cli/apphost/bundle/file_entry.h +++ b/src/installer/corehost/cli/bundle/file_entry.h @@ -58,6 +58,7 @@ namespace bundle int64_t offset() const { return m_offset; } int64_t size() const { return m_size; } file_type_t type() const { return m_type; } + bool needs_extraction() const; static file_entry_t read(reader_t &reader); diff --git a/src/installer/corehost/cli/apphost/bundle/file_type.h b/src/installer/corehost/cli/bundle/file_type.h similarity index 100% rename from src/installer/corehost/cli/apphost/bundle/file_type.h rename to src/installer/corehost/cli/bundle/file_type.h diff --git a/src/installer/corehost/cli/apphost/bundle/header.cpp b/src/installer/corehost/cli/bundle/header.cpp similarity index 53% rename from src/installer/corehost/cli/apphost/bundle/header.cpp rename to src/installer/corehost/cli/bundle/header.cpp index 23695bb..7e6f778 100644 --- a/src/installer/corehost/cli/apphost/bundle/header.cpp +++ b/src/installer/corehost/cli/bundle/header.cpp @@ -9,29 +9,23 @@ using namespace bundle; -// The AppHost expects the bundle_header to be an exact_match for which it was built. -// The framework accepts backwards compatible header versions. -bool header_fixed_t::is_valid(bool exact_match) const +bool header_fixed_t::is_valid() const { if (num_embedded_files <= 0) { return false; } - if (exact_match) - { - return (major_version == header_t::major_version) && (minor_version == header_t::minor_version); - } - - return ((major_version < header_t::major_version) || - (major_version == header_t::major_version && minor_version <= header_t::minor_version)); + // .net 5 host expects the version information to be 2.0 + // .net core 3 single-file bundles are handled within the netcoreapp3.x apphost, and are not processed here in the framework. + return (major_version == header_t::major_version) && (minor_version == header_t::minor_version); } -header_t header_t::read(reader_t& reader, bool need_exact_version) +header_t header_t::read(reader_t& reader) { const header_fixed_t* fixed_header = reinterpret_cast(reader.read_direct(sizeof(header_fixed_t))); - if (!fixed_header->is_valid(need_exact_version)) + if (!fixed_header->is_valid()) { trace::error(_X("Failure processing application bundle.")); trace::error(_X("Bundle header version compatibility check failed.")); @@ -44,10 +38,8 @@ header_t header_t::read(reader_t& reader, bool need_exact_version) // bundle_id is a component of the extraction path reader.read_path_string(header.m_bundle_id); - if (fixed_header->major_version > 1) - { - header.m_v2_header = reinterpret_cast(reader.read_direct(sizeof(header_fixed_v2_t))); - } + const header_fixed_v2_t *v2_header = reinterpret_cast(reader.read_direct(sizeof(header_fixed_v2_t))); + header.m_v2_header = *v2_header; return header; } diff --git a/src/installer/corehost/cli/apphost/bundle/header.h b/src/installer/corehost/cli/bundle/header.h similarity index 76% rename from src/installer/corehost/cli/apphost/bundle/header.h rename to src/installer/corehost/cli/bundle/header.h index 1b16962..f9ec85f 100644 --- a/src/installer/corehost/cli/apphost/bundle/header.h +++ b/src/installer/corehost/cli/bundle/header.h @@ -32,9 +32,8 @@ namespace bundle uint32_t minor_version; int32_t num_embedded_files; - bool is_valid(bool exact_match = false) const; + bool is_valid() const; }; -#pragma pack(pop) // netcoreapp3_compat_mode flag is set on a .net5 app, which chooses to build single-file apps in .netcore3.x compat mode, // This indicates that: @@ -46,12 +45,13 @@ namespace bundle netcoreapp3_compat_mode = 1 }; -#pragma pack(push, 1) struct location_t { public: int64_t offset; int64_t size; + + bool is_valid() const { return offset != 0; } }; // header_fixed_v2_t is available in single-file apps targetting .net5+ frameworks. @@ -65,6 +65,8 @@ namespace bundle location_t deps_json_location; location_t runtimeconfig_json_location; header_flags_t flags; + + bool is_netcoreapp3_compat_mode() const { return (flags & header_flags_t::netcoreapp3_compat_mode) != 0; } }; #pragma pack(pop) @@ -74,14 +76,17 @@ namespace bundle header_t(int32_t num_embedded_files = 0) : m_num_embedded_files(num_embedded_files) , m_bundle_id() - , m_v2_header(NULL) - + , m_v2_header() { } - static header_t read(reader_t& reader, bool need_exact_version); - const pal::string_t& bundle_id() { return m_bundle_id; } - int32_t num_embedded_files() { return m_num_embedded_files; } + static header_t read(reader_t& reader); + const pal::string_t& bundle_id() const { return m_bundle_id; } + int32_t num_embedded_files() const { return m_num_embedded_files; } + + const location_t& deps_json_location() const { return m_v2_header.deps_json_location; } + const location_t& runtimeconfig_json_location() const { return m_v2_header.runtimeconfig_json_location; } + bool is_netcoreapp3_compat_mode() const { return m_v2_header.is_netcoreapp3_compat_mode(); } static const uint32_t major_version = 2; static const uint32_t minor_version = 0; @@ -89,8 +94,7 @@ namespace bundle private: int32_t m_num_embedded_files; pal::string_t m_bundle_id; - const header_fixed_v2_t* m_v2_header; - + header_fixed_v2_t m_v2_header; }; } #endif // __HEADER_H__ diff --git a/src/installer/corehost/cli/bundle/info.cpp b/src/installer/corehost/cli/bundle/info.cpp new file mode 100644 index 0000000..3fef422 --- /dev/null +++ b/src/installer/corehost/cli/bundle/info.cpp @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "trace.h" +#include "info.h" +#include "utils.h" + +using namespace bundle; + +// Global single-file bundle information, if any +const info_t* info_t::the_app = nullptr; + +info_t::info_t(const pal::char_t* bundle_path, + const pal::char_t* app_path, + int64_t header_offset) + : m_bundle_path(bundle_path) + , m_bundle_size(0) + , m_header_offset(header_offset) +{ + m_base_path = get_directory(m_bundle_path); + + // Single-file bundles currently only support deps/runtime config json files + // named based on the app.dll. Any other name for these configuration files + // mentioned via the command line are assumed to be actual files on disk. + // + // Supporting custom names for these config files is straightforward (with associated changes in bundler and SDK). + // There is no known use-case for it yet, and the facility is TBD. + + m_deps_json = config_t(get_deps_from_app_binary(m_base_path, app_path)); + m_runtimeconfig_json = config_t(get_runtime_config_path(m_base_path, get_filename_without_ext(app_path))); +} + +StatusCode info_t::process_bundle(const pal::char_t* bundle_path, const pal::char_t* app_path, int64_t header_offset) +{ + if (header_offset == 0) + { + // Not a single-file bundle. + return StatusCode::Success; + } + + static info_t info(bundle_path, app_path, header_offset); + StatusCode status = info.process_header(); + + if (status != StatusCode::Success) + { + return status; + } + + trace::info(_X("Single-File bundle details:")); + trace::info(_X("DepsJson Offset:[%lx] Size[%lx]"), info.m_header.deps_json_location().offset, info.m_header.deps_json_location().size); + trace::info(_X("RuntimeConfigJson Offset:[%lx] Size[%lx]"), info.m_header.runtimeconfig_json_location().offset, info.m_header.runtimeconfig_json_location().size); + trace::info(_X(".net core 3 compatibility mode: [%s]"), info.m_header.is_netcoreapp3_compat_mode() ? _X("Yes") : _X("No")); + + the_app = &info; + + return StatusCode::Success; +} + +StatusCode info_t::process_header() +{ + try + { + const char* addr = map_bundle(); + + reader_t reader(addr, m_bundle_size, m_header_offset); + + m_header = header_t::read(reader); + m_deps_json.set_location(&m_header.deps_json_location()); + m_runtimeconfig_json.set_location(&m_header.runtimeconfig_json_location()); + + unmap_bundle(addr); + + return StatusCode::Success; + } + catch (StatusCode e) + { + return e; + } +} + +char* info_t::config_t::map(const pal::string_t& path, const location_t* &location) +{ + assert(is_single_file_bundle()); + + const bundle::info_t* app = bundle::info_t::the_app; + if (app->m_deps_json.matches(path)) + { + location = app->m_deps_json.m_location; + } + else if (app->m_runtimeconfig_json.matches(path)) + { + location = app->m_runtimeconfig_json.m_location; + } + else + { + return nullptr; + } + + // When necessary to map the deps.json or runtimeconfig.json files, we map the whole single-file bundle, + // and return the address at the appropriate offset. + // This is because: + // * The host is the only code that is currently running and trying to map the bundle. + // * Files can only be memory mapped at page-aligned offsets, and in whole page units. + // Therefore, mapping only portions of the bundle will involve align-down/round-up calculations, and associated offset adjustments. + // We choose the simpler approach of rounding to the whole file + // * There is no performance limitation due to a larger sized mapping, since we actually only read the pages with relevant contents. + // * Files that are too large to be mapped (ex: that exhaust 32-bit virtual address space) are not supported. + + char* addr = (char*)pal::mmap_copy_on_write(app->m_bundle_path); + if (addr == nullptr) + { + trace::error(_X("Failure processing application bundle.")); + trace::error(_X("Failed to map bundle file [%s]"), path.c_str()); + } + + trace::info(_X("Mapped bundle for [%s]"), path.c_str()); + + return addr + location->offset; +} + +void info_t::config_t::unmap(const char* addr, const location_t* location) +{ + // Adjust to the beginning of the bundle. + addr -= location->offset; + bundle::info_t::the_app->unmap_bundle(addr); +} + +const char* info_t::map_bundle() +{ + const void *addr = pal::mmap_read(m_bundle_path, &m_bundle_size); + + if (addr == nullptr) + { + trace::error(_X("Failure processing application bundle.")); + trace::error(_X("Couldn't memory map the bundle file for reading.")); + throw StatusCode::BundleExtractionIOError; + } + + trace::info(_X("Mapped application bundle")); + + return (const char *)addr; +} + +void info_t::unmap_bundle(const char* addr) const +{ + if (!pal::munmap((void*)addr, m_bundle_size)) + { + trace::warning(_X("Failed to unmap bundle after extraction.")); + } + else + { + trace::info(_X("Unmapped application bundle")); + } +} + + diff --git a/src/installer/corehost/cli/bundle/info.h b/src/installer/corehost/cli/bundle/info.h new file mode 100644 index 0000000..0f3db55 --- /dev/null +++ b/src/installer/corehost/cli/bundle/info.h @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef __INFO_H_ +#define __INFO_H_ + +#include "error_codes.h" +#include "header.h" + +// bundle::info supports: +// * API for identification of a single-file app bundle, and +// * Minimal probing and mapping functionality only for the app.runtimeconfig.json and app.deps.json files. +// bundle::info is used by HostFxr to read the above config files. + +namespace bundle +{ + struct info_t + { + struct config_t + { + config_t() + : m_location(nullptr) {} + + config_t(const config_t& config) + { + m_path = config.m_path; + m_location = config.m_location; + } + + config_t(const pal::string_t& path, const location_t *location=nullptr) + { + m_path = path; + m_location = location; + } + + bool matches(const pal::string_t& path) const + { + return m_location->is_valid() && path.compare(m_path) == 0; + } + + static bool probe(const pal::string_t& path) + { + return is_single_file_bundle() && + (the_app->m_deps_json.matches(path) || the_app->m_runtimeconfig_json.matches(path)); + } + + void set_location(const location_t* location) + { + m_location = location; + } + + static char* map(const pal::string_t& path, const location_t* &location); + static void unmap(const char* addr, const location_t* location); + + private: + pal::string_t m_path; + const location_t *m_location; + }; + + static StatusCode process_bundle(const pal::char_t* bundle_path, const pal::char_t *app_path, int64_t header_offset); + static bool is_single_file_bundle() { return the_app != nullptr; } + + bool is_netcoreapp3_compat_mode() const { return m_header.is_netcoreapp3_compat_mode(); } + const pal::string_t& base_path() const { return m_base_path; } + int64_t header_offset() const { return m_header_offset; } + + // Global single-file info object + static const info_t* the_app; + + protected: + info_t(const pal::char_t* bundle_path, + const pal::char_t* app_path, + int64_t header_offset); + + const char* map_bundle(); + void unmap_bundle(const char* addr) const; + + pal::string_t m_bundle_path; + pal::string_t m_base_path; + size_t m_bundle_size; + int64_t m_header_offset; + header_t m_header; + config_t m_deps_json; + config_t m_runtimeconfig_json; + + private: + StatusCode process_header(); + }; +} +#endif // __INFO_H_ diff --git a/src/installer/corehost/cli/apphost/bundle/manifest.cpp b/src/installer/corehost/cli/bundle/manifest.cpp similarity index 71% rename from src/installer/corehost/cli/apphost/bundle/manifest.cpp rename to src/installer/corehost/cli/bundle/manifest.cpp index 6de65d3..55f1ff1 100644 --- a/src/installer/corehost/cli/apphost/bundle/manifest.cpp +++ b/src/installer/corehost/cli/bundle/manifest.cpp @@ -12,7 +12,9 @@ manifest_t manifest_t::read(reader_t& reader, int32_t num_files) for (int32_t i = 0; i < num_files; i++) { - manifest.files.emplace_back(file_entry_t::read(reader)); + file_entry_t entry = file_entry_t::read(reader); + manifest.files.push_back(std::move(entry)); + manifest.m_need_extraction |= entry.needs_extraction(); } return manifest; diff --git a/src/installer/corehost/cli/apphost/bundle/manifest.h b/src/installer/corehost/cli/bundle/manifest.h similarity index 76% rename from src/installer/corehost/cli/apphost/bundle/manifest.h rename to src/installer/corehost/cli/bundle/manifest.h index ee8cd1e..af1f9fa 100644 --- a/src/installer/corehost/cli/apphost/bundle/manifest.h +++ b/src/installer/corehost/cli/bundle/manifest.h @@ -16,9 +16,17 @@ namespace bundle class manifest_t { public: + manifest_t() + : m_need_extraction(false) {} + std::vector files; static manifest_t read(reader_t &reader, int32_t num_files); + + bool files_need_extraction() { return m_need_extraction; } + + private: + bool m_need_extraction; }; } #endif // __MANIFEST_H__ diff --git a/src/installer/corehost/cli/apphost/bundle/reader.cpp b/src/installer/corehost/cli/bundle/reader.cpp similarity index 88% rename from src/installer/corehost/cli/apphost/bundle/reader.cpp rename to src/installer/corehost/cli/bundle/reader.cpp index e2fa9b6..2e5135d 100644 --- a/src/installer/corehost/cli/apphost/bundle/reader.cpp +++ b/src/installer/corehost/cli/bundle/reader.cpp @@ -8,9 +8,9 @@ using namespace bundle; -const int8_t* reader_t::add_without_overflow(const int8_t* ptr, int64_t len) +const char* reader_t::add_without_overflow(const char* ptr, int64_t len) { - const int8_t* new_ptr = ptr + len; + const char* new_ptr = ptr + len; // The following check will fail in case len < 0 (which is also an error while reading) // even if the actual arthmetic didn't overflow. @@ -38,7 +38,7 @@ void reader_t::set_offset(int64_t offset) void reader_t::bounds_check(int64_t len) { - const int8_t* post_read_ptr = add_without_overflow(m_ptr, len); + const char* post_read_ptr = add_without_overflow(m_ptr, len); // It is legal for post_read_ptr == m_bound_ptr after reading the last byte. if (m_ptr < m_base_ptr || post_read_ptr > m_bound_ptr) @@ -88,11 +88,14 @@ size_t reader_t::read_path_length() return length; } -void reader_t::read_path_string(pal::string_t &str) +size_t reader_t::read_path_string(pal::string_t &str) { + const char* start_ptr = m_ptr; size_t size = read_path_length(); std::unique_ptr buffer{ new uint8_t[size + 1] }; read(buffer.get(), size); buffer[size] = 0; // null-terminator pal::clr_palstring(reinterpret_cast(buffer.get()), &str); + + return m_ptr - start_ptr; // This subtraction can't overflow because addition above is bounds_checked } diff --git a/src/installer/corehost/cli/apphost/bundle/reader.h b/src/installer/corehost/cli/bundle/reader.h similarity index 74% rename from src/installer/corehost/cli/apphost/bundle/reader.h rename to src/installer/corehost/cli/bundle/reader.h index 1824ece..8a1212d 100644 --- a/src/installer/corehost/cli/apphost/bundle/reader.h +++ b/src/installer/corehost/cli/bundle/reader.h @@ -13,19 +13,20 @@ namespace bundle // Helper class for reading sequentially from the memory-mapped bundle file. struct reader_t { - reader_t(const int8_t* base_ptr, int64_t bound) + reader_t(const char* base_ptr, int64_t bound, int64_t start_offset = 0) : m_base_ptr(base_ptr) , m_ptr(base_ptr) , m_bound(bound) , m_bound_ptr(add_without_overflow(base_ptr, bound)) { + set_offset(start_offset); } public: void set_offset(int64_t offset); - operator const int8_t*() const + operator const char*() const { return m_ptr; } @@ -46,26 +47,26 @@ namespace bundle // Return a pointer to the requested bytes within the memory-mapped file. // Skip over len bytes. - const int8_t* read_direct(int64_t len) + const char* read_direct(int64_t len) { bounds_check(len); - const int8_t *ptr = m_ptr; + const char *ptr = m_ptr; m_ptr += len; return ptr; } size_t read_path_length(); - void read_path_string(pal::string_t &str); + size_t read_path_string(pal::string_t &str); private: void bounds_check(int64_t len = 1); - static const int8_t* add_without_overflow(const int8_t* ptr, int64_t len); + static const char* add_without_overflow(const char* ptr, int64_t len); - const int8_t* const m_base_ptr; - const int8_t* m_ptr; + const char* const m_base_ptr; + const char* m_ptr; const int64_t m_bound; - const int8_t* const m_bound_ptr; + const char* const m_bound_ptr; }; } diff --git a/src/installer/corehost/cli/bundle/runner.cpp b/src/installer/corehost/cli/bundle/runner.cpp new file mode 100644 index 0000000..58d10a8 --- /dev/null +++ b/src/installer/corehost/cli/bundle/runner.cpp @@ -0,0 +1,83 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include +#include "extractor.h" +#include "runner.h" +#include "trace.h" +#include "header.h" +#include "manifest.h" +#include "utils.h" + +using namespace bundle; + +// This method processes the bundle manifest. +// It also implements the extraction of files that cannot be directly processed from the bundle. +StatusCode runner_t::extract() +{ + try + { + const char* addr = map_bundle(); + + // Set the Reader at header_offset + reader_t reader(addr, m_bundle_size, m_header_offset); + + // Read the bundle header + m_header = header_t::read(reader); + m_deps_json.set_location(&m_header.deps_json_location()); + m_runtimeconfig_json.set_location(&m_header.runtimeconfig_json_location()); + + // Read the bundle manifest + m_manifest = manifest_t::read(reader, m_header.num_embedded_files()); + + // Extract the files if necessary + if (m_manifest.files_need_extraction()) + { + extractor_t extractor(m_header.bundle_id(), m_bundle_path, m_manifest); + m_extraction_path = extractor.extract(reader); + } + + unmap_bundle(addr); + + return StatusCode::Success; + } + catch (StatusCode e) + { + return e; + } +} + +const file_entry_t* runner_t::probe(const pal::string_t& path) const +{ + for (const file_entry_t& entry : m_manifest.files) + { + if (entry.relative_path() == path) + { + return &entry; + } + } + + return nullptr; +} + +bool runner_t::locate(const pal::string_t& relative_path, pal::string_t& full_path) const +{ + const bundle::runner_t* app = bundle::runner_t::app(); + const bundle::file_entry_t* entry = app->probe(relative_path); + + if (entry == nullptr) + { + full_path.clear(); + return false; + } + + // Currently, all files except deps.json and runtimeconfig.json are extracted to disk. + // The json files are not queried by the host using this method. + assert(entry->needs_extraction()); + + full_path.assign(app->extraction_path()); + append_path(&full_path, relative_path.c_str()); + + return true; +} diff --git a/src/installer/corehost/cli/bundle/runner.h b/src/installer/corehost/cli/bundle/runner.h new file mode 100644 index 0000000..fb4d74b --- /dev/null +++ b/src/installer/corehost/cli/bundle/runner.h @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef __RUNNER_H__ +#define __RUNNER_H__ + +#include "error_codes.h" +#include "header.h" +#include "manifest.h" +#include "info.h" + +// bundle::runner extends bundle::info to supports: +// * Reading the bundle manifest and identifying file locations for the runtime +// * Extracting bundled files to disk when necessary +// bundle::runner is used by HostPolicy. + +namespace bundle +{ + class runner_t : public info_t + { + public: + runner_t(const pal::char_t* bundle_path, + const pal::char_t *app_path, + int64_t header_offset) + : info_t(bundle_path, app_path, header_offset) {} + + const pal::string_t& extraction_path() const { return m_extraction_path; } + + const file_entry_t *probe(const pal::string_t& path) const; + bool locate(const pal::string_t& relative_path, pal::string_t& full_path) const; + + static StatusCode process_manifest_and_extract() + { + return ((runner_t*) the_app)->extract(); + } + + static const runner_t* app() { return (const runner_t*)the_app; } + + private: + + StatusCode extract(); + + manifest_t m_manifest; + pal::string_t m_extraction_path; + }; +} + +#endif // __RUNNER_H__ diff --git a/src/installer/corehost/cli/deps_entry.cpp b/src/installer/corehost/cli/deps_entry.cpp index 08d196c..449403f 100644 --- a/src/installer/corehost/cli/deps_entry.cpp +++ b/src/installer/corehost/cli/deps_entry.cpp @@ -6,9 +6,39 @@ #include "utils.h" #include "deps_entry.h" #include "trace.h" +#include "bundle/runner.h" +static pal::string_t normalize_dir_separator(const pal::string_t& path) +{ + // Entry relative path contains '/' separator, sanitize it to use + // platform separator. Perf: avoid extra copy if it matters. + pal::string_t normalized_path = path; + if (_X('/') != DIR_SEPARATOR) + { + replace_char(&normalized_path, _X('/'), DIR_SEPARATOR); + } + + return normalized_path; +} -bool deps_entry_t::to_path(const pal::string_t& base, bool look_in_base, pal::string_t* str) const +// ----------------------------------------------------------------------------- +// Given a "base" directory, determine the resolved path for this file. +// +// * If this file exists within the single-file bundle candidate is +// the full-path to the extracted file. +// * Otherwise, candidate is the full local path of the file. +// +// Parameters: +// base - The base directory to look for the relative path of this entry +// ietf_dir - If this is a resource asset, the IETF intermediate directory +// look_in_base - Whether to search as a relative path +// look_in_bundle - Whether to look within the single-file bundle +// str - If the method returns true, contains the file path for this deps entry +// +// Returns: +// If the file exists in the path relative to the "base" directory within the +// single-file or on disk. +bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_dir, bool look_in_base, bool look_in_bundle, pal::string_t* str) const { pal::string_t& candidate = *str; @@ -20,20 +50,41 @@ bool deps_entry_t::to_path(const pal::string_t& base, bool look_in_base, pal::st return false; } - // Entry relative path contains '/' separator, sanitize it to use - // platform separator. Perf: avoid extra copy if it matters. - pal::string_t pal_relative_path = asset.relative_path; - if (_X('/') != DIR_SEPARATOR) - { - replace_char(&pal_relative_path, _X('/'), DIR_SEPARATOR); - } + pal::string_t normalized_path = normalize_dir_separator(asset.relative_path); // Reserve space for the path below - candidate.reserve(base.length() + - pal_relative_path.length() + 3); + candidate.reserve(base.length() + ietf_dir.length() + normalized_path.length() + 3); + + pal::string_t file_path = look_in_base ? get_filename(normalized_path) : normalized_path; + pal::string_t sub_path = ietf_dir; + append_path(&sub_path, file_path.c_str()); + + if (look_in_bundle && bundle::info_t::is_single_file_bundle()) + { + const bundle::runner_t* app = bundle::runner_t::app(); + + if (base.compare(app->base_path()) == 0) + { + // If sub_path is found in the single-file bundle, + // app::locate() will set candidate to the full-path to the assembly extracted out to disk. + if (app->locate(sub_path, candidate)) + { + trace::verbose(_X(" %s found in bundle [%s]"), sub_path.c_str(), candidate.c_str()); + return true; + } + else + { + trace::verbose(_X(" %s not found in bundle"), sub_path.c_str()); + } + } + else + { + trace::verbose(_X(" %s not searched in bundle base path %s doesn't match bundle base %s."), + sub_path.c_str(), base.c_str(), app->base_path().c_str()); + } + } candidate.assign(base); - pal::string_t sub_path = look_in_base ? get_filename(pal_relative_path) : pal_relative_path; append_path(&candidate, sub_path.c_str()); bool exists = pal::file_exists(candidate); @@ -47,6 +98,7 @@ bool deps_entry_t::to_path(const pal::string_t& base, bool look_in_base, pal::st { trace::verbose(_X(" %s path query exists %s"), query_type, candidate.c_str()); } + return exists; } @@ -55,55 +107,50 @@ bool deps_entry_t::to_path(const pal::string_t& base, bool look_in_base, pal::st // // Parameters: // base - The base directory to look for the relative path of this entry -// str - If the method returns true, contains the file path for this deps -// entry relative to the "base" directory +// str - If the method returns true, contains the file path for this deps entry // // Returns: // If the file exists in the path relative to the "base" directory. // -bool deps_entry_t::to_dir_path(const pal::string_t& base, pal::string_t* str) const +bool deps_entry_t::to_dir_path(const pal::string_t& base, bool look_in_bundle, pal::string_t* str) const { + pal::string_t ietf_dir; + if (asset_type == asset_types::resources) { - pal::string_t pal_relative_path = asset.relative_path; - if (_X('/') != DIR_SEPARATOR) - { - replace_char(&pal_relative_path, _X('/'), DIR_SEPARATOR); - } + pal::string_t pal_relative_path = normalize_dir_separator(asset.relative_path); // Resources are represented as "lib///" in the deps.json. // The is the "directory" in the pal_relative_path below, so extract it. - pal::string_t ietf_dir = get_directory(pal_relative_path); - pal::string_t ietf = ietf_dir; + ietf_dir = get_directory(pal_relative_path); // get_directory returns with DIR_SEPARATOR appended that we need to remove. - remove_trailing_dir_seperator(&ietf); + remove_trailing_dir_seperator(&ietf_dir); // Extract IETF code from "lib//" - ietf = get_filename(ietf); - - pal::string_t base_ietf_dir = base; - append_path(&base_ietf_dir, ietf.c_str()); - trace::verbose(_X("Detected a resource asset, will query dir/ietf-tag/resource base: %s asset: %s"), base_ietf_dir.c_str(), asset.name.c_str()); - return to_path(base_ietf_dir, true, str); + ietf_dir = get_filename(ietf_dir); + + trace::verbose(_X("Detected a resource asset, will query dir/ietf-tag/resource base: %s ietf: %s asset: %s"), + base.c_str(), ietf_dir.c_str(), asset.name.c_str()); } - return to_path(base, true, str); + + return to_path(base, ietf_dir, true, look_in_bundle, str); } + // ----------------------------------------------------------------------------- // Given a "base" directory, yield the relative path of this file in the package // layout. // // Parameters: // base - The base directory to look for the relative path of this entry -// str - If the method returns true, contains the file path for this deps -// entry relative to the "base" directory +// str - If the method returns true, contains the file path for this deps entry // // Returns: // If the file exists in the path relative to the "base" directory. // -bool deps_entry_t::to_rel_path(const pal::string_t& base, pal::string_t* str) const +bool deps_entry_t::to_rel_path(const pal::string_t& base, bool look_in_bundle, pal::string_t* str) const { - return to_path(base, false, str); + return to_path(base, _X(""), false, look_in_bundle, str); } // ----------------------------------------------------------------------------- @@ -112,8 +159,7 @@ bool deps_entry_t::to_rel_path(const pal::string_t& base, pal::string_t* str) co // // Parameters: // base - The base directory to look for the relative path of this entry -// str - If the method returns true, contains the file path for this deps -// entry relative to the "base" directory +// str - If the method returns true, contains the file path for this deps entry // // Returns: // If the file exists in the path relative to the "base" directory. @@ -140,5 +186,5 @@ bool deps_entry_t::to_full_path(const pal::string_t& base, pal::string_t* str) c append_path(&new_base, library_path.c_str()); } - return to_rel_path(new_base, str); + return to_rel_path(new_base, false, str); } diff --git a/src/installer/corehost/cli/deps_entry.h b/src/installer/corehost/cli/deps_entry.h index d06e298..2c66b1f 100644 --- a/src/installer/corehost/cli/deps_entry.h +++ b/src/installer/corehost/cli/deps_entry.h @@ -52,17 +52,19 @@ struct deps_entry_t bool is_serviceable; bool is_rid_specific; - // Given a "base" dir, yield the filepath within this directory or relative to this directory based on "look_in_base" - bool to_path(const pal::string_t& base, bool look_in_base, pal::string_t* str) const; - - // Given a "base" dir, yield the file path within this directory. - bool to_dir_path(const pal::string_t& base, pal::string_t* str) const; + // Given a "base" dir, yield the file path within this directory or single-file bundle. + bool to_dir_path(const pal::string_t& base, bool look_in_bundle, pal::string_t* str) const; // Given a "base" dir, yield the relative path in the package layout. - bool to_rel_path(const pal::string_t& base, pal::string_t* str) const; + bool to_rel_path(const pal::string_t& base, bool look_in_bundle, pal::string_t* str) const; // Given a "base" dir, yield the relative path with package name, version in the package layout. bool to_full_path(const pal::string_t& root, pal::string_t* str) const; + +private: + // Given a "base" dir, yield the filepath within this directory or relative to this directory based on "look_in_base" + // Returns a path within the single-file bundle, or a file on disk, + bool to_path(const pal::string_t& base, const pal::string_t& ietf_code, bool look_in_base, bool look_in_bundle, pal::string_t* str) const; }; #endif // __DEPS_ENTRY_H_ diff --git a/src/installer/corehost/cli/deps_format.cpp b/src/installer/corehost/cli/deps_format.cpp index b098d95..873dd81 100644 --- a/src/installer/corehost/cli/deps_format.cpp +++ b/src/installer/corehost/cli/deps_format.cpp @@ -6,6 +6,7 @@ #include "deps_format.h" #include "utils.h" #include "trace.h" +#include "bundle/info.h" #include #include #include @@ -426,16 +427,16 @@ bool deps_json_t::has_package(const pal::string_t& name, const pal::string_t& ve 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); + m_file_exists = bundle::info_t::config_t::probe(deps_path) || pal::file_exists(deps_path); - // If file doesn't exist, then assume parsed. + json_parser_t json; if (!m_file_exists) { + // If file doesn't exist, then assume parsed. trace::verbose(_X("Could not locate the dependencies manifest file [%s]. Some libraries may fail to resolve."), deps_path.c_str()); return true; } - json_parser_t json; if (!json.parse_file(deps_path)) { return false; diff --git a/src/installer/corehost/cli/fxr/command_line.cpp b/src/installer/corehost/cli/fxr/command_line.cpp index e356394..045f770 100644 --- a/src/installer/corehost/cli/fxr/command_line.cpp +++ b/src/installer/corehost/cli/fxr/command_line.cpp @@ -9,6 +9,7 @@ #include "sdk_info.h" #include #include +#include "bundle/info.h" namespace { @@ -144,7 +145,7 @@ namespace if (mode == host_mode_t::apphost) { app_candidate = host_info.app_path; - doesAppExist = pal::realpath(&app_candidate); + doesAppExist = bundle::info_t::is_single_file_bundle() || pal::realpath(&app_candidate); } else { diff --git a/src/installer/corehost/cli/fxr/corehost_init.cpp b/src/installer/corehost/cli/fxr/corehost_init.cpp index 22a3921..d8a5f24 100644 --- a/src/installer/corehost/cli/fxr/corehost_init.cpp +++ b/src/installer/corehost/cli/fxr/corehost_init.cpp @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. #include "corehost_init.h" +#include "bundle/info.h" void make_cstr_arr(const std::vector& arr, std::vector* out) { @@ -132,6 +133,8 @@ const host_interface_t& corehost_init_t::get_host_init_data() hi.host_info_dotnet_root = m_host_info_dotnet_root.c_str(); hi.host_info_app_path = m_host_info_app_path.c_str(); + hi.single_file_bundle_header_offset = bundle::info_t::is_single_file_bundle() ? bundle::info_t::the_app->header_offset() : 0; + return hi; } diff --git a/src/installer/corehost/cli/fxr/corehost_init.h b/src/installer/corehost/cli/fxr/corehost_init.h index eedc2e7..0d7f87a 100644 --- a/src/installer/corehost/cli/fxr/corehost_init.h +++ b/src/installer/corehost/cli/fxr/corehost_init.h @@ -8,7 +8,7 @@ #include "host_interface.h" #include "host_startup_info.h" #include "fx_definition.h" - + class corehost_init_t { private: @@ -37,6 +37,7 @@ private: 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, diff --git a/src/installer/corehost/cli/fxr/fx_muxer.cpp b/src/installer/corehost/cli/fxr/fx_muxer.cpp index 00584e4..31c8c17 100644 --- a/src/installer/corehost/cli/fxr/fx_muxer.cpp +++ b/src/installer/corehost/cli/fxr/fx_muxer.cpp @@ -27,6 +27,7 @@ #include "sdk_info.h" #include "sdk_resolver.h" #include "roll_fwd_on_no_candidate_fx_option.h" +#include "bundle/info.h" namespace { @@ -262,6 +263,7 @@ namespace pal::string_t& runtime_config, const runtime_config_t::settings_t& override_settings) { + // Check for the runtimeconfig.json file specified at the command line if (!runtime_config.empty() && !pal::realpath(&runtime_config)) { trace::error(_X("The specified runtimeconfig.json [%s] does not exist"), runtime_config.c_str()); @@ -293,6 +295,11 @@ namespace host_mode_t detect_operating_mode(const host_startup_info_t& host_info) { + if (bundle::info_t::is_single_file_bundle()) + { + return host_mode_t::apphost; + } + if (coreclr_exists_in_dir(host_info.dotnet_root)) { // Detect between standalone apphost or legacy split mode (specifying --depsfile and --runtimeconfig) @@ -357,6 +364,7 @@ namespace { pal::string_t runtime_config = command_line::get_option_value(opts, known_options::runtime_config, _X("")); + // This check is for --depsfile option, which must be an actual file. pal::string_t deps_file = command_line::get_option_value(opts, known_options::deps_file, _X("")); if (!deps_file.empty() && !pal::realpath(&deps_file)) { diff --git a/src/installer/corehost/cli/fxr/hostfxr.cpp b/src/installer/corehost/cli/fxr/hostfxr.cpp index c9a346b..1fdfa21 100644 --- a/src/installer/corehost/cli/fxr/hostfxr.cpp +++ b/src/installer/corehost/cli/fxr/hostfxr.cpp @@ -14,6 +14,7 @@ #include "sdk_resolver.h" #include "hostfxr.h" #include "host_context.h" +#include "bundle/info.h" namespace { @@ -24,6 +25,23 @@ namespace } } +SHARED_API int HOSTFXR_CALLTYPE hostfxr_main_bundle_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, int64_t bundle_header_offset) +{ + trace_hostfxr_entry_point(_X("hostfxr_main_bundle_startupinfo")); + + StatusCode bundleStatus = bundle::info_t::process_bundle(host_path, app_path, bundle_header_offset); + if (bundleStatus != StatusCode::Success) + { + trace::error(_X("A fatal error occured while processing application bundle")); + return bundleStatus; + } + + host_startup_info_t startup_info(host_path, dotnet_root, app_path); + + return fx_muxer_t::execute(pal::string_t(), argc, argv, startup_info, nullptr, 0, nullptr); +} + + SHARED_API int HOSTFXR_CALLTYPE 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_hostfxr_entry_point(_X("hostfxr_main_startupinfo")); diff --git a/src/installer/corehost/cli/fxr/hostpolicy_resolver.cpp b/src/installer/corehost/cli/fxr/hostpolicy_resolver.cpp index 049d61a..de3291f 100644 --- a/src/installer/corehost/cli/fxr/hostpolicy_resolver.cpp +++ b/src/installer/corehost/cli/fxr/hostpolicy_resolver.cpp @@ -29,7 +29,6 @@ namespace trace::verbose(_X("--- Resolving %s version from deps json [%s]"), LIBHOSTPOLICY_NAME, deps_json.c_str()); pal::string_t retval; - json_parser_t json; if (!json.parse_file(deps_json)) { diff --git a/src/installer/corehost/cli/host_interface.h b/src/installer/corehost/cli/host_interface.h index c7148e0..72295ae 100644 --- a/src/installer/corehost/cli/host_interface.h +++ b/src/installer/corehost/cli/host_interface.h @@ -7,6 +7,7 @@ #include #include "pal.h" +#include "bundle/info.h" enum host_mode_t { @@ -58,6 +59,7 @@ struct host_interface_t const pal::char_t* host_info_host_path; const pal::char_t* host_info_dotnet_root; const pal::char_t* host_info_app_path; + size_t single_file_bundle_header_offset; // !! 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) @@ -91,7 +93,8 @@ static_assert(offsetof(host_interface_t, host_command) == 26 * sizeof(size_t), " 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?"); +static_assert(offsetof(host_interface_t, single_file_bundle_header_offset) == 30 * sizeof(size_t), "Struct offset breaks backwards compatibility"); +static_assert(sizeof(host_interface_t) == 31 * 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) diff --git a/src/installer/corehost/cli/host_startup_info.h b/src/installer/corehost/cli/host_startup_info.h index 4cb4b3e..cdc2746 100644 --- a/src/installer/corehost/cli/host_startup_info.h +++ b/src/installer/corehost/cli/host_startup_info.h @@ -11,6 +11,7 @@ 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, diff --git a/src/installer/corehost/cli/hostcommon/CMakeLists.txt b/src/installer/corehost/cli/hostcommon/CMakeLists.txt index deac261..8ad0c8d 100644 --- a/src/installer/corehost/cli/hostcommon/CMakeLists.txt +++ b/src/installer/corehost/cli/hostcommon/CMakeLists.txt @@ -23,6 +23,9 @@ set(SOURCES ../version.cpp ../version_compatibility_range.cpp ../runtime_config.cpp + ../bundle/info.cpp + ../bundle/reader.cpp + ../bundle/header.cpp ) set(HEADERS @@ -37,6 +40,9 @@ set(HEADERS ../version.h ../version_compatibility_range.h ../runtime_config.h + ../bundle/info.h + ../bundle/reader.h + ../bundle/header.h ) set(SKIP_VERSIONING 1) diff --git a/src/installer/corehost/cli/hostfxr.h b/src/installer/corehost/cli/hostfxr.h index 040b7c7..f0bc0a5 100644 --- a/src/installer/corehost/cli/hostfxr.h +++ b/src/installer/corehost/cli/hostfxr.h @@ -37,6 +37,13 @@ typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_startupinfo_fn)( const char_t *host_path, const char_t *dotnet_root, const char_t *app_path); +typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_main_bundle_startupinfo_fn)( + const int argc, + const char_t** argv, + const char_t* host_path, + const char_t* dotnet_root, + const char_t* app_path, + int64_t bundle_header_offset); typedef void(HOSTFXR_CALLTYPE *hostfxr_error_writer_fn)(const char_t *message); typedef hostfxr_error_writer_fn(HOSTFXR_CALLTYPE *hostfxr_set_error_writer_fn)(hostfxr_error_writer_fn error_writer); diff --git a/src/installer/corehost/cli/hostmisc/pal.h b/src/installer/corehost/cli/hostmisc/pal.h index 432756a..10cb0e2 100644 --- a/src/installer/corehost/cli/hostmisc/pal.h +++ b/src/installer/corehost/cli/hostmisc/pal.h @@ -165,7 +165,7 @@ namespace pal inline bool rmdir (const char_t* path) { return RemoveDirectoryW(path) != 0; } inline int rename(const char_t* old_name, const char_t* new_name) { return ::_wrename(old_name, new_name); } inline int remove(const char_t* path) { return ::_wremove(path); } - inline bool unmap_file(void* addr, size_t length) { return UnmapViewOfFile(addr) != 0; } + inline bool munmap(void* addr, size_t length) { return UnmapViewOfFile(addr) != 0; } inline int get_pid() { return GetCurrentProcessId(); } inline void sleep(uint32_t milliseconds) { Sleep(milliseconds); } #else @@ -222,7 +222,7 @@ namespace pal inline bool rmdir(const char_t* path) { return ::rmdir(path) == 0; } inline int rename(const char_t* old_name, const char_t* new_name) { return ::rename(old_name, new_name); } inline int remove(const char_t* path) { return ::remove(path); } - inline bool unmap_file(void* addr, size_t length) { return munmap(addr, length) == 0; } + inline bool munmap(void* addr, size_t length) { return ::munmap(addr, length) == 0; } inline int get_pid() { return getpid(); } inline void sleep(uint32_t milliseconds) { usleep(milliseconds * 1000); } @@ -257,7 +257,9 @@ namespace pal return fallbackRid; } - void* map_file_readonly(const string_t& path, size_t& length); + const void* mmap_read(const string_t& path, size_t* length = nullptr); + void* mmap_copy_on_write(const string_t& path, size_t* length = nullptr); + bool touch_file(const string_t& path); bool realpath(string_t* path, bool skip_error_logging = false); bool file_exists(const string_t& path); diff --git a/src/installer/corehost/cli/hostmisc/pal.unix.cpp b/src/installer/corehost/cli/hostmisc/pal.unix.cpp index 4b637e4..65338b6 100644 --- a/src/installer/corehost/cli/hostmisc/pal.unix.cpp +++ b/src/installer/corehost/cli/hostmisc/pal.unix.cpp @@ -66,7 +66,7 @@ bool pal::touch_file(const pal::string_t& path) return true; } -void* pal::map_file_readonly(const pal::string_t& path, size_t& length) +static void* map_file(const pal::string_t& path, size_t* length, int prot, int flags) { int fd = open(path.c_str(), O_RDONLY); if (fd == -1) @@ -82,21 +82,35 @@ void* pal::map_file_readonly(const pal::string_t& path, size_t& length) close(fd); return nullptr; } + size_t size = buf.st_size; - length = buf.st_size; - void* address = mmap(nullptr, length, PROT_READ, MAP_SHARED, fd, 0); + if (length != nullptr) + { + *length = size; + } + + void* address = mmap(nullptr, size, prot, flags, fd, 0); - if(address == nullptr) + if (address == MAP_FAILED) { trace::error(_X("Failed to map file. mmap(%s) failed with error %d"), path.c_str(), errno); - close(fd); - return nullptr; + address = nullptr; } close(fd); return address; } +const void* pal::mmap_read(const string_t& path, size_t* length) +{ + return map_file(path, length, PROT_READ, MAP_SHARED); +} + +void* pal::mmap_copy_on_write(const string_t& path, size_t* length) +{ + return map_file(path, length, PROT_READ | PROT_WRITE, MAP_PRIVATE); +} + bool pal::getcwd(pal::string_t* recv) { recv->clear(); @@ -486,10 +500,10 @@ bool pal::get_default_installation_dir(pal::string_t* recv) pal::string_t trim_quotes(pal::string_t stringToCleanup) { pal::char_t quote_array[2] = {'\"', '\''}; - for(size_t index = 0; index < sizeof(quote_array)/sizeof(quote_array[0]); index++) + for (size_t index = 0; index < sizeof(quote_array)/sizeof(quote_array[0]); index++) { size_t pos = stringToCleanup.find(quote_array[index]); - while(pos != std::string::npos) + while (pos != std::string::npos) { stringToCleanup = stringToCleanup.erase(pos, 1); pos = stringToCleanup.find(quote_array[index]); diff --git a/src/installer/corehost/cli/hostmisc/pal.windows.cpp b/src/installer/corehost/cli/hostmisc/pal.windows.cpp index 0cb6d2f..f8348a0 100644 --- a/src/installer/corehost/cli/hostmisc/pal.windows.cpp +++ b/src/installer/corehost/cli/hostmisc/pal.windows.cpp @@ -76,7 +76,7 @@ bool pal::touch_file(const pal::string_t& path) return true; } -void* pal::map_file_readonly(const pal::string_t& path, size_t &length) +static void* map_file(const pal::string_t& path, size_t *length, DWORD mapping_protect, DWORD view_desired_access) { HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); @@ -86,16 +86,19 @@ void* pal::map_file_readonly(const pal::string_t& path, size_t &length) return nullptr; } - LARGE_INTEGER fileSize; - if (GetFileSizeEx(file, &fileSize) == 0) + if (length != nullptr) { - trace::error(_X("Failed to map file. GetFileSizeEx(%s) failed with error %d"), path.c_str(), GetLastError()); - CloseHandle(file); - return nullptr; + LARGE_INTEGER fileSize; + if (GetFileSizeEx(file, &fileSize) == 0) + { + trace::error(_X("Failed to map file. GetFileSizeEx(%s) failed with error %d"), path.c_str(), GetLastError()); + CloseHandle(file); + return nullptr; + } + *length = (size_t)fileSize.QuadPart; } - length = (size_t)fileSize.QuadPart; - HANDLE map = CreateFileMappingW(file, NULL, PAGE_READONLY, 0, 0, NULL); + HANDLE map = CreateFileMappingW(file, NULL, mapping_protect, 0, 0, NULL); if (map == NULL) { @@ -104,7 +107,7 @@ void* pal::map_file_readonly(const pal::string_t& path, size_t &length) return nullptr; } - void *address = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0); + void *address = MapViewOfFile(map, view_desired_access, 0, 0, 0); if (address == NULL) { @@ -120,6 +123,16 @@ void* pal::map_file_readonly(const pal::string_t& path, size_t &length) return address; } +const void* pal::mmap_read(const string_t& path, size_t* length) +{ + return map_file(path, length, PAGE_READONLY, FILE_MAP_READ); +} + +void* pal::mmap_copy_on_write(const string_t& path, size_t* length) +{ + return map_file(path, length, PAGE_WRITECOPY, FILE_MAP_READ | FILE_MAP_COPY); +} + bool pal::getcwd(pal::string_t* recv) { recv->clear(); diff --git a/src/installer/corehost/cli/hostmisc/utils.cpp b/src/installer/corehost/cli/hostmisc/utils.cpp index 4e0d8ab..4a3208a 100644 --- a/src/installer/corehost/cli/hostmisc/utils.cpp +++ b/src/installer/corehost/cli/hostmisc/utils.cpp @@ -4,6 +4,7 @@ #include "utils.h" #include "trace.h" +#include "bundle/info.h" bool library_exists_in_dir(const pal::string_t& lib_dir, const pal::string_t& lib_name, pal::string_t* p_lib_path) { @@ -365,6 +366,7 @@ pal::string_t get_deps_from_app_binary(const pal::string_t& app_base, const pal: { pal::string_t deps_file; auto app_name = get_filename(app); + deps_file.reserve(app_base.length() + 1 + app_name.length() + 5); deps_file.append(app_base); @@ -377,19 +379,28 @@ pal::string_t get_deps_from_app_binary(const pal::string_t& app_base, const pal: return deps_file; } -void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& name, pal::string_t* cfg, pal::string_t* dev_cfg) +pal::string_t get_runtime_config_path(const pal::string_t& path, const pal::string_t& name) { auto json_path = path; auto json_name = name + _X(".runtimeconfig.json"); append_path(&json_path, json_name.c_str()); - cfg->assign(json_path); + return json_path; +} +pal::string_t get_runtime_config_dev_path(const pal::string_t& path, const pal::string_t& name) +{ auto dev_json_path = path; auto dev_json_name = name + _X(".runtimeconfig.dev.json"); append_path(&dev_json_path, dev_json_name.c_str()); - dev_cfg->assign(dev_json_path); + return dev_json_path; +} + +void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& name, pal::string_t* cfg, pal::string_t* dev_cfg) +{ + cfg->assign(get_runtime_config_path(path, name)); + dev_cfg->assign(get_runtime_config_dev_path(path, name)); - trace::verbose(_X("Runtime config is cfg=%s dev=%s"), json_path.c_str(), dev_json_path.c_str()); + trace::verbose(_X("Runtime config is cfg=%s dev=%s"), cfg->c_str(), dev_cfg->c_str()); } pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t &fxr_path) diff --git a/src/installer/corehost/cli/hostmisc/utils.h b/src/installer/corehost/cli/hostmisc/utils.h index 2f13193..7de346c 100644 --- a/src/installer/corehost/cli/hostmisc/utils.h +++ b/src/installer/corehost/cli/hostmisc/utils.h @@ -45,6 +45,8 @@ 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(); pal::string_t get_deps_from_app_binary(const pal::string_t& app_base, const pal::string_t& app); +pal::string_t get_runtime_config_path(const pal::string_t& path, const pal::string_t& name); +pal::string_t get_runtime_config_dev_path(const pal::string_t& path, const pal::string_t& name); void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& name, pal::string_t* cfg, pal::string_t* dev_cfg); pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t &fxr_path); diff --git a/src/installer/corehost/cli/hostpolicy/CMakeLists.txt b/src/installer/corehost/cli/hostpolicy/CMakeLists.txt index c05c876..ca2c78f 100644 --- a/src/installer/corehost/cli/hostpolicy/CMakeLists.txt +++ b/src/installer/corehost/cli/hostpolicy/CMakeLists.txt @@ -19,6 +19,11 @@ set(SOURCES ./hostpolicy_context.cpp ./hostpolicy.cpp ./hostpolicy_init.cpp + ../bundle/dir_utils.cpp + ../bundle/extractor.cpp + ../bundle/file_entry.cpp + ../bundle/manifest.cpp + ../bundle/runner.cpp ) set(HEADERS @@ -30,6 +35,11 @@ set(HEADERS ./hostpolicy_context.h ../hostpolicy.h ./hostpolicy_init.h + ../bundle/dir_utils.h + ../bundle/extractor.h + ../bundle/file_entry.h + ../bundle/manifest.h + ../bundle/runner.h ) include(../lib.cmake) diff --git a/src/installer/corehost/cli/hostpolicy/args.cpp b/src/installer/corehost/cli/hostpolicy/args.cpp index 562f70b..f022d23 100644 --- a/src/installer/corehost/cli/hostpolicy/args.cpp +++ b/src/installer/corehost/cli/hostpolicy/args.cpp @@ -4,6 +4,7 @@ #include "args.h" #include +#include "bundle/runner.h" arguments_t::arguments_t() : host_mode(host_mode_t::invalid) @@ -101,6 +102,47 @@ bool parse_arguments( args); } +bool set_root_from_app(const pal::string_t& managed_application_path, + arguments_t& args) +{ + args.managed_application = managed_application_path; + + if (args.managed_application.empty()) + { + // Managed app being empty by itself is not a failure. Host may be initialized from a config file. + assert(args.host_mode != host_mode_t::apphost); + return true; + } + + if (bundle::info_t::is_single_file_bundle()) + { + const bundle::runner_t* app = bundle::runner_t::app(); + args.app_root = app->base_path(); + + // Check for the main app within the bundle. + // locate() sets args.managed_application to the full path of the app extracted to disk. + pal::string_t managed_application_name = get_filename(managed_application_path); + if (app->locate(managed_application_name, args.managed_application)) + { + return true; + } + + trace::info(_X("Managed application [%s] not found in single-file bundle"), managed_application_name.c_str()); + + // If the main assembly is not found in the bundle, continue checking on disk + // for very unlikely case where the main app.dll was itself excluded from the app bundle. + return pal::realpath(&args.managed_application); + } + + if (pal::realpath(&args.managed_application)) + { + args.app_root = get_directory(args.managed_application); + return true; + } + + return false; +} + bool init_arguments( const pal::string_t& managed_application_path, const host_startup_info_t& host_info, @@ -115,13 +157,11 @@ bool init_arguments( args.host_path = host_info.host_path; args.additional_deps_serialized = additional_deps_serialized; - args.managed_application = managed_application_path; - if (!args.managed_application.empty() && !pal::realpath(&args.managed_application)) + if (!set_root_from_app(managed_application_path, args)) { trace::error(_X("Failed to locate managed application [%s]"), args.managed_application.c_str()); return false; } - args.app_root = get_directory(args.managed_application); if (!deps_file.empty()) { diff --git a/src/installer/corehost/cli/hostpolicy/deps_resolver.cpp b/src/installer/corehost/cli/hostpolicy/deps_resolver.cpp index 2e245b5..06ededa 100644 --- a/src/installer/corehost/cli/hostpolicy/deps_resolver.cpp +++ b/src/installer/corehost/cli/hostpolicy/deps_resolver.cpp @@ -316,7 +316,7 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str // If the deps json has the package name and version, then someone has already done rid selection and // put the right asset in the dir. So checking just package name and version would suffice. // No need to check further for the exact asset relative sub path. - if (config.probe_deps_json->has_package(entry.library_name, entry.library_version) && entry.to_dir_path(probe_dir, candidate)) + if (config.probe_deps_json->has_package(entry.library_name, entry.library_version) && entry.to_dir_path(probe_dir, false, candidate)) { trace::verbose(_X(" Probed deps json and matched '%s'"), candidate->c_str()); return true; @@ -334,7 +334,7 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str { if (entry.is_rid_specific) { - if (entry.to_rel_path(deps_dir, candidate)) + if (entry.to_rel_path(deps_dir, true, candidate)) { trace::verbose(_X(" Probed deps dir and matched '%s'"), candidate->c_str()); return true; @@ -343,7 +343,7 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str else { // Non-rid assets, lookup in the published dir. - if (entry.to_dir_path(deps_dir, candidate)) + if (entry.to_dir_path(deps_dir, true, candidate)) { trace::verbose(_X(" Probed deps dir and matched '%s'"), candidate->c_str()); return true; diff --git a/src/installer/corehost/cli/hostpolicy/deps_resolver.h b/src/installer/corehost/cli/hostpolicy/deps_resolver.h index d61a78c..f2b27d4 100644 --- a/src/installer/corehost/cli/hostpolicy/deps_resolver.h +++ b/src/installer/corehost/cli/hostpolicy/deps_resolver.h @@ -14,6 +14,7 @@ #include "deps_format.h" #include "deps_entry.h" #include "runtime_config.h" +#include "bundle/runner.h" // Probe paths to be resolved for ordering struct probe_paths_t @@ -185,6 +186,17 @@ public: static const pal::string_t s_empty; return s_empty; } + if (m_host_mode == host_mode_t::apphost) + { + if (bundle::info_t::is_single_file_bundle()) + { + const bundle::runner_t* app = bundle::runner_t::app(); + if (app->is_netcoreapp3_compat_mode()) + { + return app->extraction_path(); + } + } + } return m_app_dir; } diff --git a/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp b/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp index 5c7d766..c2a933e 100644 --- a/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp @@ -18,6 +18,7 @@ #include #include #include "hostpolicy_context.h" +#include "bundle/runner.h" namespace { @@ -354,7 +355,7 @@ int corehost_init( } int corehost_main_init( - hostpolicy_init_t &hostpolicy_init, + hostpolicy_init_t& hostpolicy_init, const int argc, const pal::char_t* argv[], const pal::string_t& location, @@ -367,6 +368,15 @@ int corehost_main_init( hostpolicy_init.host_info.parse(argc, argv); } + if (bundle::info_t::is_single_file_bundle()) + { + StatusCode status = bundle::runner_t::process_manifest_and_extract(); + if (status != StatusCode::Success) + { + return status; + } + } + return corehost_init(hostpolicy_init, argc, argv, location, args); } @@ -434,6 +444,9 @@ int corehost_libhost_init(const hostpolicy_init_t &hostpolicy_init, const pal::s // Host info should always be valid in the delegate scenario assert(hostpolicy_init.host_info.is_valid(host_mode_t::libhost)); + // Single-file bundle is only expected in apphost mode. + assert(!bundle::info_t::is_single_file_bundle()); + return corehost_init(hostpolicy_init, 0, nullptr, location, args); } diff --git a/src/installer/corehost/cli/hostpolicy/hostpolicy_init.cpp b/src/installer/corehost/cli/hostpolicy/hostpolicy_init.cpp index c6103f7..3e0d02a 100644 --- a/src/installer/corehost/cli/hostpolicy/hostpolicy_init.cpp +++ b/src/installer/corehost/cli/hostpolicy/hostpolicy_init.cpp @@ -4,6 +4,7 @@ #include "hostpolicy_init.h" #include +#include "bundle/runner.h" void make_palstr_arr(int argc, const pal::char_t** argv, std::vector* out) { @@ -126,6 +127,15 @@ bool hostpolicy_init_t::init(host_interface_t* input, hostpolicy_init_t* init) // For the backwards compat case, this will be later initialized with argv[0] } + if (input->version_lo >= offsetof(host_interface_t, single_file_bundle_header_offset) + sizeof(input->single_file_bundle_header_offset)) + { + if (input->single_file_bundle_header_offset != 0) + { + static bundle::runner_t bundle_runner(input->host_info_host_path, input->host_info_app_path, input->single_file_bundle_header_offset); + bundle::info_t::the_app = &bundle_runner; + } + } + return true; } diff --git a/src/installer/corehost/cli/hostpolicy/hostpolicy_init.h b/src/installer/corehost/cli/hostpolicy/hostpolicy_init.h index 18af97c..8d2ea61 100644 --- a/src/installer/corehost/cli/hostpolicy/hostpolicy_init.h +++ b/src/installer/corehost/cli/hostpolicy/hostpolicy_init.h @@ -8,6 +8,7 @@ #include "host_interface.h" #include "host_startup_info.h" #include "fx_definition.h" +#include "bundle/info.h" struct hostpolicy_init_t { diff --git a/src/installer/corehost/cli/ijwhost/ijwthunk.cpp b/src/installer/corehost/cli/ijwhost/ijwthunk.cpp index b232911..2b05bcf 100644 --- a/src/installer/corehost/cli/ijwhost/ijwthunk.cpp +++ b/src/installer/corehost/cli/ijwhost/ijwthunk.cpp @@ -73,7 +73,7 @@ bool patch_vtable_entries(PEDecoder& pe) error_writer_scope_t writer_scope(swallow_trace); size_t currentThunk = 0; - for(size_t i = 0; i < numFixupRecords; ++i) + for (size_t i = 0; i < numFixupRecords; ++i) { if (pFixupTable[i].Type & COR_VTABLE_PTRSIZED) { @@ -81,7 +81,7 @@ bool patch_vtable_entries(PEDecoder& pe) #ifdef _WIN64 DWORD oldProtect; - if(!VirtualProtect(pointers, (sizeof(BYTE*) * pFixupTable[i].Count), PAGE_READWRITE, &oldProtect)) + if (!VirtualProtect(pointers, (sizeof(BYTE*) * pFixupTable[i].Count), PAGE_READWRITE, &oldProtect)) { trace::error(_X("Failed to change the vtfixup table from RO to R/W failed.\n")); return false; @@ -101,7 +101,7 @@ bool patch_vtable_entries(PEDecoder& pe) #ifdef _WIN64 DWORD _; - if(!VirtualProtect(pointers, (sizeof(BYTE*) * pFixupTable[i].Count), oldProtect, &_)) + if (!VirtualProtect(pointers, (sizeof(BYTE*) * pFixupTable[i].Count), oldProtect, &_)) { trace::warning(_X("Failed to change the vtfixup table from R/W back to RO failed.\n")); } diff --git a/src/installer/corehost/cli/json/rapidjson/document.h b/src/installer/corehost/cli/json/rapidjson/document.h index 9783fe4..74666e3 100644 --- a/src/installer/corehost/cli/json/rapidjson/document.h +++ b/src/installer/corehost/cli/json/rapidjson/document.h @@ -2094,11 +2094,11 @@ private: const SizeType len1 = GetStringLength(); const SizeType len2 = rhs.GetStringLength(); - if(len1 != len2) { return false; } + if (len1 != len2) { return false; } const Ch* const str1 = GetString(); const Ch* const str2 = rhs.GetString(); - if(str1 == str2) { return true; } // fast path for constant string + if (str1 == str2) { return true; } // fast path for constant string return (std::memcmp(str1, str2, sizeof(Ch) * len1) == 0); } diff --git a/src/installer/corehost/cli/json_parser.cpp b/src/installer/corehost/cli/json_parser.cpp index 9814f32..c6f0cbb 100644 --- a/src/installer/corehost/cli/json_parser.cpp +++ b/src/installer/corehost/cli/json_parser.cpp @@ -42,9 +42,9 @@ std::streampos get_utf8_bom_length(pal::istream_t& stream) return 3; } -void get_line_column_from_offset(const std::vector& json, size_t offset, int *line, int *column) +void get_line_column_from_offset(const char* data, uint64_t size, size_t offset, int *line, int *column) { - assert(offset < json.size()); + assert(offset < size); *line = *column = 1; @@ -52,12 +52,12 @@ void get_line_column_from_offset(const std::vector& json, size_t offset, i { (*column)++; - if (json[i] == '\n') + if (data[i] == '\n') { (*line)++; *column = 1; } - else if (json[i] == '\r' && json[i + 1] == '\n') + else if (data[i] == '\r' && data[i + 1] == '\n') { (*line)++; *column = 1; @@ -75,32 +75,30 @@ void json_parser_t::realloc_buffer(size_t size) m_json[size] = '\0'; } -bool json_parser_t::parse_json(const pal::string_t& context) +bool json_parser_t::parse_json(char* data, int64_t size, const pal::string_t& context) { - assert(!m_json.empty()); - #ifdef _WIN32 // Can't use in-situ parsing on Windows, as JSON data is encoded in // UTF-8 and the host expects wide strings. m_document will store // data in UTF-16 (with pal::char_t as the character type), but it // has to know that data is encoded in UTF-8 to convert during parsing. constexpr auto flags = rapidjson::ParseFlag::kParseStopWhenDoneFlag - | rapidjson::ParseFlag::kParseCommentsFlag; - m_document.Parse>(m_json.data()); -#else - m_document.ParseInsitu(m_json.data()); -#endif + | rapidjson::ParseFlag::kParseCommentsFlag; + m_document.Parse>(data); +#else // _WIN32 + m_document.ParseInsitu(data); +#endif // _WIN32 if (m_document.HasParseError()) { int line, column; size_t offset = m_document.GetErrorOffset(); - get_line_column_from_offset(m_json, offset, &line, &column); + get_line_column_from_offset(data, size, offset, &line, &column); trace::error(_X("A JSON parsing exception occurred in [%s], offset %zu (line %d, column %d): %s"), - context.c_str(), offset, line, column, - rapidjson::GetParseError_En(m_document.GetParseError())); + context.c_str(), offset, line, column, + rapidjson::GetParseError_En(m_document.GetParseError())); return false; } @@ -130,5 +128,37 @@ bool json_parser_t::parse_stream(pal::istream_t& stream, realloc_buffer(stream_size - current_pos); stream.read(m_json.data(), stream_size - current_pos); - return parse_json(context); + return parse_json(m_json.data(), m_json.size(), context); +} + +bool json_parser_t::parse_file(const pal::string_t& path) +{ + // This code assumes that the caller has checked that the file `path` exists + // either within the bundle, or as a real file on disk. + assert(m_bundle_data == nullptr); + assert(m_bundle_location == nullptr); + + if (bundle::info_t::is_single_file_bundle()) + { + m_bundle_data = bundle::info_t::config_t::map(path, m_bundle_location); + // The mapping will be unmapped by the json_parser destructor. + // The mapping cannot be immediately released due to in-situ parsing on Linux. + + if (m_bundle_data != nullptr) + { + bool result = parse_json(m_bundle_data, m_bundle_location->size, path); + return result; + } + } + + pal::ifstream_t file{ path }; + return parse_stream(file, path); +} + +json_parser_t::~json_parser_t() +{ + if (m_bundle_data != nullptr) + { + bundle::info_t::config_t::unmap(m_bundle_data, m_bundle_location); + } } diff --git a/src/installer/corehost/cli/json_parser.h b/src/installer/corehost/cli/json_parser.h index e5c477b..16ed21b 100644 --- a/src/installer/corehost/cli/json_parser.h +++ b/src/installer/corehost/cli/json_parser.h @@ -9,6 +9,7 @@ #include "rapidjson/document.h" #include "rapidjson/fwd.h" #include +#include "bundle/info.h" class json_parser_t { public: @@ -21,12 +22,15 @@ class json_parser_t { using document_t = rapidjson::GenericDocument; const document_t& document() const { return m_document; } + bool parse_stream(pal::istream_t& stream, const pal::string_t& context); - bool parse_file(const pal::string_t& path) - { - pal::ifstream_t file{path}; - return parse_stream(file, path); - } + bool parse_file(const pal::string_t& path); + + json_parser_t() + : m_bundle_data(nullptr) + , m_bundle_location(nullptr) {} + + ~json_parser_t(); private: // This is a vector of char and not pal::char_t because JSON data @@ -36,8 +40,12 @@ class json_parser_t { std::vector m_json; document_t m_document; + // If a json file is parsed from a single-file bundle, the following two fields represent: + char* m_bundle_data; // The memory mapped bytes of the application bundle. + const bundle::location_t* m_bundle_location; // Location of this json file within the bundle. + void realloc_buffer(size_t size); - bool parse_json(const pal::string_t& context); + bool parse_json(char* data, int64_t size, const pal::string_t& context); }; #endif // __JSON_PARSER_H__ diff --git a/src/installer/corehost/cli/runtime_config.cpp b/src/installer/corehost/cli/runtime_config.cpp index 2026da5..d41aa2a 100644 --- a/src/installer/corehost/cli/runtime_config.cpp +++ b/src/installer/corehost/cli/runtime_config.cpp @@ -9,6 +9,7 @@ #include "runtime_config.h" #include "trace.h" #include "utils.h" +#include "bundle/info.h" #include // The semantics of applying the runtimeconfig.json values follows, in the following steps from @@ -338,6 +339,8 @@ bool runtime_config_t::ensure_dev_config_parsed() return true; } + // runtimeconfig.dev.json is never bundled into the single-file app. + // So, only a file on disk is processed. json_parser_t json; if (!json.parse_file(m_dev_path)) { @@ -398,7 +401,7 @@ bool runtime_config_t::ensure_parsed() trace::verbose(_X("Did not successfully parse the runtimeconfig.dev.json")); } - if (!pal::file_exists(m_path)) + if (!bundle::info_t::config_t::probe(m_path) && !pal::file_exists(m_path)) { // Not existing is not an error. return true; diff --git a/src/installer/corehost/cli/test/mockhostpolicy/mockhostpolicy.cpp b/src/installer/corehost/cli/test/mockhostpolicy/mockhostpolicy.cpp index 4a0567f..2130fe7 100644 --- a/src/installer/corehost/cli/test/mockhostpolicy/mockhostpolicy.cpp +++ b/src/installer/corehost/cli/test/mockhostpolicy/mockhostpolicy.cpp @@ -66,6 +66,7 @@ SHARED_API int HOSTPOLICY_CALLTYPE corehost_load(host_interface_t* init) std::cout << "mock host_info_host_path:" << tostr(init->host_info_host_path).data() << std::endl; std::cout << "mock host_info_dotnet_root:" << tostr(init->host_info_dotnet_root).data() << std::endl; std::cout << "mock host_info_app_path:" << tostr(init->host_info_app_path).data() << std::endl; + std::cout << "mock single_file_bundle_header_offset:" << std::hex << init->single_file_bundle_header_offset << std::endl; if (init->fx_names.len == 0) { diff --git a/src/installer/corehost/corehost.cpp b/src/installer/corehost/corehost.cpp index bb6b4ce..55b8ed6 100644 --- a/src/installer/corehost/corehost.cpp +++ b/src/installer/corehost/corehost.cpp @@ -11,8 +11,7 @@ #include "utils.h" #if defined(FEATURE_APPHOST) -#include "cli/apphost/bundle/marker.h" -#include "cli/apphost/bundle/runner.h" +#include "bundle_marker.h" #if defined(_WIN32) #include "cli/apphost/apphost.windows.h" @@ -84,6 +83,14 @@ bool is_exe_enabled_for_execution(pal::string_t* app_dll) #define CURHOST_EXE #endif +void need_newer_framework_error() +{ + pal::string_t url = get_download_url(); + trace::error(_X(" _ To run this application, you need to install a newer version of .NET Core.")); + trace::error(_X("")); + trace::error(_X(" - %s&apphost_version=%s"), url.c_str(), _STRINGIFY(COMMON_HOST_PKG_VER)); +} + #if defined(CURHOST_EXE) int exe_start(const int argc, const pal::char_t* argv[]) @@ -97,8 +104,8 @@ int exe_start(const int argc, const pal::char_t* argv[]) pal::string_t app_path; pal::string_t app_root; - bool requires_v2_hostfxr_interface = false; - + bool requires_hostfxr_startupinfo_interface = false; + #if defined(FEATURE_APPHOST) pal::string_t embedded_app_name; if (!is_exe_enabled_for_execution(&embedded_app_name)) @@ -115,29 +122,17 @@ int exe_start(const int argc, const pal::char_t* argv[]) auto pos_path_char = embedded_app_name.find(DIR_SEPARATOR); if (pos_path_char != pal::string_t::npos) { - requires_v2_hostfxr_interface = true; + requires_hostfxr_startupinfo_interface = true; } - if (bundle::marker_t::is_bundle()) - { - bundle::runner_t bundle_runner(host_path); - StatusCode bundle_status = bundle_runner.extract(); - - if (bundle_status != StatusCode::Success) - { - trace::error(_X("A fatal error was encountered. Could not extract contents of the bundle")); - return bundle_status; - } + app_path.assign(get_directory(host_path)); + append_path(&app_path, embedded_app_name.c_str()); - app_path.assign(bundle_runner.extraction_dir()); - } - else + if (bundle_marker_t::is_bundle()) { - app_path.assign(get_directory(host_path)); + trace::info(_X("Detected Single-File app bundle")); } - - append_path(&app_path, embedded_app_name.c_str()); - if (!pal::realpath(&app_path)) + else if (!pal::realpath(&app_path)) { trace::error(_X("The application to execute does not exist: '%s'."), app_path.c_str()); return StatusCode::LibHostAppRootFindFailure; @@ -200,58 +195,88 @@ int exe_start(const int argc, const pal::char_t* argv[]) // Obtain the entrypoints. int rc; - hostfxr_main_startupinfo_fn main_fn_v2 = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_main_startupinfo")); - if (main_fn_v2 != nullptr) +#if defined(FEATURE_APPHOST) + if (bundle_marker_t::is_bundle()) + { + hostfxr_main_bundle_startupinfo_fn hostfxr_main_bundle_startupinfo = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_main_bundle_startupinfo")); + if (hostfxr_main_bundle_startupinfo != nullptr) + { + 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(); + int64_t bundle_header_offset = bundle_marker_t::header_offset(); + + trace::info(_X("Invoking fx resolver [%s] hostfxr_main_bundle_startupinfo"), 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()); + trace::info(_X("Bundle Header Offset: [%lx]"), bundle_header_offset); + + hostfxr_set_error_writer_fn set_error_writer_fn = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_set_error_writer")); + propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer_fn); + rc = hostfxr_main_bundle_startupinfo(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr, bundle_header_offset); + } + else + { + // The host components will be statically linked with the app-host: https://github.com/dotnet/runtime/issues/32823 + // Once this work is completed, an outdated hostfxr can only be found for framework-related apps. + trace::error(_X("The required library %s does not support single-file apps."), fxr_path.c_str()); + need_newer_framework_error(); + rc = StatusCode::FrameworkMissingFailure; + } + } + else +#endif // defined(FEATURE_APPHOST) { - 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(); + hostfxr_main_startupinfo_fn hostfxr_main_startupinfo = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_main_startupinfo")); + if (hostfxr_main_startupinfo != nullptr) + { + 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()); + trace::info(_X("Invoking fx resolver [%s] hostfxr_main_startupinfo"), 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()); - hostfxr_set_error_writer_fn set_error_writer_fn = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_set_error_writer")); - { + hostfxr_set_error_writer_fn set_error_writer_fn = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_set_error_writer")); propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer_fn); - rc = main_fn_v2(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr); + rc = hostfxr_main_startupinfo(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr); + // This check exists to provide an error message for UI apps when running 3.0 apps on 2.0 only hostfxr, which doesn't support error writer redirection. if (trace::get_error_writer() != nullptr && rc == static_cast(StatusCode::FrameworkMissingFailure) && !set_error_writer_fn) { - pal::string_t url = get_download_url(); - trace::error(_X(" _ To run this application, you need to install a newer version of .NET.")); - trace::error(_X("")); - trace::error(_X(" - %s"), url.c_str()); + need_newer_framework_error(); } } - } - else - { - if (requires_v2_hostfxr_interface) - { - trace::error(_X("The required library %s does not support relative app dll paths."), 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 = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_main")); - if (main_fn_v1 != nullptr) + if (requires_hostfxr_startupinfo_interface) { - rc = main_fn_v1(argc, argv); + trace::error(_X("The required library %s does not support relative app dll paths."), fxr_path.c_str()); + rc = StatusCode::CoreHostEntryPointFailure; } else { - trace::error(_X("The required library %s does not contain the expected entry point."), fxr_path.c_str()); - rc = StatusCode::CoreHostEntryPointFailure; + 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 = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_main")); + if (main_fn_v1 != nullptr) + { + 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; + } } } } diff --git a/src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs b/src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs index 320969c..29e9635 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs +++ b/src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs @@ -90,7 +90,7 @@ namespace Microsoft.NET.HostModel.Bundle return fileRelativePath.Equals(RuntimeConfigDevJson); } - bool ShouldExclude(FileType type) + bool ShouldExclude(FileType type, string relativePath) { switch (type) { @@ -100,7 +100,7 @@ namespace Microsoft.NET.HostModel.Bundle return false; case FileType.NativeBinary: - return !Options.HasFlag(BundleOptions.BundleNativeBinaries); + return !Options.HasFlag(BundleOptions.BundleNativeBinaries) || Target.ShouldExclude(relativePath); case FileType.Symbols: return !Options.HasFlag(BundleOptions.BundleSymbolFiles); @@ -229,22 +229,24 @@ namespace Microsoft.NET.HostModel.Bundle foreach (var fileSpec in fileSpecs) { - if (IsHost(fileSpec.BundleRelativePath)) + string relativePath = fileSpec.BundleRelativePath; + + if (IsHost(relativePath)) { continue; } - if (ShouldIgnore(fileSpec.BundleRelativePath)) + if (ShouldIgnore(relativePath)) { - Tracer.Log($"Ignore: {fileSpec.BundleRelativePath}"); + Tracer.Log($"Ignore: {relativePath}"); continue; } FileType type = InferType(fileSpec); - if (ShouldExclude(type)) + if (ShouldExclude(type, relativePath)) { - Tracer.Log($"Exclude [{type}]: {fileSpec.BundleRelativePath}"); + Tracer.Log($"Exclude [{type}]: {relativePath}"); fileSpec.Excluded = true; continue; } @@ -253,7 +255,7 @@ namespace Microsoft.NET.HostModel.Bundle { FileType targetType = Target.TargetSpecificFileType(type); long startOffset = AddToBundle(bundle, file, targetType); - FileEntry entry = BundleManifest.AddEntry(targetType, fileSpec.BundleRelativePath, startOffset, file.Length); + FileEntry entry = BundleManifest.AddEntry(targetType, relativePath, startOffset, file.Length); Tracer.Log($"Embed: {entry}"); } } diff --git a/src/installer/managed/Microsoft.NET.HostModel/Bundle/Manifest.cs b/src/installer/managed/Microsoft.NET.HostModel/Bundle/Manifest.cs index bd2b340..9c171ae 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/Bundle/Manifest.cs +++ b/src/installer/managed/Microsoft.NET.HostModel/Bundle/Manifest.cs @@ -59,7 +59,7 @@ namespace Microsoft.NET.HostModel.Bundle enum HeaderFlags : ulong { None = 0, - NetcoreApp3CompatMode = 2 + NetcoreApp3CompatMode = 1 } // Bundle ID is a string that is used to uniquely diff --git a/src/installer/managed/Microsoft.NET.HostModel/Bundle/TargetInfo.cs b/src/installer/managed/Microsoft.NET.HostModel/Bundle/TargetInfo.cs index 1f6f61a..87b1c65 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/Bundle/TargetInfo.cs +++ b/src/installer/managed/Microsoft.NET.HostModel/Bundle/TargetInfo.cs @@ -27,8 +27,6 @@ namespace Microsoft.NET.HostModel.Bundle public TargetInfo(OSPlatform? os, Version targetFrameworkVersion) { - Version net50 = new Version(5, 0); - OS = os ?? HostOS; FrameworkVersion = targetFrameworkVersion ?? net50; @@ -71,6 +69,21 @@ namespace Microsoft.NET.HostModel.Bundle // The .net core 3 apphost doesn't care about semantics of FileType -- all files are extracted at startup. // However, the apphost checks that the FileType value is within expected bounds, so set it to the first enumeration. public FileType TargetSpecificFileType(FileType fileType) => (BundleVersion == 1) ? FileType.Unknown : fileType; + + // In .net core 3.x, bundle processing happens within the AppHost. + // Therefore HostFxr and HostPolicy can be bundled within the single-file app. + // In .net 5, bundle processing happens in HostFxr and HostPolicy libraries. + // Therefore, these libraries themselves cannot be bundled into the single-file app. + // This problem is mitigated by statically linking these host components with the AppHost. + // https://github.com/dotnet/runtime/issues/32823 + public bool ShouldExclude(string relativePath) => + (FrameworkVersion.Major != 3) && (relativePath.Equals(HostFxr) || relativePath.Equals(HostPolicy)); + + readonly Version net50 = new Version(5, 0); + string HostFxr => IsWindows ? "hostfxr.dll" : IsLinux ? "libhostfxr.so" : "libhostfxr.dylib"; + string HostPolicy => IsWindows ? "hostpolicy.dll" : IsLinux ? "libhostpolicy.so" : "libhostpolicy.dylib"; + + } } diff --git a/src/installer/test/Assets/TestProjects/AppWithSubDirs/Program.cs b/src/installer/test/Assets/TestProjects/AppWithSubDirs/Program.cs index 20c2483..5b51c37 100644 --- a/src/installer/test/Assets/TestProjects/AppWithSubDirs/Program.cs +++ b/src/installer/test/Assets/TestProjects/AppWithSubDirs/Program.cs @@ -4,7 +4,6 @@ using System; using System.IO; -using System.Reflection; namespace AppWithSubDirs { @@ -12,10 +11,7 @@ namespace AppWithSubDirs { public static void Main(string[] args) { - string baseDir = - Path.Combine( - Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), - "Sentence"); + string baseDir = Path.Combine(AppContext.BaseDirectory, "Sentence"); string Part(string dir="", string subdir="", string subsubdir="") { diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs index 5f35a40..26dbc3a 100644 --- a/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs +++ b/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs @@ -7,9 +7,7 @@ using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.DotNet.CoreSetup.Test; using Microsoft.NET.HostModel.Bundle; using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading; using Xunit; @@ -29,24 +27,19 @@ namespace AppHost.Bundle.Tests { var fixture = sharedTestState.TestFixture.Copy(); var hostName = BundleHelper.GetHostName(fixture); - var appName = Path.GetFileNameWithoutExtension(hostName); - string publishPath = BundleHelper.GetPublishPath(fixture); // Publish the bundle - var bundleDir = BundleHelper.GetBundleDir(fixture); - var bundler = new Bundler(hostName, bundleDir.FullName, BundleOptions.BundleAllContent); - string singleFile = BundleHelper.GenerateBundle(bundler, publishPath); - - // Compute bundled files - var bundledFiles = bundler.BundleManifest.Files.Select(file => file.RelativePath).ToList(); + string singleFile; + Bundler bundler = BundleHelper.BundleApp(fixture, out singleFile); // Verify expected files in the bundle directory + var bundleDir = BundleHelper.GetBundleDir(fixture); bundleDir.Should().HaveFile(hostName); - bundleDir.Should().NotHaveFiles(bundledFiles); + bundleDir.Should().NotHaveFiles(BundleHelper.GetBundledFiles(fixture)); // Create a directory for extraction. - var extractBaseDir = BundleHelper.GetExtractDir(fixture); - extractBaseDir.Should().NotHaveDirectory(appName); + var extractBaseDir = BundleHelper.GetExtractionRootDir(fixture); + extractBaseDir.Should().NotHaveDirectory(BundleHelper.GetAppBaseName(fixture)); // Run the bundled app for the first time, and extract files to // $DOTNET_BUNDLE_EXTRACT_BASE_DIR//bundle-id @@ -60,27 +53,22 @@ namespace AppHost.Bundle.Tests .And .HaveStdOutContaining("Hello World"); - string extractPath = Path.Combine(extractBaseDir.FullName, appName, bundler.BundleManifest.BundleID); - var extractDir = new DirectoryInfo(extractPath); - extractDir.Should().OnlyHaveFiles(bundledFiles); - extractDir.Should().NotHaveFile(hostName); + var extractDir = BundleHelper.GetExtractionDir(fixture, bundler); + extractDir.Should().HaveFiles(BundleHelper.GetExtractedFiles(fixture)); + extractDir.Should().NotHaveFiles(BundleHelper.GetFilesNeverExtracted(fixture)); } [Fact] private void Bundle_extraction_is_reused() { var fixture = sharedTestState.TestFixture.Copy(); - var hostName = BundleHelper.GetHostName(fixture); - var appName = Path.GetFileNameWithoutExtension(hostName); - string publishPath = BundleHelper.GetPublishPath(fixture); // Publish the bundle - var bundleDir = BundleHelper.GetBundleDir(fixture); - var bundler = new Bundler(hostName, bundleDir.FullName, BundleOptions.BundleAllContent); - string singleFile = BundleHelper.GenerateBundle(bundler, publishPath); + string singleFile; + Bundler bundler = BundleHelper.BundleApp(fixture, out singleFile, BundleOptions.BundleNativeBinaries); // Create a directory for extraction. - var extractBaseDir = BundleHelper.GetExtractDir(fixture); + var extractBaseDir = BundleHelper.GetExtractionRootDir(fixture); // Run the bunded app for the first time, and extract files to // $DOTNET_BUNDLE_EXTRACT_BASE_DIR//bundle-id @@ -94,8 +82,8 @@ namespace AppHost.Bundle.Tests .And .HaveStdOutContaining("Hello World"); - string extractPath = Path.Combine(extractBaseDir.FullName, appName, bundler.BundleManifest.BundleID); - var extractDir = new DirectoryInfo(extractPath); + var appBaseName = BundleHelper.GetAppBaseName(fixture); + var extractDir = BundleHelper.GetExtractionDir(fixture, bundler); extractDir.Refresh(); DateTime firstWriteTime = extractDir.LastWriteTimeUtc; @@ -125,20 +113,14 @@ namespace AppHost.Bundle.Tests var fixture = sharedTestState.TestFixture.Copy(); var hostName = BundleHelper.GetHostName(fixture); var appName = Path.GetFileNameWithoutExtension(hostName); - string publishPath = BundleHelper.GetPublishPath(fixture); // Publish the bundle - var bundleDir = BundleHelper.GetBundleDir(fixture); - var bundler = new Bundler(hostName, bundleDir.FullName, BundleOptions.BundleAllContent); - string singleFile = BundleHelper.GenerateBundle(bundler, publishPath); - - // Compute bundled files - List bundledFiles = bundler.BundleManifest.Files.Select(file => file.RelativePath).ToList(); + string singleFile; + Bundler bundler = BundleHelper.BundleApp(fixture, out singleFile, BundleOptions.BundleNativeBinaries); // Create a directory for extraction. - var extractBaseDir = BundleHelper.GetExtractDir(fixture); - string extractPath = Path.Combine(extractBaseDir.FullName, appName, bundler.BundleManifest.BundleID); - var extractDir = new DirectoryInfo(extractPath); + var extractBaseDir = BundleHelper.GetExtractionRootDir(fixture); + // Run the bunded app for the first time, and extract files to // $DOTNET_BUNDLE_EXTRACT_BASE_DIR//bundle-id @@ -152,10 +134,14 @@ namespace AppHost.Bundle.Tests .And .HaveStdOutContaining("Hello World"); - bundledFiles.ForEach(file => File.Delete(Path.Combine(extractPath, file))); + // Remove the extracted files, but keep the extraction directory + var extractDir = BundleHelper.GetExtractionDir(fixture, bundler); + var extractedFiles = BundleHelper.GetExtractedFiles(fixture); + + Array.ForEach(extractedFiles, file => File.Delete(Path.Combine(extractDir.FullName, file))); extractDir.Should().Exist(); - extractDir.Should().NotHaveFiles(bundledFiles); + extractDir.Should().NotHaveFiles(extractedFiles); // Run the bundled app again (recover deleted files) Command.Create(singleFile) @@ -168,7 +154,7 @@ namespace AppHost.Bundle.Tests .And .HaveStdOutContaining("Hello World"); - extractDir.Should().HaveFiles(bundledFiles); + extractDir.Should().HaveFiles(extractedFiles); } diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleRename.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleRename.cs index 5466743..581a115 100644 --- a/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleRename.cs +++ b/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleRename.cs @@ -51,11 +51,13 @@ namespace AppHost.Bundle.Tests .CaptureStdOut() .Start(); - while (!File.Exists(waitFile)) + while (!File.Exists(waitFile) && !singleExe.Process.HasExited) { Thread.Sleep(100); } + Assert.True(File.Exists(waitFile)); + File.Move(singleFile, renameFile); File.Create(resumeFile).Close(); diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs index 59360c0..33e05b5 100644 --- a/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs +++ b/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs @@ -2,11 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using Xunit; -using Microsoft.DotNet.Cli.Build.Framework; using BundleTests.Helpers; +using Microsoft.DotNet.Cli.Build.Framework; +using Microsoft.NET.HostModel.Bundle; using Microsoft.DotNet.CoreSetup.Test; +using System; +using Xunit; namespace AppHost.Bundle.Tests { @@ -31,11 +32,16 @@ namespace AppHost.Bundle.Tests .HaveStdOutContaining("Wow! We now say hello to the big world and you."); } - [Fact] - public void Bundled_Framework_dependent_App_Run_Succeeds() + // BundleOptions.BundleNativeBinaries: Test when the payload data files are unbundled, and beside the single-file app. + // BundleOptions.BundleAllContent: Test when the payload data files are bundled and extracted to temporary directory. + // Once the runtime can load assemblies from the bundle, BundleOptions.None can be used in place of BundleOptions.BundleNativeBinaries. + [InlineData(BundleOptions.BundleNativeBinaries)] + [InlineData(BundleOptions.BundleAllContent)] + [Theory] + public void Bundled_Framework_dependent_App_Run_Succeeds(BundleOptions options) { var fixture = sharedTestState.TestFrameworkDependentFixture.Copy(); - var singleFile = BundleHelper.BundleApp(fixture); + var singleFile = BundleHelper.BundleApp(fixture, options); // Run the bundled app (extract files) RunTheApp(singleFile); @@ -44,11 +50,13 @@ namespace AppHost.Bundle.Tests RunTheApp(singleFile); } - [Fact] - public void Bundled_Self_Contained_App_Run_Succeeds() + [InlineData(BundleOptions.BundleNativeBinaries)] + [InlineData(BundleOptions.BundleAllContent)] + [Theory] + public void Bundled_Self_Contained_App_Run_Succeeds(BundleOptions options) { var fixture = sharedTestState.TestSelfContainedFixture.Copy(); - var singleFile = BundleHelper.BundleApp(fixture); + var singleFile = BundleHelper.BundleApp(fixture, options); // Run the bundled app (extract files) RunTheApp(singleFile); @@ -57,11 +65,13 @@ namespace AppHost.Bundle.Tests RunTheApp(singleFile); } - [Fact] - public void Bundled_With_Empty_File_Succeeds() + [InlineData(BundleOptions.BundleNativeBinaries)] + [InlineData(BundleOptions.BundleAllContent)] + [Theory] + public void Bundled_With_Empty_File_Succeeds(BundleOptions options) { var fixture = sharedTestState.TestAppWithEmptyFileFixture.Copy(); - var singleFile = BundleHelper.BundleApp(fixture); + var singleFile = BundleHelper.BundleApp(fixture, options); // Run the app RunTheApp(singleFile); diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/Helpers/BundleHelper.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/Helpers/BundleHelper.cs index e941bec..c5f7717 100644 --- a/src/installer/test/Microsoft.NET.HostModel.Tests/Helpers/BundleHelper.cs +++ b/src/installer/test/Microsoft.NET.HostModel.Tests/Helpers/BundleHelper.cs @@ -40,6 +40,32 @@ namespace BundleTests.Helpers return Path.GetFileName(fixture.TestProject.AppDll); } + public static string GetAppBaseName(TestProjectFixture fixture) + { + return Path.GetFileNameWithoutExtension(GetAppName(fixture)); + } + + public static string[] GetBundledFiles(TestProjectFixture fixture) + { + string appBaseName = GetAppBaseName(fixture); + return new string[] { $"{appBaseName}.dll", $"{appBaseName}.deps.json", $"{appBaseName}.runtimeconfig.json" }; + } + + public static string[] GetExtractedFiles(TestProjectFixture fixture) + { + string appBaseName = GetAppBaseName(fixture); + return new string[] { $"{appBaseName}.dll" }; + } + + public static string[] GetFilesNeverExtracted(TestProjectFixture fixture) + { + string appBaseName = GetAppBaseName(fixture); + return new string[] { $"{appBaseName}.deps.json", + $"{appBaseName}.runtimeconfig.json", + Path.GetFileName(fixture.TestProject.HostFxrDll), + Path.GetFileName(fixture.TestProject.HostPolicyDll) }; + } + public static string GetPublishPath(TestProjectFixture fixture) { return Path.Combine(fixture.TestProject.ProjectDirectory, "publish"); @@ -50,13 +76,28 @@ namespace BundleTests.Helpers return Directory.CreateDirectory(Path.Combine(fixture.TestProject.ProjectDirectory, "bundle")); } - public static DirectoryInfo GetExtractDir(TestProjectFixture fixture) + public static string GetExtractionRootPath(TestProjectFixture fixture) + { + return Path.Combine(fixture.TestProject.ProjectDirectory, "extract"); + } + + public static DirectoryInfo GetExtractionRootDir(TestProjectFixture fixture) { - return Directory.CreateDirectory(Path.Combine(fixture.TestProject.ProjectDirectory, "extract")); + return Directory.CreateDirectory(GetExtractionRootPath(fixture)); + } + + public static string GetExtractionPath(TestProjectFixture fixture, Bundler bundler) + { + return Path.Combine(GetExtractionRootPath(fixture), GetAppBaseName(fixture), bundler.BundleManifest.BundleID); + + } + public static DirectoryInfo GetExtractionDir(TestProjectFixture fixture, Bundler bundler) + { + return new DirectoryInfo(GetExtractionPath(fixture, bundler)); } /// Generate a bundle containind the (embeddable) files in sourceDir - public static string GenerateBundle(Bundler bundler, string sourceDir) + public static string GenerateBundle(Bundler bundler, string sourceDir, string outputDir, bool copyExludedFiles=true) { // Convert sourceDir to absolute path sourceDir = Path.GetFullPath(sourceDir); @@ -73,7 +114,22 @@ namespace BundleTests.Helpers fileSpecs.Add(new FileSpec(file, Path.GetRelativePath(sourceDir, file))); } - return bundler.GenerateBundle(fileSpecs); + var singleFile = bundler.GenerateBundle(fileSpecs); + + if (copyExludedFiles) + { + foreach (var spec in fileSpecs) + { + if (spec.Excluded) + { + var outputFilePath = Path.Combine(outputDir, spec.BundleRelativePath); + Directory.CreateDirectory(Path.GetDirectoryName(outputFilePath)); + File.Copy(spec.SourcePath, outputFilePath, true); + } + } + } + + return singleFile; } // Bundle to a single-file @@ -81,23 +137,40 @@ namespace BundleTests.Helpers // instead of the SDK via /p:PublishSingleFile=true. // This is necessary when the test needs the latest changes in the AppHost, // which may not (yet) be available in the SDK. - // - // Currently, AppHost can only handle bundles if all content is extracted to disk on startup. - // Therefore, the BundleOption is BundleAllContent by default. - // The default should be BundleOptions.None once host/runtime no longer requires full-extraction. - public static string BundleApp(TestProjectFixture fixture, - BundleOptions options = BundleOptions.BundleAllContent, - Version targetFrameworkVersion = null) + public static Bundler BundleApp(TestProjectFixture fixture, + out string singleFile, + BundleOptions options = BundleOptions.BundleNativeBinaries, + Version targetFrameworkVersion = null, + bool copyExcludedFiles = true) { var hostName = GetHostName(fixture); string publishPath = GetPublishPath(fixture); var bundleDir = GetBundleDir(fixture); var bundler = new Bundler(hostName, bundleDir.FullName, options, targetFrameworkVersion: targetFrameworkVersion); - string singleFile = GenerateBundle(bundler, publishPath); + singleFile = GenerateBundle(bundler, publishPath, bundleDir.FullName, copyExcludedFiles); + + return bundler; + } + + // The defaut option for Bundling apps is BundleOptions.BundleNativeBinaries + // Until CoreCLR runtime can learn how to process assemblies from the bundle. + // This is because CoreCLR expects System.Private.Corelib.dll to be beside CoreCLR.dll + public static string BundleApp(TestProjectFixture fixture, + BundleOptions options = BundleOptions.BundleNativeBinaries, + Version targetFrameworkVersion = null) + { + string singleFile; + BundleApp(fixture, out singleFile, options, targetFrameworkVersion); return singleFile; } + public static Bundler Bundle(TestProjectFixture fixture, BundleOptions options = BundleOptions.None) + { + string singleFile; + return BundleApp(fixture, out singleFile, options, copyExcludedFiles:false); + } + public static void AddLongNameContentToAppWithSubDirs(TestProjectFixture fixture) { // For tests using the AppWithSubDirs, One of the sub-directories with a really long name diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleAndRun.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleAndRun.cs index 3199b15..e4949b4 100644 --- a/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleAndRun.cs +++ b/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleAndRun.cs @@ -7,7 +7,6 @@ using System.IO; using Xunit; using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.DotNet.CoreSetup.Test; -using Microsoft.NET.HostModel.Bundle; using BundleTests.Helpers; namespace Microsoft.NET.HostModel.Tests @@ -41,9 +40,7 @@ namespace Microsoft.NET.HostModel.Tests RunTheApp(Path.Combine(publishPath, hostName)); // Bundle to a single-file - // Bundle all content, until the host can handle other scenarios. - Bundler bundler = new Bundler(hostName, singleFileDir, BundleOptions.BundleAllContent); - string singleFile = BundleHelper.GenerateBundle(bundler, publishPath); + string singleFile = BundleHelper.BundleApp(fixture); // Run the extracted app RunTheApp(singleFile); diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleLegacy.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleLegacy.cs index a9c9b70..7b9d686 100644 --- a/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleLegacy.cs +++ b/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleLegacy.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.IO; using Xunit; using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.DotNet.CoreSetup.Test; diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundlerConsistencyTests.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundlerConsistencyTests.cs index efa1328..2e0bda9 100644 --- a/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundlerConsistencyTests.cs +++ b/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundlerConsistencyTests.cs @@ -92,18 +92,10 @@ namespace Microsoft.NET.HostModel.Tests public void TestFilesAlwaysBundled(BundleOptions options) { var fixture = sharedTestState.TestFixture.Copy(); + var bundler = BundleHelper.Bundle(fixture, options); + var bundledFiles = BundleHelper.GetBundledFiles(fixture); - var hostName = BundleHelper.GetHostName(fixture); - var appName = Path.GetFileNameWithoutExtension(hostName); - string publishPath = BundleHelper.GetPublishPath(fixture); - var bundleDir = BundleHelper.GetBundleDir(fixture); - - var bundler = new Bundler(hostName, bundleDir.FullName, options); - BundleHelper.GenerateBundle(bundler, publishPath); - - bundler.BundleManifest.Contains($"{appName}.dll").Should().BeTrue(); - bundler.BundleManifest.Contains($"{appName}.deps.json").Should().BeTrue(); - bundler.BundleManifest.Contains($"{appName}.runtimeconfig.json").Should().BeTrue(); + Array.ForEach(bundledFiles, file => bundler.BundleManifest.Contains(file).Should().BeTrue()); } [InlineData(BundleOptions.None)] @@ -115,20 +107,16 @@ namespace Microsoft.NET.HostModel.Tests public void TestFilesNeverBundled(BundleOptions options) { var fixture = sharedTestState.TestFixture.Copy(); - - var hostName = BundleHelper.GetHostName(fixture); - var appName = Path.GetFileNameWithoutExtension(hostName); + var appBaseName = BundleHelper.GetAppBaseName(fixture); string publishPath = BundleHelper.GetPublishPath(fixture); - var bundleDir = BundleHelper.GetBundleDir(fixture); // Make up a app.runtimeconfig.dev.json file in the publish directory. - File.Copy(Path.Combine(publishPath, $"{appName}.runtimeconfig.json"), - Path.Combine(publishPath, $"{appName}.runtimeconfig.dev.json")); + File.Copy(Path.Combine(publishPath, $"{appBaseName}.runtimeconfig.json"), + Path.Combine(publishPath, $"{appBaseName}.runtimeconfig.dev.json")); - var bundler = new Bundler(hostName, bundleDir.FullName, options); - BundleHelper.GenerateBundle(bundler, publishPath); + var bundler = BundleHelper.Bundle(fixture, options); - bundler.BundleManifest.Contains($"{appName}.runtimeconfig.dev.json").Should().BeFalse(); + bundler.BundleManifest.Contains($"{appBaseName}.runtimeconfig.dev.json").Should().BeFalse(); } [InlineData(BundleOptions.None)] @@ -137,16 +125,10 @@ namespace Microsoft.NET.HostModel.Tests public void TestBundlingSymbols(BundleOptions options) { var fixture = sharedTestState.TestFixture.Copy(); + var appBaseName = BundleHelper.GetAppBaseName(fixture); + var bundler = BundleHelper.Bundle(fixture, options); - var hostName = BundleHelper.GetHostName(fixture); - var appName = Path.GetFileNameWithoutExtension(hostName); - string publishPath = BundleHelper.GetPublishPath(fixture); - var bundleDir = BundleHelper.GetBundleDir(fixture); - - var bundler = new Bundler(hostName, bundleDir.FullName, options); - BundleHelper.GenerateBundle(bundler, publishPath); - - bundler.BundleManifest.Contains($"{appName}.pdb").Should().Be(options.HasFlag(BundleOptions.BundleSymbolFiles)); + bundler.BundleManifest.Contains($"{appBaseName}.pdb").Should().Be(options.HasFlag(BundleOptions.BundleSymbolFiles)); } [InlineData(BundleOptions.None)] @@ -155,30 +137,17 @@ namespace Microsoft.NET.HostModel.Tests public void TestBundlingNativeBinaries(BundleOptions options) { var fixture = sharedTestState.TestFixture.Copy(); + var coreclr = Path.GetFileName(fixture.TestProject.CoreClrDll); + var bundler = BundleHelper.Bundle(fixture, options); - var hostName = BundleHelper.GetHostName(fixture); - var hostfxr = Path.GetFileName(fixture.TestProject.HostFxrDll); - string publishPath = BundleHelper.GetPublishPath(fixture); - var bundleDir = BundleHelper.GetBundleDir(fixture); - - var bundler = new Bundler(hostName, bundleDir.FullName, options); - BundleHelper.GenerateBundle(bundler, publishPath); - - bundler.BundleManifest.Contains($"{hostfxr}").Should().Be(options.HasFlag(BundleOptions.BundleNativeBinaries)); + bundler.BundleManifest.Contains($"{coreclr}").Should().Be(options.HasFlag(BundleOptions.BundleNativeBinaries)); } [Fact] public void TestAssemblyAlignment() { var fixture = sharedTestState.TestFixture.Copy(); - - var hostName = BundleHelper.GetHostName(fixture); - var appName = Path.GetFileNameWithoutExtension(hostName); - string publishPath = BundleHelper.GetPublishPath(fixture); - var bundleDir = BundleHelper.GetBundleDir(fixture); - - var bundler = new Bundler(hostName, bundleDir.FullName, BundleOptions.BundleAllContent); - BundleHelper.GenerateBundle(bundler, publishPath); + var bundler = BundleHelper.Bundle(fixture); bundler.BundleManifest.Files.ForEach(file => Assert.True((file.Type != FileType.Assembly) || (file.Offset % Bundler.AssemblyAlignment == 0))); @@ -188,13 +157,7 @@ namespace Microsoft.NET.HostModel.Tests public void TestWithAdditionalContentAfterBundleMetadata() { var fixture = sharedTestState.TestFixture.Copy(); - - var hostName = BundleHelper.GetHostName(fixture); - var bundleDir = BundleHelper.GetBundleDir(fixture); - string publishPath = BundleHelper.GetPublishPath(fixture); - - var bundler = new Bundler(hostName, bundleDir.FullName, BundleOptions.BundleAllContent); - string singleFile = BundleHelper.GenerateBundle(bundler, publishPath); + string singleFile = BundleHelper.BundleApp(fixture); using (var file = File.OpenWrite(singleFile)) { diff --git a/src/installer/test/TestUtils/TestApp.cs b/src/installer/test/TestUtils/TestApp.cs index 3e4e72f..a1ff67d 100644 --- a/src/installer/test/TestUtils/TestApp.cs +++ b/src/installer/test/TestUtils/TestApp.cs @@ -16,6 +16,7 @@ namespace Microsoft.DotNet.CoreSetup.Test public string RuntimeDevConfigJson { get; private set; } public string HostPolicyDll { get; private set; } public string HostFxrDll { get; private set; } + public string CoreClrDll { get; private set; } public string AssemblyName { get; } @@ -55,6 +56,7 @@ namespace Microsoft.DotNet.CoreSetup.Test RuntimeDevConfigJson = Path.Combine(Location, $"{AssemblyName}.runtimeconfig.dev.json"); HostPolicyDll = Path.Combine(Location, RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostpolicy")); HostFxrDll = Path.Combine(Location, RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostfxr")); + CoreClrDll = Path.Combine(Location, RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("coreclr")); } } } diff --git a/src/installer/test/TestUtils/TestProject.cs b/src/installer/test/TestUtils/TestProject.cs index 5fd9135..b512f4c 100644 --- a/src/installer/test/TestUtils/TestProject.cs +++ b/src/installer/test/TestUtils/TestProject.cs @@ -22,6 +22,7 @@ namespace Microsoft.DotNet.CoreSetup.Test public string AppExe { get => BuiltApp?.AppExe; } public string HostPolicyDll { get => BuiltApp?.HostPolicyDll; } public string HostFxrDll { get => BuiltApp?.HostFxrDll; } + public string CoreClrDll { get => BuiltApp?.CoreClrDll; } public TestApp BuiltApp { get; private set; } -- 2.7.4