Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract bundled files when IncludeAllContentForSelfExtract is set #42435

Merged
merged 19 commits into from
Sep 30, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/installer/corehost/cli/bundle/file_entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ bool file_entry_t::is_valid() const
static_cast<file_type_t>(m_type) < file_type_t::__last;
}

file_entry_t file_entry_t::read(reader_t &reader)
file_entry_t file_entry_t::read(reader_t &reader, bool force_extraction)
{
// First read the fixed-sized portion of file-entry
const file_entry_fixed_t* fixed_data = reinterpret_cast<const file_entry_fixed_t*>(reader.read_direct(sizeof(file_entry_fixed_t)));
file_entry_t entry(fixed_data);
file_entry_t entry(fixed_data, force_extraction);

if (!entry.is_valid())
{
Expand All @@ -35,6 +35,9 @@ file_entry_t file_entry_t::read(reader_t &reader)

bool file_entry_t::needs_extraction() const
{
if (m_force_extraction)
return true;

switch (m_type)
{
case file_type_t::deps_json:
Expand Down
11 changes: 8 additions & 3 deletions src/installer/corehost/cli/bundle/file_entry.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,16 @@ namespace bundle
, m_type(file_type_t::__last)
, m_relative_path()
, m_disabled(false)
, m_force_extraction(false)
{
}

file_entry_t(const file_entry_fixed_t *fixed_data)
:m_relative_path()
file_entry_t(
const file_entry_fixed_t *fixed_data,
const bool force_extraction = false)
: m_relative_path()
, m_disabled(false)
, m_force_extraction(force_extraction)
{
// File_entries in the bundle-manifest are expected to be used
// beyond startup (for loading files directly from bundle, lazy extraction, etc.).
Expand All @@ -64,7 +68,7 @@ namespace bundle
bool needs_extraction() const;
bool matches(const pal::string_t& path) const { return (pal::pathcmp(relative_path(), path) == 0) && !is_disabled(); }

static file_entry_t read(reader_t &reader);
static file_entry_t read(reader_t &reader, bool force_extraction);

private:
int64_t m_offset;
Expand All @@ -76,6 +80,7 @@ namespace bundle
// So in order to make sure that the servicing location is used, the file entry in the bundle is marked as "disabled"
// in such case, and the lookup logic will behave as if the file is not present in the bundle.
bool m_disabled;
bool m_force_extraction;
bool is_valid() const;
};
}
Expand Down
8 changes: 4 additions & 4 deletions src/installer/corehost/cli/bundle/manifest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

using namespace bundle;

manifest_t manifest_t::read(reader_t& reader, int32_t num_files)
manifest_t manifest_t::read(reader_t& reader, header_t& header)
{
manifest_t manifest;

for (int32_t i = 0; i < num_files; i++)
for (int32_t i = 0; i < header.num_embedded_files(); i++)
{
file_entry_t entry = file_entry_t::read(reader);
file_entry_t entry = file_entry_t::read(reader, header.is_netcoreapp3_compat_mode());
manifest.files.push_back(std::move(entry));
manifest.m_need_extraction |= entry.needs_extraction();
manifest.m_files_need_extraction |= entry.needs_extraction();
}

return manifest;
Expand Down
14 changes: 10 additions & 4 deletions src/installer/corehost/cli/bundle/manifest.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <list>
#include "file_entry.h"
#include "header.h"

namespace bundle
{
Expand All @@ -16,16 +17,21 @@ namespace bundle
{
public:
manifest_t()
: m_need_extraction(false) {}
: m_files_need_extraction(false)
{
}

std::vector<file_entry_t> files;

static manifest_t read(reader_t &reader, int32_t num_files);
static manifest_t read(reader_t &reader, header_t &header);

bool files_need_extraction() { return m_need_extraction; }
bool files_need_extraction() const
{
return m_files_need_extraction;
}

private:
bool m_need_extraction;
bool m_files_need_extraction;
};
}
#endif // __MANIFEST_H__
5 changes: 3 additions & 2 deletions src/installer/corehost/cli/bundle/runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ StatusCode runner_t::extract()
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());
m_manifest = manifest_t::read(reader, m_header);

// Extract the files if necessary
if (m_manifest.files_need_extraction())
Expand Down Expand Up @@ -65,7 +65,8 @@ bool runner_t::probe(const pal::string_t& relative_path, int64_t* offset, int64_
{
const bundle::file_entry_t* entry = probe(relative_path);

if (entry == nullptr)
// Do not report extracted entries - those should be reported through either TPA or resource paths
if (entry == nullptr || entry->needs_extraction())
{
return false;
}
Expand Down
16 changes: 12 additions & 4 deletions src/installer/corehost/cli/hostpolicy/deps_resolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,26 +172,34 @@ class deps_resolver_t
return m_is_framework_dependent;
}

const pal::string_t &get_app_dir() const
const void get_app_dir(pal::string_t *app_dir) const
{
if (m_host_mode == host_mode_t::libhost)
{
static const pal::string_t s_empty;
return s_empty;
*app_dir = s_empty;
return;
}
*app_dir = m_app_dir;
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();
*app_dir = app->extraction_path();
}
}
}

return m_app_dir;
// Make sure the path ends with a directory separator
// This has been the behavior for a long time, and we should make it consistent
// for all cases.
if (app_dir->back() != DIR_SEPARATOR)
{
app_dir->append(1, DIR_SEPARATOR);
}
}

private:
Expand Down
12 changes: 11 additions & 1 deletion src/installer/corehost/cli/hostpolicy/hostpolicy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -369,11 +369,21 @@ int corehost_main_init(

if (bundle::info_t::is_single_file_bundle())
{
StatusCode status = bundle::runner_t::process_manifest_and_extract();
const bundle::runner_t* bundle = bundle::runner_t::app();
StatusCode status = bundle->process_manifest_and_extract();
if (status != StatusCode::Success)
{
return status;
}

if (bundle->is_netcoreapp3_compat_mode())
{
auto extracted_assembly = bundle->extraction_path();
auto app_name = hostpolicy_init.host_info.get_app_name() + _X(".dll");
append_path(&extracted_assembly, app_name.c_str());
assert(pal::file_exists(extracted_assembly));
hostpolicy_init.host_info.app_path = extracted_assembly;
}
}

return corehost_init(hostpolicy_init, argc, argv, location, args);
Expand Down
3 changes: 2 additions & 1 deletion src/installer/corehost/cli/hostpolicy/hostpolicy_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const a
}

// Build properties for CoreCLR instantiation
pal::string_t app_base = resolver.get_app_dir();
pal::string_t app_base;
resolver.get_app_dir(&app_base);
coreclr_properties.add(common_property::TrustedPlatformAssemblies, probe_paths.tpa.c_str());
coreclr_properties.add(common_property::NativeDllSearchDirectories, probe_paths.native.c_str());
coreclr_properties.add(common_property::PlatformResourceRoots, probe_paths.resources.c_str());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,71 @@
using System;
using System.IO;
using System.Reflection;

namespace SingleFileApiTests
{
public class Program
{
public static void Main(string[] args)
public static int Main(string[] args)
{
switch (args[0])
for (int i = 0; i < args.Length; i++)
{
case "fullyqualifiedname":
var module = typeof(object).Assembly.GetModules()[0];
Console.WriteLine("FullyQualifiedName: " + module.FullyQualifiedName);
Console.WriteLine("Name: " + module.Name);
return;

case "cmdlineargs":
Console.WriteLine(Environment.GetCommandLineArgs()[0]);
return;

case "codebase":
try
{
#pragma warning disable SYSLIB0012
_ = typeof(Program).Assembly.CodeBase;
#pragma warning restore SYSLIB0012
}
catch (NotSupportedException)
{
Console.WriteLine("CodeBase NotSupported");
return;
}
break;

case "appcontext":
var deps_files = AppContext.GetData("APP_CONTEXT_DEPS_FILES");
Console.WriteLine("APP_CONTEXT_DEPS_FILES: " + deps_files);
return;
string arg = args[i];
switch (arg)
{
case "appcontext":
var deps_files = AppContext.GetData("APP_CONTEXT_DEPS_FILES");
Console.WriteLine("APP_CONTEXT_DEPS_FILES: " + deps_files);
break;

case "executing_assembly_location":
Console.WriteLine("ExecutingAssembly.Location: " + Assembly.GetExecutingAssembly().Location);
break;

case "assembly_location":
string assemblyName = args[++i];
Console.WriteLine(assemblyName + " location: " + Assembly.Load(assemblyName).Location);
break;

case "cmdlineargs":
Console.WriteLine(Environment.GetCommandLineArgs()[0]);
break;

case "codebase":
try
{
#pragma warning disable SYSLIB0012
_ = typeof(Program).Assembly.CodeBase;
#pragma warning restore SYSLIB0012
}
catch (NotSupportedException)
{
Console.WriteLine("CodeBase NotSupported");
return -1;
}
break;

case "fullyqualifiedname":
var module = typeof(object).Assembly.GetModules()[0];
Console.WriteLine("FullyQualifiedName: " + module.FullyQualifiedName);
Console.WriteLine("Name: " + module.Name);
break;

case "trusted_platform_assemblies":
Console.WriteLine("TRUSTED_PLATFORM_ASSEMBLIES:");
foreach (var assemblyPath in ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")).Split(Path.PathSeparator))
{
Console.WriteLine(assemblyPath);
}
break;

default:
Console.WriteLine("test failure");
return -1;
}
}

Console.WriteLine("test failure");
return 0;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//

using System;
using System.IO;
using System.Linq;
using BundleTests.Helpers;
using Microsoft.DotNet.Cli.Build.Framework;
using Microsoft.DotNet.CoreSetup.Test;
using Microsoft.NET.HostModel.Bundle;
using Xunit;

namespace AppHost.Bundle.Tests
{
public class NetCoreApp3CompatModeTests : IClassFixture<NetCoreApp3CompatModeTests.SharedTestState>
{
private SharedTestState sharedTestState;

public NetCoreApp3CompatModeTests(SharedTestState fixture)
{
sharedTestState = fixture;
}

[Fact]
public void Bundle_Is_Extracted()
{
var fixture = sharedTestState.TestFixture.Copy();
string singleFile;
Bundler bundler = BundleHelper.BundleApp(fixture, out singleFile, BundleOptions.BundleAllContent);
var extractionBaseDir = BundleHelper.GetExtractionRootDir(fixture);

Command.Create(singleFile, "executing_assembly_location trusted_platform_assemblies assembly_location System.Console")
.CaptureStdOut()
.CaptureStdErr()
.EnvironmentVariable(BundleHelper.DotnetBundleExtractBaseEnvVariable, extractionBaseDir.FullName)
.Execute()
.Should()
.Pass()
// Validate that the main assembly is running from disk (and not from bundle)
.And.HaveStdOutContaining("ExecutingAssembly.Location: " + extractionBaseDir.FullName)
// Validate that TPA contains at least one framework assembly from the extraction directory
.And.HaveStdOutContaining("System.Runtime.dll")
// Validate that framework assembly is actually loaded from the extraction directory
.And.HaveStdOutContaining("System.Console location: " + extractionBaseDir.FullName);

var extractionDir = BundleHelper.GetExtractionDir(fixture, bundler);
var bundleFiles = BundleHelper.GetBundleDir(fixture).GetFiles().Select(file => file.Name).ToArray();
var publishedFiles = Directory.GetFiles(BundleHelper.GetPublishPath(fixture), searchPattern: "*", searchOption: SearchOption.AllDirectories)
.Select(file => Path.GetFileName(file))
.Except(bundleFiles)
.ToArray();
var bundlerFiles = BundleHelper.GetBundleDir(fixture).GetFiles();
extractionDir.Should().HaveFiles(publishedFiles);
}

public class SharedTestState : IDisposable
{
public TestProjectFixture TestFixture { get; set; }
public RepoDirectoriesProvider RepoDirectories { get; set; }

public SharedTestState()
{
RepoDirectories = new RepoDirectoriesProvider();
TestFixture = new TestProjectFixture("SingleFileApiTests", RepoDirectories);
TestFixture
.EnsureRestoredForRid(TestFixture.CurrentRid, RepoDirectories.CorehostPackages)
.PublishProject(runtime: TestFixture.CurrentRid, outputDirectory: BundleHelper.GetPublishPath(TestFixture));

// The above will publish the app as self-contained using the stock .NET Core SDK (typically from Program Files)
// In order to test the hosting components we need the hostpolicy and hostfxr built by the repo
string hostFxrFileName = RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostfxr");
File.Copy(
Path.Combine(RepoDirectories.Artifacts, "corehost", hostFxrFileName),
Path.Combine(BundleHelper.GetPublishPath(TestFixture), hostFxrFileName),
overwrite: true);

string hostPolicyFileName = RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostpolicy");
File.Copy(
Path.Combine(RepoDirectories.Artifacts, "corehost", hostPolicyFileName),
Path.Combine(BundleHelper.GetPublishPath(TestFixture), hostPolicyFileName),
overwrite: true);
}

public void Dispose()
{
TestFixture.Dispose();
}
}
}
}
Loading