未验证 提交 47ec733b 编写于 作者: S Swaroop Sridhar 提交者: GitHub

[release/5.0-preview4] Revert processing bundles in framework (#35679)

This commit reverts:
Revert "Single-File: Process bundles in the framework (#34274)"
This reverts commit 78b303df.

Revert "Single-File Bundler: Add a FileSize test (#35149)"
This reverts commit 779588a5.

*Customer Scenario*

Publishing apps as a self-contained single-file doesn't work as expected.

* Publish needs to generate hostpolicy and hostfxr separate from the single file bundle
* Cross-platform publishing is incorrect

*Problem*

Since Static-apphost is not yet ready, processing bundle content in hostpolicy means that  hostpolicy and hostfxr DLLs need to be separate from the bundle. This causes self-contained single-file apps to not be a "single file" temporarily. 

The change also requires supporting changes from the SDK, to publish hostfxr and hostpolicy as separate files, and to invoke HostModel library with arguments that facilitate cross-platform publishing.

*Solution*

To solve these, problem, this change reverts:

Revert "Single-File: Process bundles in the framework (#34274)" commit 78b303df.

and a dependent test-only change:

Revert "Single-File Bundler: Add a FileSize test (#35149)" commit 779588a5.

*Risk*

Medium
The change is contained to only host components: apphost, hostpolicy, and hostfxr.
However, the change is big, and needs testing in runtime and SDK repos.

*Testing*

Manually tested the SDK by inserting apphost, hostfxr, hostpolicy, and hostmodel library from this build into the `dotnet/packs` preview-4 SDK from https://github.com/dotnet/sdk/pull/11518 build.

Verified that:
* Singlefile apps can be published and run OK for  { Windows, Linux, Osx } x {netcoreapp3.0, netcoreapp3.1, netcoreapp5.0} 
* Cross-targeting builds of single-file apps build and run OK (ex: built on Windos, run on Mac).
上级 6d1f7e01
......@@ -18,11 +18,26 @@ endif()
set(SKIP_VERSIONING 1)
set(SOURCES
./bundle_marker.cpp
./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
)
set(HEADERS
./bundle_marker.h
./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
)
if(CLR_CMAKE_TARGET_WIN32)
......
......@@ -197,10 +197,7 @@ void extractor_t::extract_new(reader_t& reader)
begin();
for (const file_entry_t& entry : m_manifest.files)
{
if (entry.needs_extraction())
{
extract(entry, reader);
}
extract(entry, reader);
}
commit_dir();
}
......@@ -214,11 +211,6 @@ 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());
......
......@@ -33,18 +33,3 @@ 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;
}
}
......@@ -58,7 +58,6 @@ 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);
......
......@@ -9,23 +9,29 @@
using namespace bundle;
bool header_fixed_t::is_valid() const
// 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
{
if (num_embedded_files <= 0)
{
return false;
}
// .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);
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));
}
header_t header_t::read(reader_t& reader)
header_t header_t::read(reader_t& reader, bool need_exact_version)
{
const header_fixed_t* fixed_header = reinterpret_cast<const header_fixed_t*>(reader.read_direct(sizeof(header_fixed_t)));
if (!fixed_header->is_valid())
if (!fixed_header->is_valid(need_exact_version))
{
trace::error(_X("Failure processing application bundle."));
trace::error(_X("Bundle header version compatibility check failed."));
......@@ -38,8 +44,10 @@ header_t header_t::read(reader_t& reader)
// bundle_id is a component of the extraction path
reader.read_path_string(header.m_bundle_id);
const header_fixed_v2_t *v2_header = reinterpret_cast<const header_fixed_v2_t*>(reader.read_direct(sizeof(header_fixed_v2_t)));
header.m_v2_header = *v2_header;
if (fixed_header->major_version > 1)
{
header.m_v2_header = reinterpret_cast<const header_fixed_v2_t*>(reader.read_direct(sizeof(header_fixed_v2_t)));
}
return header;
}
......@@ -32,8 +32,9 @@ namespace bundle
uint32_t minor_version;
int32_t num_embedded_files;
bool is_valid() const;
bool is_valid(bool exact_match = false) 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:
......@@ -45,13 +46,12 @@ 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,8 +65,6 @@ 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)
......@@ -76,17 +74,14 @@ namespace bundle
header_t(int32_t num_embedded_files = 0)
: m_num_embedded_files(num_embedded_files)
, m_bundle_id()
, m_v2_header()
, m_v2_header(NULL)
{
}
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 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 const uint32_t major_version = 2;
static const uint32_t minor_version = 0;
......@@ -94,7 +89,8 @@ namespace bundle
private:
int32_t m_num_embedded_files;
pal::string_t m_bundle_id;
header_fixed_v2_t m_v2_header;
const header_fixed_v2_t* m_v2_header;
};
}
#endif // __HEADER_H__
......@@ -12,9 +12,7 @@ manifest_t manifest_t::read(reader_t& reader, int32_t num_files)
for (int32_t i = 0; i < num_files; i++)
{
file_entry_t entry = file_entry_t::read(reader);
manifest.files.push_back(std::move(entry));
manifest.m_need_extraction |= entry.needs_extraction();
manifest.files.emplace_back(file_entry_t::read(reader));
}
return manifest;
......
......@@ -16,17 +16,9 @@ namespace bundle
class manifest_t
{
public:
manifest_t()
: m_need_extraction(false) {}
std::vector<file_entry_t> 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__
......@@ -2,12 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#include "bundle_marker.h"
#include "marker.h"
#include "pal.h"
#include "trace.h"
#include "utils.h"
int64_t bundle_marker_t::header_offset()
using namespace bundle;
int64_t 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
......@@ -25,7 +27,7 @@ int64_t bundle_marker_t::header_offset()
0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae
};
volatile bundle_marker_t* marker = reinterpret_cast<volatile bundle_marker_t *>(placeholder);
volatile marker_t* marker = reinterpret_cast<volatile marker_t *>(placeholder);
return marker->locator.bundle_header_offset;
}
......@@ -2,13 +2,15 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#ifndef __BUNDLE_MARKER_H__
#define __BUNDLE_MARKER_H__
#ifndef __MARKER_H__
#define __MARKER_H__
#include <cstdint>
namespace bundle
{
#pragma pack(push, 1)
union bundle_marker_t
union marker_t
{
public:
uint8_t placeholder[40];
......@@ -26,5 +28,5 @@
};
#pragma pack(pop)
#endif // __BUNDLE_MARKER_H__
}
#endif // __MARKER_H__
......@@ -8,9 +8,9 @@
using namespace bundle;
const char* reader_t::add_without_overflow(const char* ptr, int64_t len)
const int8_t* reader_t::add_without_overflow(const int8_t* ptr, int64_t len)
{
const char* new_ptr = ptr + len;
const int8_t* 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 char* post_read_ptr = add_without_overflow(m_ptr, len);
const int8_t* 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,14 +88,11 @@ size_t reader_t::read_path_length()
return length;
}
size_t reader_t::read_path_string(pal::string_t &str)
void reader_t::read_path_string(pal::string_t &str)
{
const char* start_ptr = m_ptr;
size_t size = read_path_length();
std::unique_ptr<uint8_t[]> buffer{ new uint8_t[size + 1] };
read(buffer.get(), size);
buffer[size] = 0; // null-terminator
pal::clr_palstring(reinterpret_cast<const char*>(buffer.get()), &str);
return m_ptr - start_ptr; // This subtraction can't overflow because addition above is bounds_checked
}
......@@ -13,20 +13,19 @@ namespace bundle
// Helper class for reading sequentially from the memory-mapped bundle file.
struct reader_t
{
reader_t(const char* base_ptr, int64_t bound, int64_t start_offset = 0)
reader_t(const int8_t* base_ptr, int64_t bound)
: 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 char*() const
operator const int8_t*() const
{
return m_ptr;
}
......@@ -47,26 +46,26 @@ namespace bundle
// Return a pointer to the requested bytes within the memory-mapped file.
// Skip over len bytes.
const char* read_direct(int64_t len)
const int8_t* read_direct(int64_t len)
{
bounds_check(len);
const char *ptr = m_ptr;
const int8_t *ptr = m_ptr;
m_ptr += len;
return ptr;
}
size_t read_path_length();
size_t read_path_string(pal::string_t &str);
void read_path_string(pal::string_t &str);
private:
void bounds_check(int64_t len = 1);
static const char* add_without_overflow(const char* ptr, int64_t len);
static const int8_t* add_without_overflow(const int8_t* ptr, int64_t len);
const char* const m_base_ptr;
const char* m_ptr;
const int8_t* const m_base_ptr;
const int8_t* m_ptr;
const int64_t m_bound;
const char* const m_bound_ptr;
const int8_t* const m_bound_ptr;
};
}
......
// 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 <memory>
#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;
}
}
// 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__
// 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"));
}
}
// 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_
// 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 <memory>
#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;
}
// 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__
......@@ -6,39 +6,9 @@
#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;
}
// -----------------------------------------------------------------------------
// 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
bool deps_entry_t::to_path(const pal::string_t& base, bool look_in_base, pal::string_t* str) const
{
pal::string_t& candidate = *str;
......@@ -50,41 +20,20 @@ bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_
return false;
}
pal::string_t normalized_path = normalize_dir_separator(asset.relative_path);
// Reserve space for the path below
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())
// 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)
{
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());
}
replace_char(&pal_relative_path, _X('/'), DIR_SEPARATOR);
}
// Reserve space for the path below
candidate.reserve(base.length() +
pal_relative_path.length() + 3);
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);
......@@ -98,7 +47,6 @@ bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_
{
trace::verbose(_X(" %s path query exists %s"), query_type, candidate.c_str());
}
return exists;
}
......@@ -107,50 +55,55 @@ bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_
//
// 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
// str - If the method returns true, contains the file path for this deps
// entry relative to the "base" directory
//
// 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, bool look_in_bundle, pal::string_t* str) const
bool deps_entry_t::to_dir_path(const pal::string_t& base, pal::string_t* str) const
{
pal::string_t ietf_dir;
if (asset_type == asset_types::resources)
{
pal::string_t pal_relative_path = normalize_dir_separator(asset.relative_path);
pal::string_t pal_relative_path = asset.relative_path;
if (_X('/') != DIR_SEPARATOR)
{
replace_char(&pal_relative_path, _X('/'), DIR_SEPARATOR);
}
// Resources are represented as "lib/<netstandrd_ver>/<ietf-code>/<ResourceAssemblyName.dll>" in the deps.json.
// The <ietf-code> is the "directory" in the pal_relative_path below, so extract it.
ietf_dir = get_directory(pal_relative_path);
pal::string_t ietf_dir = get_directory(pal_relative_path);
pal::string_t ietf = ietf_dir;
// get_directory returns with DIR_SEPARATOR appended that we need to remove.
remove_trailing_dir_seperator(&ietf_dir);
remove_trailing_dir_seperator(&ietf);
// Extract IETF code from "lib/<netstandrd_ver>/<ietf-code>"
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());
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);
}
return to_path(base, ietf_dir, true, look_in_bundle, str);
return to_path(base, true, 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
// str - If the method returns true, contains the file path for this deps
// entry relative to the "base" directory
//
// 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, bool look_in_bundle, pal::string_t* str) const
bool deps_entry_t::to_rel_path(const pal::string_t& base, pal::string_t* str) const
{
return to_path(base, _X(""), false, look_in_bundle, str);
return to_path(base, false, str);
}
// -----------------------------------------------------------------------------
......@@ -159,7 +112,8 @@ bool deps_entry_t::to_rel_path(const pal::string_t& base, bool look_in_bundle, p
//
// 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
// str - If the method returns true, contains the file path for this deps
// entry relative to the "base" directory
//
// Returns:
// If the file exists in the path relative to the "base" directory.
......@@ -186,5 +140,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, false, str);
return to_rel_path(new_base, str);
}
......@@ -52,19 +52,17 @@ struct deps_entry_t
bool is_serviceable;
bool is_rid_specific;
// 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 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 relative path in the package layout.
bool to_rel_path(const pal::string_t& base, bool look_in_bundle, pal::string_t* str) const;
bool to_rel_path(const pal::string_t& base, 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_
......@@ -6,7 +6,6 @@
#include "deps_format.h"
#include "utils.h"
#include "trace.h"
#include "bundle/info.h"
#include <tuple>
#include <array>
#include <iterator>
......@@ -427,16 +426,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 = bundle::info_t::config_t::probe(deps_path) || pal::file_exists(deps_path);
m_file_exists = pal::file_exists(deps_path);
json_parser_t json;
// If file doesn't exist, then assume parsed.
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;
......
......@@ -9,7 +9,6 @@
#include "sdk_info.h"
#include <trace.h>
#include <utils.h>
#include "bundle/info.h"
namespace
{
......@@ -145,7 +144,7 @@ namespace
if (mode == host_mode_t::apphost)
{
app_candidate = host_info.app_path;
doesAppExist = bundle::info_t::is_single_file_bundle() || pal::realpath(&app_candidate);
doesAppExist = pal::realpath(&app_candidate);
}
else
{
......
......@@ -3,7 +3,6 @@
// 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<pal::string_t>& arr, std::vector<const pal::char_t*>* out)
{
......@@ -133,8 +132,6 @@ 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;
}
......
......@@ -8,7 +8,7 @@
#include "host_interface.h"
#include "host_startup_info.h"
#include "fx_definition.h"
class corehost_init_t
{
private:
......@@ -37,7 +37,6 @@ 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,
......
......@@ -27,7 +27,6 @@
#include "sdk_info.h"
#include "sdk_resolver.h"
#include "roll_fwd_on_no_candidate_fx_option.h"
#include "bundle/info.h"
namespace
{
......@@ -263,7 +262,6 @@ 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());
......@@ -295,11 +293,6 @@ 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)
......@@ -364,7 +357,6 @@ 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))
{
......
......@@ -14,7 +14,6 @@
#include "sdk_resolver.h"
#include "hostfxr.h"
#include "host_context.h"
#include "bundle/info.h"
namespace
{
......@@ -25,23 +24,6 @@ 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"));
......
......@@ -29,6 +29,7 @@ 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))
{
......
......@@ -7,7 +7,6 @@
#include <cstddef>
#include "pal.h"
#include "bundle/info.h"
enum host_mode_t
{
......@@ -59,7 +58,6 @@ 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)
......@@ -93,8 +91,7 @@ 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(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?");
static_assert(sizeof(host_interface_t) == 30 * sizeof(size_t), "Did you add static asserts for the newly added fields?");
#define HOST_INTERFACE_LAYOUT_VERSION_HI 0x16041101 // YYMMDD:nn always increases when layout breaks compat.
#define HOST_INTERFACE_LAYOUT_VERSION_LO sizeof(host_interface_t)
......
......@@ -11,7 +11,6 @@
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,
......
......@@ -23,9 +23,6 @@ set(SOURCES
../version.cpp
../version_compatibility_range.cpp
../runtime_config.cpp
../bundle/info.cpp
../bundle/reader.cpp
../bundle/header.cpp
)
set(HEADERS
......@@ -40,9 +37,6 @@ set(HEADERS
../version.h
../version_compatibility_range.h
../runtime_config.h
../bundle/info.h
../bundle/reader.h
../bundle/header.h
)
set(SKIP_VERSIONING 1)
......
......@@ -37,13 +37,6 @@ 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);
......
......@@ -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 munmap(void* addr, size_t length) { return UnmapViewOfFile(addr) != 0; }
inline bool unmap_file(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 munmap(void* addr, size_t length) { return ::munmap(addr, length) == 0; }
inline bool unmap_file(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,9 +257,7 @@ namespace pal
return fallbackRid;
}
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);
void* map_file_readonly(const string_t& path, size_t& length);
bool touch_file(const string_t& path);
bool realpath(string_t* path, bool skip_error_logging = false);
bool file_exists(const string_t& path);
......
......@@ -70,7 +70,7 @@ bool pal::touch_file(const pal::string_t& path)
return true;
}
static void* map_file(const pal::string_t& path, size_t* length, int prot, int flags)
void* pal::map_file_readonly(const pal::string_t& path, size_t& length)
{
int fd = open(path.c_str(), O_RDONLY);
if (fd == -1)
......@@ -86,35 +86,21 @@ static void* map_file(const pal::string_t& path, size_t* length, int prot, int f
close(fd);
return nullptr;
}
size_t size = buf.st_size;
if (length != nullptr)
{
*length = size;
}
void* address = mmap(nullptr, size, prot, flags, fd, 0);
length = buf.st_size;
void* address = mmap(nullptr, length, PROT_READ, MAP_SHARED, fd, 0);
if (address == MAP_FAILED)
if(address == nullptr)
{
trace::error(_X("Failed to map file. mmap(%s) failed with error %d"), path.c_str(), errno);
address = nullptr;
close(fd);
return 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();
......@@ -504,10 +490,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]);
......
......@@ -76,7 +76,7 @@ bool pal::touch_file(const pal::string_t& path)
return true;
}
static void* map_file(const pal::string_t& path, size_t *length, DWORD mapping_protect, DWORD view_desired_access)
void* pal::map_file_readonly(const pal::string_t& path, size_t &length)
{
HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
......@@ -86,19 +86,16 @@ static void* map_file(const pal::string_t& path, size_t *length, DWORD mapping_p
return nullptr;
}
if (length != nullptr)
LARGE_INTEGER fileSize;
if (GetFileSizeEx(file, &fileSize) == 0)
{
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;
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;
HANDLE map = CreateFileMappingW(file, NULL, mapping_protect, 0, 0, NULL);
HANDLE map = CreateFileMappingW(file, NULL, PAGE_READONLY, 0, 0, NULL);
if (map == NULL)
{
......@@ -107,7 +104,7 @@ static void* map_file(const pal::string_t& path, size_t *length, DWORD mapping_p
return nullptr;
}
void *address = MapViewOfFile(map, view_desired_access, 0, 0, 0);
void *address = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
if (address == NULL)
{
......@@ -123,16 +120,6 @@ static void* map_file(const pal::string_t& path, size_t *length, DWORD mapping_p
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();
......
......@@ -4,7 +4,6 @@
#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)
{
......@@ -366,7 +365,6 @@ 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);
......@@ -379,28 +377,19 @@ pal::string_t get_deps_from_app_binary(const pal::string_t& app_base, const pal:
return deps_file;
}
pal::string_t get_runtime_config_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)
{
auto json_path = path;
auto json_name = name + _X(".runtimeconfig.json");
append_path(&json_path, json_name.c_str());
return json_path;
}
cfg->assign(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());
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));
dev_cfg->assign(dev_json_path);
trace::verbose(_X("Runtime config is cfg=%s dev=%s"), cfg->c_str(), dev_cfg->c_str());
trace::verbose(_X("Runtime config is cfg=%s dev=%s"), json_path.c_str(), dev_json_path.c_str());
}
pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t &fxr_path)
......
......@@ -45,8 +45,6 @@ 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);
......
......@@ -19,11 +19,6 @@ 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
......@@ -35,11 +30,6 @@ 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)
......
......@@ -4,7 +4,6 @@
#include "args.h"
#include <utils.h>
#include "bundle/runner.h"
arguments_t::arguments_t()
: host_mode(host_mode_t::invalid)
......@@ -102,47 +101,6 @@ 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,
......@@ -157,11 +115,13 @@ bool init_arguments(
args.host_path = host_info.host_path;
args.additional_deps_serialized = additional_deps_serialized;
if (!set_root_from_app(managed_application_path, args))
args.managed_application = managed_application_path;
if (!args.managed_application.empty() && !pal::realpath(&args.managed_application))
{
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())
{
......
......@@ -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, false, candidate))
if (config.probe_deps_json->has_package(entry.library_name, entry.library_version) && entry.to_dir_path(probe_dir, 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, true, candidate))
if (entry.to_rel_path(deps_dir, 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, true, candidate))
if (entry.to_dir_path(deps_dir, candidate))
{
trace::verbose(_X(" Probed deps dir and matched '%s'"), candidate->c_str());
return true;
......
......@@ -14,7 +14,6 @@
#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
......@@ -186,17 +185,6 @@ 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;
}
......
......@@ -18,7 +18,6 @@
#include <corehost_context_contract.h>
#include <hostpolicy.h>
#include "hostpolicy_context.h"
#include "bundle/runner.h"
namespace
{
......@@ -355,7 +354,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,
......@@ -368,15 +367,6 @@ 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);
}
......@@ -444,9 +434,6 @@ 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);
}
......
......@@ -4,7 +4,6 @@
#include "hostpolicy_init.h"
#include <trace.h>
#include "bundle/runner.h"
void make_palstr_arr(int argc, const pal::char_t** argv, std::vector<pal::string_t>* out)
{
......@@ -127,15 +126,6 @@ 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;
}
......
......@@ -8,7 +8,6 @@
#include "host_interface.h"
#include "host_startup_info.h"
#include "fx_definition.h"
#include "bundle/info.h"
struct hostpolicy_init_t
{
......
......@@ -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"));
}
......
......@@ -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);
}
......
......@@ -42,9 +42,9 @@ std::streampos get_utf8_bom_length(pal::istream_t& stream)
return 3;
}
void get_line_column_from_offset(const char* data, uint64_t size, size_t offset, int *line, int *column)
void get_line_column_from_offset(const std::vector<char>& json, size_t offset, int *line, int *column)
{
assert(offset < size);
assert(offset < json.size());
*line = *column = 1;
......@@ -52,12 +52,12 @@ void get_line_column_from_offset(const char* data, uint64_t size, size_t offset,
{
(*column)++;
if (data[i] == '\n')
if (json[i] == '\n')
{
(*line)++;
*column = 1;
}
else if (data[i] == '\r' && data[i + 1] == '\n')
else if (json[i] == '\r' && json[i + 1] == '\n')
{
(*line)++;
*column = 1;
......@@ -75,30 +75,32 @@ void json_parser_t::realloc_buffer(size_t size)
m_json[size] = '\0';
}
bool json_parser_t::parse_json(char* data, int64_t size, const pal::string_t& context)
bool json_parser_t::parse_json(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<flags, rapidjson::UTF8<>>(data);
#else // _WIN32
m_document.ParseInsitu<rapidjson::ParseFlag::kParseCommentsFlag>(data);
#endif // _WIN32
| rapidjson::ParseFlag::kParseCommentsFlag;
m_document.Parse<flags, rapidjson::UTF8<>>(m_json.data());
#else
m_document.ParseInsitu<rapidjson::ParseFlag::kParseCommentsFlag>(m_json.data());
#endif
if (m_document.HasParseError())
{
int line, column;
size_t offset = m_document.GetErrorOffset();
get_line_column_from_offset(data, size, offset, &line, &column);
get_line_column_from_offset(m_json, 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;
}
......@@ -128,37 +130,5 @@ 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(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);
}
return parse_json(context);
}
......@@ -9,7 +9,6 @@
#include "rapidjson/document.h"
#include "rapidjson/fwd.h"
#include <vector>
#include "bundle/info.h"
class json_parser_t {
public:
......@@ -22,15 +21,12 @@ class json_parser_t {
using document_t = rapidjson::GenericDocument<internal_encoding_type_t>;
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);
json_parser_t()
: m_bundle_data(nullptr)
, m_bundle_location(nullptr) {}
~json_parser_t();
bool parse_file(const pal::string_t& path)
{
pal::ifstream_t file{path};
return parse_stream(file, path);
}
private:
// This is a vector of char and not pal::char_t because JSON data
......@@ -40,12 +36,8 @@ class json_parser_t {
std::vector<char> 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(char* data, int64_t size, const pal::string_t& context);
bool parse_json(const pal::string_t& context);
};
#endif // __JSON_PARSER_H__
......@@ -9,7 +9,6 @@
#include "runtime_config.h"
#include "trace.h"
#include "utils.h"
#include "bundle/info.h"
#include <cassert>
// The semantics of applying the runtimeconfig.json values follows, in the following steps from
......@@ -339,8 +338,6 @@ 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))
{
......@@ -401,7 +398,7 @@ bool runtime_config_t::ensure_parsed()
trace::verbose(_X("Did not successfully parse the runtimeconfig.dev.json"));
}
if (!bundle::info_t::config_t::probe(m_path) && !pal::file_exists(m_path))
if (!pal::file_exists(m_path))
{
// Not existing is not an error.
return true;
......
......@@ -66,7 +66,6 @@ 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)
{
......
......@@ -11,7 +11,8 @@
#include "utils.h"
#if defined(FEATURE_APPHOST)
#include "bundle_marker.h"
#include "cli/apphost/bundle/marker.h"
#include "cli/apphost/bundle/runner.h"
#if defined(_WIN32)
#include "cli/apphost/apphost.windows.h"
......@@ -83,14 +84,6 @@ 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[])
......@@ -104,8 +97,8 @@ int exe_start(const int argc, const pal::char_t* argv[])
pal::string_t app_path;
pal::string_t app_root;
bool requires_hostfxr_startupinfo_interface = false;
bool requires_v2_hostfxr_interface = false;
#if defined(FEATURE_APPHOST)
pal::string_t embedded_app_name;
if (!is_exe_enabled_for_execution(&embedded_app_name))
......@@ -122,17 +115,29 @@ 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_hostfxr_startupinfo_interface = true;
requires_v2_hostfxr_interface = true;
}
app_path.assign(get_directory(host_path));
append_path(&app_path, embedded_app_name.c_str());
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;
}
if (bundle_marker_t::is_bundle())
app_path.assign(bundle_runner.extraction_dir());
}
else
{
trace::info(_X("Detected Single-File app bundle"));
app_path.assign(get_directory(host_path));
}
else if (!pal::realpath(&app_path))
append_path(&app_path, embedded_app_name.c_str());
if (!pal::realpath(&app_path))
{
trace::error(_X("The application to execute does not exist: '%s'."), app_path.c_str());
return StatusCode::LibHostAppRootFindFailure;
......@@ -195,88 +200,58 @@ int exe_start(const int argc, const pal::char_t* argv[])
// Obtain the entrypoints.
int rc;
#if defined(FEATURE_APPHOST)
if (bundle_marker_t::is_bundle())
{
hostfxr_main_bundle_startupinfo_fn hostfxr_main_bundle_startupinfo = reinterpret_cast<hostfxr_main_bundle_startupinfo_fn>(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<hostfxr_set_error_writer_fn>(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)
hostfxr_main_startupinfo_fn main_fn_v2 = reinterpret_cast<hostfxr_main_startupinfo_fn>(pal::get_symbol(fxr, "hostfxr_main_startupinfo"));
if (main_fn_v2 != nullptr)
{
hostfxr_main_startupinfo_fn hostfxr_main_startupinfo = reinterpret_cast<hostfxr_main_startupinfo_fn>(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();
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] 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());
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());
hostfxr_set_error_writer_fn set_error_writer_fn = reinterpret_cast<hostfxr_set_error_writer_fn>(pal::get_symbol(fxr, "hostfxr_set_error_writer"));
hostfxr_set_error_writer_fn set_error_writer_fn = reinterpret_cast<hostfxr_set_error_writer_fn>(pal::get_symbol(fxr, "hostfxr_set_error_writer"));
{
propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer_fn);
rc = hostfxr_main_startupinfo(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr);
rc = main_fn_v2(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<int>(StatusCode::FrameworkMissingFailure) && !set_error_writer_fn)
{
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."));
trace::error(_X(""));
trace::error(_X(" - %s"), url.c_str());
}
}
}
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
{
if (requires_hostfxr_startupinfo_interface)
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<hostfxr_main_fn>(pal::get_symbol(fxr, "hostfxr_main"));
if (main_fn_v1 != nullptr)
{
trace::error(_X("The required library %s does not support relative app dll paths."), fxr_path.c_str());
rc = StatusCode::CoreHostEntryPointFailure;
rc = main_fn_v1(argc, argv);
}
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<hostfxr_main_fn>(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;
}
trace::error(_X("The required library %s does not contain the expected entry point."), fxr_path.c_str());
rc = StatusCode::CoreHostEntryPointFailure;
}
}
}
......
......@@ -90,7 +90,7 @@ bool ShouldIgnore(string fileRelativePath)
return fileRelativePath.Equals(RuntimeConfigDevJson);
}
bool ShouldExclude(FileType type, string relativePath)
bool ShouldExclude(FileType type)
{
switch (type)
{
......@@ -100,7 +100,7 @@ bool ShouldExclude(FileType type, string relativePath)
return false;
case FileType.NativeBinary:
return !Options.HasFlag(BundleOptions.BundleNativeBinaries) || Target.ShouldExclude(relativePath);
return !Options.HasFlag(BundleOptions.BundleNativeBinaries);
case FileType.Symbols:
return !Options.HasFlag(BundleOptions.BundleSymbolFiles);
......@@ -229,24 +229,22 @@ public string GenerateBundle(IReadOnlyList<FileSpec> fileSpecs)
foreach (var fileSpec in fileSpecs)
{
string relativePath = fileSpec.BundleRelativePath;
if (IsHost(relativePath))
if (IsHost(fileSpec.BundleRelativePath))
{
continue;
}
if (ShouldIgnore(relativePath))
if (ShouldIgnore(fileSpec.BundleRelativePath))
{
Tracer.Log($"Ignore: {relativePath}");
Tracer.Log($"Ignore: {fileSpec.BundleRelativePath}");
continue;
}
FileType type = InferType(fileSpec);
if (ShouldExclude(type, relativePath))
if (ShouldExclude(type))
{
Tracer.Log($"Exclude [{type}]: {relativePath}");
Tracer.Log($"Exclude [{type}]: {fileSpec.BundleRelativePath}");
fileSpec.Excluded = true;
continue;
}
......@@ -255,7 +253,7 @@ public string GenerateBundle(IReadOnlyList<FileSpec> fileSpecs)
{
FileType targetType = Target.TargetSpecificFileType(type);
long startOffset = AddToBundle(bundle, file, targetType);
FileEntry entry = BundleManifest.AddEntry(targetType, relativePath, startOffset, file.Length);
FileEntry entry = BundleManifest.AddEntry(targetType, fileSpec.BundleRelativePath, startOffset, file.Length);
Tracer.Log($"Embed: {entry}");
}
}
......
......@@ -59,7 +59,7 @@ public class Manifest
enum HeaderFlags : ulong
{
None = 0,
NetcoreApp3CompatMode = 1
NetcoreApp3CompatMode = 2
}
// Bundle ID is a string that is used to uniquely
......
......@@ -27,6 +27,8 @@ public class TargetInfo
public TargetInfo(OSPlatform? os, Version targetFrameworkVersion)
{
Version net50 = new Version(5, 0);
OS = os ?? HostOS;
FrameworkVersion = targetFrameworkVersion ?? net50;
......@@ -69,21 +71,6 @@ public override string ToString()
// 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";
}
}
......@@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Reflection;
namespace AppWithSubDirs
{
......@@ -11,7 +12,10 @@ public static class Program
{
public static void Main(string[] args)
{
string baseDir = Path.Combine(AppContext.BaseDirectory, "Sentence");
string baseDir =
Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
"Sentence");
string Part(string dir="", string subdir="", string subsubdir="")
{
......
......@@ -7,7 +7,9 @@
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;
......@@ -27,19 +29,24 @@ private void Bundle_Extraction_To_Specific_Path_Succeeds()
{
var fixture = sharedTestState.TestFixture.Copy();
var hostName = BundleHelper.GetHostName(fixture);
var appName = Path.GetFileNameWithoutExtension(hostName);
string publishPath = BundleHelper.GetPublishPath(fixture);
// Publish the bundle
string singleFile;
Bundler bundler = BundleHelper.BundleApp(fixture, out singleFile);
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();
// Verify expected files in the bundle directory
var bundleDir = BundleHelper.GetBundleDir(fixture);
bundleDir.Should().HaveFile(hostName);
bundleDir.Should().NotHaveFiles(BundleHelper.GetBundledFiles(fixture));
bundleDir.Should().NotHaveFiles(bundledFiles);
// Create a directory for extraction.
var extractBaseDir = BundleHelper.GetExtractionRootDir(fixture);
extractBaseDir.Should().NotHaveDirectory(BundleHelper.GetAppBaseName(fixture));
var extractBaseDir = BundleHelper.GetExtractDir(fixture);
extractBaseDir.Should().NotHaveDirectory(appName);
// Run the bundled app for the first time, and extract files to
// $DOTNET_BUNDLE_EXTRACT_BASE_DIR/<app>/bundle-id
......@@ -53,22 +60,27 @@ private void Bundle_Extraction_To_Specific_Path_Succeeds()
.And
.HaveStdOutContaining("Hello World");
var extractDir = BundleHelper.GetExtractionDir(fixture, bundler);
extractDir.Should().HaveFiles(BundleHelper.GetExtractedFiles(fixture));
extractDir.Should().NotHaveFiles(BundleHelper.GetFilesNeverExtracted(fixture));
string extractPath = Path.Combine(extractBaseDir.FullName, appName, bundler.BundleManifest.BundleID);
var extractDir = new DirectoryInfo(extractPath);
extractDir.Should().OnlyHaveFiles(bundledFiles);
extractDir.Should().NotHaveFile(hostName);
}
[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
string singleFile;
Bundler bundler = BundleHelper.BundleApp(fixture, out singleFile, BundleOptions.BundleNativeBinaries);
var bundleDir = BundleHelper.GetBundleDir(fixture);
var bundler = new Bundler(hostName, bundleDir.FullName, BundleOptions.BundleAllContent);
string singleFile = BundleHelper.GenerateBundle(bundler, publishPath);
// Create a directory for extraction.
var extractBaseDir = BundleHelper.GetExtractionRootDir(fixture);
var extractBaseDir = BundleHelper.GetExtractDir(fixture);
// Run the bunded app for the first time, and extract files to
// $DOTNET_BUNDLE_EXTRACT_BASE_DIR/<app>/bundle-id
......@@ -82,8 +94,8 @@ private void Bundle_extraction_is_reused()
.And
.HaveStdOutContaining("Hello World");
var appBaseName = BundleHelper.GetAppBaseName(fixture);
var extractDir = BundleHelper.GetExtractionDir(fixture, bundler);
string extractPath = Path.Combine(extractBaseDir.FullName, appName, bundler.BundleManifest.BundleID);
var extractDir = new DirectoryInfo(extractPath);
extractDir.Refresh();
DateTime firstWriteTime = extractDir.LastWriteTimeUtc;
......@@ -113,14 +125,20 @@ private void Bundle_extraction_can_recover_missing_files()
var fixture = sharedTestState.TestFixture.Copy();
var hostName = BundleHelper.GetHostName(fixture);
var appName = Path.GetFileNameWithoutExtension(hostName);
string publishPath = BundleHelper.GetPublishPath(fixture);
// Publish the bundle
string singleFile;
Bundler bundler = BundleHelper.BundleApp(fixture, out singleFile, BundleOptions.BundleNativeBinaries);
var bundleDir = BundleHelper.GetBundleDir(fixture);
var bundler = new Bundler(hostName, bundleDir.FullName, BundleOptions.BundleAllContent);
string singleFile = BundleHelper.GenerateBundle(bundler, publishPath);
// Compute bundled files
List<string> bundledFiles = bundler.BundleManifest.Files.Select(file => file.RelativePath).ToList();
// Create a directory for extraction.
var extractBaseDir = BundleHelper.GetExtractionRootDir(fixture);
var extractBaseDir = BundleHelper.GetExtractDir(fixture);
string extractPath = Path.Combine(extractBaseDir.FullName, appName, bundler.BundleManifest.BundleID);
var extractDir = new DirectoryInfo(extractPath);
// Run the bunded app for the first time, and extract files to
// $DOTNET_BUNDLE_EXTRACT_BASE_DIR/<app>/bundle-id
......@@ -134,14 +152,10 @@ private void Bundle_extraction_can_recover_missing_files()
.And
.HaveStdOutContaining("Hello World");
// 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)));
bundledFiles.ForEach(file => File.Delete(Path.Combine(extractPath, file)));
extractDir.Should().Exist();
extractDir.Should().NotHaveFiles(extractedFiles);
extractDir.Should().NotHaveFiles(bundledFiles);
// Run the bundled app again (recover deleted files)
Command.Create(singleFile)
......@@ -154,7 +168,7 @@ private void Bundle_extraction_can_recover_missing_files()
.And
.HaveStdOutContaining("Hello World");
extractDir.Should().HaveFiles(extractedFiles);
extractDir.Should().HaveFiles(bundledFiles);
}
......
......@@ -51,13 +51,11 @@ private void Bundle_can_be_renamed_while_running(bool renameFirstRun)
.CaptureStdOut()
.Start();
while (!File.Exists(waitFile) && !singleExe.Process.HasExited)
while (!File.Exists(waitFile))
{
Thread.Sleep(100);
}
Assert.True(File.Exists(waitFile));
File.Move(singleFile, renameFile);
File.Create(resumeFile).Close();
......
......@@ -2,12 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using BundleTests.Helpers;
using Microsoft.DotNet.Cli.Build.Framework;
using Microsoft.NET.HostModel.Bundle;
using Microsoft.DotNet.CoreSetup.Test;
using System;
using Xunit;
using Microsoft.DotNet.Cli.Build.Framework;
using BundleTests.Helpers;
using Microsoft.DotNet.CoreSetup.Test;
namespace AppHost.Bundle.Tests
{
......@@ -32,16 +31,11 @@ private void RunTheApp(string path)
.HaveStdOutContaining("Wow! We now say hello to the big world and you.");
}
// 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)
[Fact]
public void Bundled_Framework_dependent_App_Run_Succeeds()
{
var fixture = sharedTestState.TestFrameworkDependentFixture.Copy();
var singleFile = BundleHelper.BundleApp(fixture, options);
var singleFile = BundleHelper.BundleApp(fixture);
// Run the bundled app (extract files)
RunTheApp(singleFile);
......@@ -50,13 +44,11 @@ public void Bundled_Framework_dependent_App_Run_Succeeds(BundleOptions options)
RunTheApp(singleFile);
}
[InlineData(BundleOptions.BundleNativeBinaries)]
[InlineData(BundleOptions.BundleAllContent)]
[Theory]
public void Bundled_Self_Contained_App_Run_Succeeds(BundleOptions options)
[Fact]
public void Bundled_Self_Contained_App_Run_Succeeds()
{
var fixture = sharedTestState.TestSelfContainedFixture.Copy();
var singleFile = BundleHelper.BundleApp(fixture, options);
var singleFile = BundleHelper.BundleApp(fixture);
// Run the bundled app (extract files)
RunTheApp(singleFile);
......@@ -65,13 +57,11 @@ public void Bundled_Self_Contained_App_Run_Succeeds(BundleOptions options)
RunTheApp(singleFile);
}
[InlineData(BundleOptions.BundleNativeBinaries)]
[InlineData(BundleOptions.BundleAllContent)]
[Theory]
public void Bundled_With_Empty_File_Succeeds(BundleOptions options)
[Fact]
public void Bundled_With_Empty_File_Succeeds()
{
var fixture = sharedTestState.TestAppWithEmptyFileFixture.Copy();
var singleFile = BundleHelper.BundleApp(fixture, options);
var singleFile = BundleHelper.BundleApp(fixture);
// Run the app
RunTheApp(singleFile);
......
......@@ -40,32 +40,6 @@ public static string GetAppName(TestProjectFixture fixture)
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");
......@@ -76,28 +50,13 @@ public static DirectoryInfo GetBundleDir(TestProjectFixture fixture)
return Directory.CreateDirectory(Path.Combine(fixture.TestProject.ProjectDirectory, "bundle"));
}
public static string GetExtractionRootPath(TestProjectFixture fixture)
{
return Path.Combine(fixture.TestProject.ProjectDirectory, "extract");
}
public static DirectoryInfo GetExtractionRootDir(TestProjectFixture fixture)
public static DirectoryInfo GetExtractDir(TestProjectFixture fixture)
{
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));
return Directory.CreateDirectory(Path.Combine(fixture.TestProject.ProjectDirectory, "extract"));
}
/// Generate a bundle containind the (embeddable) files in sourceDir
public static string GenerateBundle(Bundler bundler, string sourceDir, string outputDir, bool copyExludedFiles=true)
public static string GenerateBundle(Bundler bundler, string sourceDir)
{
// Convert sourceDir to absolute path
sourceDir = Path.GetFullPath(sourceDir);
......@@ -114,22 +73,7 @@ public static string GenerateBundle(Bundler bundler, string sourceDir, string ou
fileSpecs.Add(new FileSpec(file, Path.GetRelativePath(sourceDir, file)));
}
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;
return bundler.GenerateBundle(fileSpecs);
}
// Bundle to a single-file
......@@ -137,40 +81,23 @@ public static string GenerateBundle(Bundler bundler, string sourceDir, string ou
// 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.
public static Bundler BundleApp(TestProjectFixture fixture,
out string singleFile,
BundleOptions options = BundleOptions.BundleNativeBinaries,
Version targetFrameworkVersion = null,
bool copyExcludedFiles = true)
//
// 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)
{
var hostName = GetHostName(fixture);
string publishPath = GetPublishPath(fixture);
var bundleDir = GetBundleDir(fixture);
var bundler = new Bundler(hostName, bundleDir.FullName, options, targetFrameworkVersion: targetFrameworkVersion);
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);
string singleFile = GenerateBundle(bundler, publishPath);
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
......
......@@ -7,6 +7,7 @@
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
......@@ -40,7 +41,9 @@ private void BundleRun(TestProjectFixture fixture, string publishPath, string si
RunTheApp(Path.Combine(publishPath, hostName));
// Bundle to a single-file
string singleFile = BundleHelper.BundleApp(fixture);
// Bundle all content, until the host can handle other scenarios.
Bundler bundler = new Bundler(hostName, singleFileDir, BundleOptions.BundleAllContent);
string singleFile = BundleHelper.GenerateBundle(bundler, publishPath);
// Run the extracted app
RunTheApp(singleFile);
......
......@@ -3,6 +3,7 @@
// 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;
......
......@@ -92,10 +92,18 @@ public void TestWithDuplicateEntriesFails()
public void TestFilesAlwaysBundled(BundleOptions options)
{
var fixture = sharedTestState.TestFixture.Copy();
var bundler = BundleHelper.Bundle(fixture, options);
var bundledFiles = BundleHelper.GetBundledFiles(fixture);
Array.ForEach(bundledFiles, file => bundler.BundleManifest.Contains(file).Should().BeTrue());
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();
}
[InlineData(BundleOptions.None)]
......@@ -107,16 +115,20 @@ public void TestFilesAlwaysBundled(BundleOptions options)
public void TestFilesNeverBundled(BundleOptions options)
{
var fixture = sharedTestState.TestFixture.Copy();
var appBaseName = BundleHelper.GetAppBaseName(fixture);
var hostName = BundleHelper.GetHostName(fixture);
var appName = Path.GetFileNameWithoutExtension(hostName);
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, $"{appBaseName}.runtimeconfig.json"),
Path.Combine(publishPath, $"{appBaseName}.runtimeconfig.dev.json"));
File.Copy(Path.Combine(publishPath, $"{appName}.runtimeconfig.json"),
Path.Combine(publishPath, $"{appName}.runtimeconfig.dev.json"));
var bundler = BundleHelper.Bundle(fixture, options);
var bundler = new Bundler(hostName, bundleDir.FullName, options);
BundleHelper.GenerateBundle(bundler, publishPath);
bundler.BundleManifest.Contains($"{appBaseName}.runtimeconfig.dev.json").Should().BeFalse();
bundler.BundleManifest.Contains($"{appName}.runtimeconfig.dev.json").Should().BeFalse();
}
[InlineData(BundleOptions.None)]
......@@ -125,10 +137,16 @@ public void TestFilesNeverBundled(BundleOptions options)
public void TestBundlingSymbols(BundleOptions options)
{
var fixture = sharedTestState.TestFixture.Copy();
var appBaseName = BundleHelper.GetAppBaseName(fixture);
var bundler = BundleHelper.Bundle(fixture, options);
bundler.BundleManifest.Contains($"{appBaseName}.pdb").Should().Be(options.HasFlag(BundleOptions.BundleSymbolFiles));
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));
}
[InlineData(BundleOptions.None)]
......@@ -137,28 +155,30 @@ public void TestBundlingSymbols(BundleOptions options)
public void TestBundlingNativeBinaries(BundleOptions options)
{
var fixture = sharedTestState.TestFixture.Copy();
var coreclr = Path.GetFileName(fixture.TestProject.CoreClrDll);
var bundler = BundleHelper.Bundle(fixture, options);
bundler.BundleManifest.Contains($"{coreclr}").Should().Be(options.HasFlag(BundleOptions.BundleNativeBinaries));
}
var hostName = BundleHelper.GetHostName(fixture);
var hostfxr = Path.GetFileName(fixture.TestProject.HostFxrDll);
string publishPath = BundleHelper.GetPublishPath(fixture);
var bundleDir = BundleHelper.GetBundleDir(fixture);
[Fact]
public void TestFileSizes()
{
var fixture = sharedTestState.TestFixture.Copy();
var bundler = BundleHelper.Bundle(fixture);
var publishPath = BundleHelper.GetPublishPath(fixture);
var bundler = new Bundler(hostName, bundleDir.FullName, options);
BundleHelper.GenerateBundle(bundler, publishPath);
bundler.BundleManifest.Files.ForEach(file =>
Assert.True(file.Size == new FileInfo(Path.Combine(publishPath, file.RelativePath)).Length));
bundler.BundleManifest.Contains($"{hostfxr}").Should().Be(options.HasFlag(BundleOptions.BundleNativeBinaries));
}
[Fact]
public void TestAssemblyAlignment()
{
var fixture = sharedTestState.TestFixture.Copy();
var bundler = BundleHelper.Bundle(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, BundleOptions.BundleAllContent);
BundleHelper.GenerateBundle(bundler, publishPath);
bundler.BundleManifest.Files.ForEach(file =>
Assert.True((file.Type != FileType.Assembly) || (file.Offset % Bundler.AssemblyAlignment == 0)));
......@@ -168,7 +188,13 @@ public void TestAssemblyAlignment()
public void TestWithAdditionalContentAfterBundleMetadata()
{
var fixture = sharedTestState.TestFixture.Copy();
string singleFile = BundleHelper.BundleApp(fixture);
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);
using (var file = File.OpenWrite(singleFile))
{
......
......@@ -16,7 +16,6 @@ public class TestApp : TestArtifact
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; }
......@@ -56,7 +55,6 @@ private void LoadAssets()
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"));
}
}
}
......@@ -22,7 +22,6 @@ public class TestProject : TestArtifact
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; }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册