Build an apphost with hostfxr and hostpolicy linked in (#35368)
authorVladimir Sadov <vsadov@microsoft.com>
Wed, 6 May 2020 01:05:50 +0000 (18:05 -0700)
committerGitHub <noreply@github.com>
Wed, 6 May 2020 01:05:50 +0000 (01:05 +0000)
* hostfxr: Build most of hostfxr as a static library

This is part of the work to create an apphost that bundles both hostfxr
and hostpolicy.  The main distinction between the static and shared
versions of hostfxr is that the static version contains a hostpolicy
resolver that references the hostpolicy symbols directly rather than
loading them from a DLL.

* hostpolicy: Build as a static library

This change is part of the work to enable an apphost that bundles both
hostfxr and hostpolicy.  There's no distinction between hostpolicy
that's built as a shared library and as a static library: the shared
library is built by linking an empty object file with the static
library.

* corehost: Allow linking of hostfxr and hostpolicy with apphost

Provide a hostfxr_iface class, that abstracts how the hostfxr functions called
by the early stage in the hosting layer is resolved.

* dotnet: Teach the muxer binary about hostfxr_iface

* apphost: Teach apphost about hostfxr_iface

This provides two implementations of hostfxr_iface: one for the static
apphost, which bundles hostfxr and hostpolicy, and another for the
conventional apphost, which loads them dynamically on startup.

* Add exports for hostfxr and policy

* Exports for unix

* EXPORTS_LINKER_OPTION

* use generateversionscript.awk from ENG

* Move fxr files out of static

* Fixes for Linux

* Fix for   win-x86

* move HEADERS next to SOURCES similarly to other files.

* PR feedback (simplifying hostpolicy_resolver::try_get_dir for static host)

* Publish static_apphost to Microsoft.NETCore.App.Host

* bind to entry points without probing, when in a static host.

* Add a test case

* renamed hostfxr_iface --> hostfxr_resolver_t

* renamed shared --> standalone

* rename static_apphost --> singlefilehost

* Signing exclusions for singlefilehost

* switched StaticHost tst to a different asset (mostly a copy of StandaloneApp)

Co-authored-by: Leandro Pereira <leandro.pereira@microsoft.com>
Co-authored-by: Swaroop Sridhar <swaroop.sridhar@microsoft.com>
35 files changed:
eng/SignCheckExclusionsFile.txt
eng/Signing.props
eng/native/functions.cmake
eng/native/generateexportedsymbols.awk [moved from src/coreclr/generateexportedsymbols.awk with 100% similarity]
eng/native/generateversionscript.awk [moved from src/coreclr/generateversionscript.awk with 100% similarity]
src/installer/corehost/cli/CMakeLists.txt
src/installer/corehost/cli/apphost/CMakeLists.txt
src/installer/corehost/cli/apphost/standalone/CMakeLists.txt [new file with mode: 0644]
src/installer/corehost/cli/apphost/standalone/hostfxr_resolver_t.cpp [new file with mode: 0644]
src/installer/corehost/cli/apphost/static/CMakeLists.txt [new file with mode: 0644]
src/installer/corehost/cli/apphost/static/hostfxr_resolver_t.cpp [new file with mode: 0644]
src/installer/corehost/cli/apphost/static/hostpolicy_resolver.cpp [new file with mode: 0644]
src/installer/corehost/cli/dotnet/CMakeLists.txt
src/installer/corehost/cli/exe.cmake
src/installer/corehost/cli/fxr/CMakeLists.txt
src/installer/corehost/cli/fxr/fx_muxer.cpp
src/installer/corehost/cli/fxr/hostpolicy_resolver.h
src/installer/corehost/cli/fxr/standalone/CMakeLists.txt [new file with mode: 0644]
src/installer/corehost/cli/fxr/standalone/hostfxr.def [new file with mode: 0644]
src/installer/corehost/cli/fxr/standalone/hostfxr_unixexports.src [new file with mode: 0644]
src/installer/corehost/cli/fxr/standalone/hostpolicy_resolver.cpp [moved from src/installer/corehost/cli/fxr/hostpolicy_resolver.cpp with 97% similarity]
src/installer/corehost/cli/fxr/static/CMakeLists.txt [new file with mode: 0644]
src/installer/corehost/cli/hostpolicy/CMakeLists.txt
src/installer/corehost/cli/hostpolicy/standalone/CMakeLists.txt [new file with mode: 0644]
src/installer/corehost/cli/hostpolicy/standalone/hostpolicy.def [new file with mode: 0644]
src/installer/corehost/cli/hostpolicy/standalone/hostpolicy_unixexports.src [new file with mode: 0644]
src/installer/corehost/cli/hostpolicy/static/CMakeLists.txt [new file with mode: 0644]
src/installer/corehost/cli/lib_static.cmake
src/installer/corehost/corehost.cpp
src/installer/corehost/hostfxr_resolver_t.h [new file with mode: 0644]
src/installer/pkg/projects/Microsoft.NETCore.DotNetAppHost/runtime.Windows_NT.Microsoft.NETCore.DotNetAppHost.props
src/installer/pkg/projects/netcoreapp/pkg/Microsoft.NETCore.App.Host.pkgproj
src/installer/test/Assets/TestProjects/StaticHostApp/Program.cs [new file with mode: 0644]
src/installer/test/Assets/TestProjects/StaticHostApp/StaticHostApp.csproj [new file with mode: 0644]
src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/StaticHost.cs [new file with mode: 0644]

index 2db3b79..a78ad40 100644 (file)
@@ -7,6 +7,7 @@
 ;; and SCD apps. If they are signed, the file that the SDK produces has an invalid signature and
 ;; can't be signed again. More info at https://github.com/dotnet/core-setup/pull/7549.
 *apphost.exe;;Template, https://github.com/dotnet/core-setup/pull/7549
+*singlefilehost.exe;;Template, https://github.com/dotnet/core-setup/pull/7549
 *comhost.dll;;Template, https://github.com/dotnet/core-setup/pull/7549
 *apphosttemplateapphostexe.exe;;Template, https://github.com/dotnet/core-setup/pull/7549
 *comhosttemplatecomhostdll.dll;;Template, https://github.com/dotnet/core-setup/pull/7549
index 05f2a8b..5ceb96a 100644 (file)
@@ -24,7 +24,7 @@
     <BundleInstallerExeArtifact Include="$(ArtifactsPackagesDir)**/*.exe" />
 
     <!-- apphost and comhost template files are not signed, by design. -->
-    <FileSignInfo Include="apphost.exe;comhost.dll" CertificateName="None" />
+    <FileSignInfo Include="apphost.exe;singlefilehost.exe;comhost.dll" CertificateName="None" />
   </ItemGroup>
 
   <ItemGroup Condition="'$(CrossTargetComponentFolder)' != ''">
index 060cff8..49b0064 100644 (file)
@@ -175,8 +175,8 @@ function(generate_exports_file)
 
   add_custom_command(
     OUTPUT ${outputFilename}
-    COMMAND ${AWK} -f ${CMAKE_SOURCE_DIR}/${AWK_SCRIPT} ${INPUT_LIST} >${outputFilename}
-    DEPENDS ${INPUT_LIST} ${CMAKE_SOURCE_DIR}/${AWK_SCRIPT}
+    COMMAND ${AWK} -f ${CLR_ENG_NATIVE_DIR}/${AWK_SCRIPT} ${INPUT_LIST} >${outputFilename}
+    DEPENDS ${INPUT_LIST} ${CLR_ENG_NATIVE_DIR}/${AWK_SCRIPT}
     COMMENT "Generating exports file ${outputFilename}"
   )
   set_source_files_properties(${outputFilename}
@@ -196,8 +196,8 @@ function(generate_exports_file_prefix inputFilename outputFilename prefix)
 
   add_custom_command(
     OUTPUT ${outputFilename}
-    COMMAND ${AWK} -f ${CMAKE_SOURCE_DIR}/${AWK_SCRIPT} ${AWK_VARS} ${inputFilename} >${outputFilename}
-    DEPENDS ${inputFilename} ${CMAKE_SOURCE_DIR}/${AWK_SCRIPT}
+    COMMAND ${AWK} -f ${CLR_ENG_NATIVE_DIR}/${AWK_SCRIPT} ${AWK_VARS} ${inputFilename} >${outputFilename}
+    DEPENDS ${inputFilename} ${CLR_ENG_NATIVE_DIR}/${AWK_SCRIPT}
     COMMENT "Generating exports file ${outputFilename}"
   )
   set_source_files_properties(${outputFilename}
index 15ce0fa..3c1bdb1 100644 (file)
@@ -1,10 +1,10 @@
 add_subdirectory(hostcommon)
 add_subdirectory(apphost)
 add_subdirectory(dotnet)
-add_subdirectory(fxr)
-add_subdirectory(hostpolicy)
 add_subdirectory(nethost)
 add_subdirectory(test_fx_ver)
+add_subdirectory(fxr)
+add_subdirectory(hostpolicy)
 
 add_subdirectory(test)
 
index ba01e32..ec7e8e3 100644 (file)
@@ -2,49 +2,5 @@
 # The .NET Foundation licenses this file to you under the MIT license.
 # See the LICENSE file in the project root for more information.
 
-project(apphost)
-set(DOTNET_PROJECT_NAME "apphost")
-
-# Add RPATH to the apphost binary that allows using local copies of shared libraries
-# dotnet core depends on for special scenarios when system wide installation of such
-# dependencies is not possible for some reason.
-# This cannot be enabled for MacOS (Darwin) since its RPATH works in a different way,
-# doesn't apply to libraries loaded via dlopen and most importantly, it is not transitive.
-if (NOT CLR_CMAKE_TARGET_OSX)
-    set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
-    set(CMAKE_INSTALL_RPATH "\$ORIGIN/netcoredeps")
-endif()
-
-set(SKIP_VERSIONING 1)
-
-set(SOURCES
-    ./bundle_marker.cpp
-)
-
-set(HEADERS
-    ./bundle_marker.h
-)
-
-if(CLR_CMAKE_TARGET_WIN32)
-    list(APPEND SOURCES
-        apphost.windows.cpp)
-
-    list(APPEND HEADERS
-        apphost.windows.h)
-endif()
-
-include(../exe.cmake)
-
-add_definitions(-DFEATURE_APPHOST=1)
-
-# Disable manifest generation into the file .exe on Windows
-if(CLR_CMAKE_TARGET_WIN32)
-    set_property(TARGET ${PROJECT_NAME} PROPERTY
-            LINK_FLAGS "/MANIFEST:NO"
-        )
-endif()
-
-# Specify non-default Windows libs to be used for Arm/Arm64 builds
-if (CLR_CMAKE_TARGET_WIN32 AND (CLR_CMAKE_TARGET_ARCH_ARM OR CLR_CMAKE_TARGET_ARCH_ARM64))
-    target_link_libraries(apphost Advapi32.lib shell32.lib)
-endif()
+add_subdirectory(static)
+add_subdirectory(standalone)
diff --git a/src/installer/corehost/cli/apphost/standalone/CMakeLists.txt b/src/installer/corehost/cli/apphost/standalone/CMakeLists.txt
new file mode 100644 (file)
index 0000000..60d9a10
--- /dev/null
@@ -0,0 +1,54 @@
+# 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.
+
+project(apphost)
+set(DOTNET_PROJECT_NAME "apphost")
+
+# Add RPATH to the apphost binary that allows using local copies of shared libraries
+# dotnet core depends on for special scenarios when system wide installation of such
+# dependencies is not possible for some reason.
+# This cannot be enabled for MacOS (Darwin) since its RPATH works in a different way,
+# doesn't apply to libraries loaded via dlopen and most importantly, it is not transitive.
+if (NOT CLR_CMAKE_TARGET_OSX)
+    set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
+    set(CMAKE_INSTALL_RPATH "\$ORIGIN/netcoredeps")
+endif()
+
+set(SKIP_VERSIONING 1)
+
+include_directories(..)
+
+set(SOURCES
+    ../bundle_marker.cpp
+    ./hostfxr_resolver_t.cpp
+)
+
+set(HEADERS
+    ../bundle_marker.h
+    ../../../hostfxr_resolver_t.h
+)
+
+if(CLR_CMAKE_TARGET_WIN32)
+    list(APPEND SOURCES
+        ../apphost.windows.cpp)
+
+    list(APPEND HEADERS
+        ../apphost.windows.h)
+endif()
+
+include(../../exe.cmake)
+
+add_definitions(-DFEATURE_APPHOST=1)
+
+# Disable manifest generation into the file .exe on Windows
+if(CLR_CMAKE_TARGET_WIN32)
+    set_property(TARGET ${PROJECT_NAME} PROPERTY
+            LINK_FLAGS "/MANIFEST:NO"
+        )
+endif()
+
+# Specify non-default Windows libs to be used for Arm/Arm64 builds
+if (CLR_CMAKE_TARGET_WIN32 AND (CLR_CMAKE_TARGET_ARCH_ARM OR CLR_CMAKE_TARGET_ARCH_ARM64))
+    target_link_libraries(apphost Advapi32.lib shell32.lib)
+endif()
diff --git a/src/installer/corehost/cli/apphost/standalone/hostfxr_resolver_t.cpp b/src/installer/corehost/cli/apphost/standalone/hostfxr_resolver_t.cpp
new file mode 100644 (file)
index 0000000..4f3c388
--- /dev/null
@@ -0,0 +1,61 @@
+// 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 <assert.h>
+
+#include "pal.h"
+#include "fxr_resolver.h"
+#include "trace.h"
+#include "hostfxr_resolver_t.h"
+
+hostfxr_main_bundle_startupinfo_fn hostfxr_resolver_t::resolve_main_bundle_startupinfo()
+{
+    assert(m_hostfxr_dll != nullptr);
+    return reinterpret_cast<hostfxr_main_bundle_startupinfo_fn>(pal::get_symbol(m_hostfxr_dll, "hostfxr_main_bundle_startupinfo"));
+}
+
+hostfxr_set_error_writer_fn hostfxr_resolver_t::resolve_set_error_writer()
+{
+    assert(m_hostfxr_dll != nullptr);
+    return reinterpret_cast<hostfxr_set_error_writer_fn>(pal::get_symbol(m_hostfxr_dll, "hostfxr_set_error_writer"));
+}
+
+hostfxr_main_startupinfo_fn hostfxr_resolver_t::resolve_main_startupinfo()
+{
+    assert(m_hostfxr_dll != nullptr);
+    return reinterpret_cast<hostfxr_main_startupinfo_fn>(pal::get_symbol(m_hostfxr_dll, "hostfxr_main_startupinfo"));
+}
+
+hostfxr_main_fn hostfxr_resolver_t::resolve_main_v1()
+{
+    assert(m_hostfxr_dll != nullptr);
+    return reinterpret_cast<hostfxr_main_fn>(pal::get_symbol(m_hostfxr_dll, "hostfxr_main"));
+}
+
+hostfxr_resolver_t::hostfxr_resolver_t(const pal::string_t& app_root)
+{
+    if (!fxr_resolver::try_get_path(app_root, &m_dotnet_root, &m_fxr_path))
+    {
+        m_status_code = StatusCode::CoreHostLibMissingFailure;
+    }
+    else if (pal::load_library(&m_fxr_path, &m_hostfxr_dll))
+    {
+        m_status_code = StatusCode::Success;
+    }
+    else
+    {
+        trace::error(_X("The library %s was found, but loading it from %s failed"), LIBFXR_NAME, m_fxr_path.c_str());
+        trace::error(_X("  - Installing .NET prerequisites might help resolve this problem."));
+        trace::error(_X("     %s"), DOTNET_CORE_INSTALL_PREREQUISITES_URL);
+        m_status_code = StatusCode::CoreHostLibLoadFailure;
+    }
+}
+
+hostfxr_resolver_t::~hostfxr_resolver_t()
+{
+    if (m_hostfxr_dll != nullptr)
+    {
+        pal::unload_library(m_hostfxr_dll);
+    }
+}
diff --git a/src/installer/corehost/cli/apphost/static/CMakeLists.txt b/src/installer/corehost/cli/apphost/static/CMakeLists.txt
new file mode 100644 (file)
index 0000000..967aebb
--- /dev/null
@@ -0,0 +1,63 @@
+# 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.
+
+project(singlefilehost)
+set(DOTNET_PROJECT_NAME "singlefilehost")
+
+# Add RPATH to the apphost binary that allows using local copies of shared libraries
+# dotnet core depends on for special scenarios when system wide installation of such
+# dependencies is not possible for some reason.
+# This cannot be enabled for MacOS (Darwin) since its RPATH works in a different way,
+# doesn't apply to libraries loaded via dlopen and most importantly, it is not transitive.
+if (NOT CLR_CMAKE_TARGET_OSX)
+    set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
+    set(CMAKE_INSTALL_RPATH "\$ORIGIN/netcoredeps")
+endif()
+
+set(SKIP_VERSIONING 1)
+
+include_directories(..)
+include_directories(../../json)
+
+set(SOURCES
+    ../bundle_marker.cpp
+    ./hostfxr_resolver_t.cpp
+    ./hostpolicy_resolver.cpp
+)
+
+set(HEADERS
+    ../bundle_marker.h
+    ../../../hostfxr_resolver_t.h
+)
+
+if(CLR_CMAKE_TARGET_WIN32)
+    list(APPEND SOURCES
+        ../apphost.windows.cpp)
+
+    list(APPEND HEADERS
+        ../apphost.windows.h)
+endif()
+
+include(../../exe.cmake)
+
+add_definitions(-DFEATURE_APPHOST=1)
+add_definitions(-DFEATURE_STATIC_HOST=1)
+
+# Disable manifest generation into the file .exe on Windows
+if(CLR_CMAKE_TARGET_WIN32)
+    set_property(TARGET ${PROJECT_NAME} PROPERTY
+            LINK_FLAGS "/MANIFEST:NO"
+        )
+endif()
+
+# Specify non-default Windows libs to be used for Arm/Arm64 builds
+if (CLR_CMAKE_TARGET_WIN32 AND (CLR_CMAKE_TARGET_ARCH_ARM OR CLR_CMAKE_TARGET_ARCH_ARM64))
+    target_link_libraries(singlefilehost Advapi32.lib shell32.lib)
+endif()
+
+target_link_libraries(singlefilehost
+    libhostfxr_static
+    libhostpolicy_static
+    libhostcommon
+)
diff --git a/src/installer/corehost/cli/apphost/static/hostfxr_resolver_t.cpp b/src/installer/corehost/cli/apphost/static/hostfxr_resolver_t.cpp
new file mode 100644 (file)
index 0000000..2c7e2b8
--- /dev/null
@@ -0,0 +1,63 @@
+// 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 <assert.h>
+#include "trace.h"
+#include "hostfxr.h"
+#include "hostfxr_resolver_t.h"
+
+extern "C"
+{
+    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);
+    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);
+    int HOSTFXR_CALLTYPE hostfxr_main(const int argc, const pal::char_t* argv[]);
+    hostfxr_error_writer_fn HOSTFXR_CALLTYPE hostfxr_set_error_writer(hostfxr_error_writer_fn error_writer);
+}
+
+hostfxr_main_bundle_startupinfo_fn hostfxr_resolver_t::resolve_main_bundle_startupinfo()
+{
+    assert(m_hostfxr_dll == nullptr);
+    return hostfxr_main_bundle_startupinfo;
+}
+
+hostfxr_set_error_writer_fn hostfxr_resolver_t::resolve_set_error_writer()
+{
+    assert(m_hostfxr_dll == nullptr);
+    return hostfxr_set_error_writer;
+}
+
+hostfxr_main_startupinfo_fn hostfxr_resolver_t::resolve_main_startupinfo()
+{
+    assert(m_hostfxr_dll == nullptr);
+    return hostfxr_main_startupinfo;
+}
+
+hostfxr_main_fn hostfxr_resolver_t::resolve_main_v1()
+{
+    assert(m_hostfxr_dll == nullptr);
+    assert(!"This function should not be called in a static host");
+    return nullptr; 
+}
+
+hostfxr_resolver_t::hostfxr_resolver_t(const pal::string_t& app_root)
+{
+    if (app_root.length() == 0)
+    {
+        trace::info(_X("Application root path is empty. This shouldn't happen"));
+        m_status_code = StatusCode::CoreHostLibMissingFailure;
+    }
+    else
+    {
+        trace::info(_X("Using internal fxr"));
+
+        m_dotnet_root.assign(app_root);
+        m_fxr_path.assign(app_root);
+
+        m_status_code = StatusCode::Success;
+    }
+}
+
+hostfxr_resolver_t::~hostfxr_resolver_t()
+{
+}
diff --git a/src/installer/corehost/cli/apphost/static/hostpolicy_resolver.cpp b/src/installer/corehost/cli/apphost/static/hostpolicy_resolver.cpp
new file mode 100644 (file)
index 0000000..0b2a356
--- /dev/null
@@ -0,0 +1,59 @@
+// 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 <cassert>
+#include <error_codes.h>
+#include <fx_definition.h>
+#include "hostpolicy_resolver.h"
+#include <pal.h>
+#include <trace.h>
+
+extern "C"
+{
+    int HOSTPOLICY_CALLTYPE corehost_load(const host_interface_t* init);
+    int HOSTPOLICY_CALLTYPE corehost_unload();
+    corehost_error_writer_fn HOSTPOLICY_CALLTYPE corehost_set_error_writer(corehost_error_writer_fn error_writer);
+    int HOSTPOLICY_CALLTYPE corehost_initialize(const corehost_initialize_request_t *init_request, int32_t options, /*out*/ corehost_context_contract *context_contract);
+    int HOSTPOLICY_CALLTYPE corehost_main(const int argc, const pal::char_t* argv[]);
+    int HOSTPOLICY_CALLTYPE corehost_main_with_output_buffer(const int argc, const pal::char_t* argv[], pal::char_t buffer[], int32_t buffer_size, int32_t* required_buffer_size);
+}
+
+int hostpolicy_resolver::load(
+    const pal::string_t& lib_dir,
+    pal::dll_t* dll,
+    hostpolicy_contract_t &hostpolicy_contract)
+{
+    static hostpolicy_contract_t contract;
+
+    trace::info(_X("Using internal hostpolicy"));
+
+    contract.load = corehost_load;
+    contract.unload = corehost_unload;
+    contract.set_error_writer = corehost_set_error_writer;
+    contract.initialize = corehost_initialize;
+    contract.corehost_main = corehost_main;
+    contract.corehost_main_with_output_buffer = corehost_main_with_output_buffer;
+
+    hostpolicy_contract = contract;
+    *dll = nullptr;
+
+    return StatusCode::Success;
+}
+
+bool hostpolicy_resolver::try_get_dir(
+    host_mode_t mode,
+    const pal::string_t& dotnet_root,
+    const fx_definition_vector_t& fx_definitions,
+    const pal::string_t& app_candidate,
+    const pal::string_t& specified_deps_file,
+    const std::vector<pal::string_t>& probe_realpaths,
+    pal::string_t* impl_dir)
+{
+    // static apphost is not supposed to be used in a framework-dependent app
+    assert(!get_app(fx_definitions).get_runtime_config().get_is_framework_dependent());
+    assert(mode == host_mode_t::apphost);
+
+    impl_dir->assign(dotnet_root);
+    return true;
+}
index 4527986..798ae40 100644 (file)
@@ -10,4 +10,8 @@ if(CLR_CMAKE_TARGET_WIN32)
         dotnet.manifest)
 endif()
 
+list(APPEND SOURCES
+    ../apphost/standalone/hostfxr_resolver_t.cpp
+)
+
 include(../exe.cmake)
index c9295d5..3acc130 100644 (file)
@@ -18,6 +18,9 @@ list(APPEND SOURCES
     ${CMAKE_CURRENT_LIST_DIR}/fxr_resolver.cpp
     ${CMAKE_CURRENT_LIST_DIR}/../corehost.cpp
 )
+list(APPEND HEADERS
+    ${CMAKE_CURRENT_LIST_DIR}/../hostfxr_resolver_t.h
+)
 
 add_executable(${DOTNET_PROJECT_NAME} ${SOURCES} ${RESOURCES})
 
index 216ecbf..ec7e8e3 100644 (file)
@@ -2,46 +2,5 @@
 # The .NET Foundation licenses this file to you under the MIT license.
 # See the LICENSE file in the project root for more information.
 
-project(hostfxr)
-
-set(DOTNET_PROJECT_NAME "hostfxr")
-
-# Include directories
-include_directories(../json)
-
-# CMake does not recommend using globbing since it messes with the freshness checks
-set(SOURCES
-    ./command_line.cpp
-    ./corehost_init.cpp
-    ./hostfxr.cpp
-    ./fx_muxer.cpp
-    ./fx_resolver.cpp
-    ./fx_resolver.messages.cpp
-    ./framework_info.cpp
-    ./host_context.cpp
-    ./hostpolicy_resolver.cpp
-    ./sdk_info.cpp
-    ./sdk_resolver.cpp
-)
-
-set(HEADERS
-    ../corehost_context_contract.h
-    ../hostpolicy.h
-    ../fx_definition.h
-    ../fx_reference.h
-    ../roll_fwd_on_no_candidate_fx_option.h
-    ./command_line.h
-    ./corehost_init.h
-    ./fx_muxer.h
-    ./fx_resolver.h
-    ./framework_info.h
-    ./host_context.h
-    ./hostpolicy_resolver.h
-    ./sdk_info.h
-    ./sdk_resolver.h
-)
-
-include(../lib.cmake)
-
-install_with_stripped_symbols(hostfxr TARGETS corehost)
-target_link_libraries(hostfxr libhostcommon)
+add_subdirectory(static)
+add_subdirectory(standalone)
index 31c8c17..f41e909 100644 (file)
@@ -66,16 +66,11 @@ namespace
     }
 }
 
-template<typename T>
 int load_hostpolicy(
     const pal::string_t& lib_dir,
     pal::dll_t* h_host,
-    hostpolicy_contract_t &hostpolicy_contract,
-    const char *main_entry_symbol,
-    T* main_fn)
+    hostpolicy_contract_t& hostpolicy_contract)
 {
-    assert(main_entry_symbol != nullptr && main_fn != nullptr);
-
     int rc = hostpolicy_resolver::load(lib_dir, h_host, hostpolicy_contract);
     if (rc != StatusCode::Success)
     {
@@ -83,11 +78,6 @@ int load_hostpolicy(
         return rc;
     }
 
-    // Obtain entrypoint symbol
-    *main_fn = reinterpret_cast<T>(pal::get_symbol(*h_host, main_entry_symbol));
-    if (*main_fn == nullptr)
-        return StatusCode::CoreHostEntryPointFailure;
-
     return StatusCode::Success;
 }
 
@@ -114,7 +104,18 @@ static int execute_app(
     hostpolicy_contract_t hostpolicy_contract{};
     corehost_main_fn host_main = nullptr;
 
-    int code = load_hostpolicy(impl_dll_dir, &hostpolicy_dll, hostpolicy_contract, "corehost_main", &host_main);
+    int code = load_hostpolicy(impl_dll_dir, &hostpolicy_dll, hostpolicy_contract);
+
+    // Obtain entrypoint symbol
+    if (code == StatusCode::Success)
+    {
+        host_main = hostpolicy_contract.corehost_main;
+        if (host_main == nullptr)
+        {
+            code = StatusCode::CoreHostEntryPointFailure;
+        }
+    }
+
     if (code != StatusCode::Success)
     {
         handle_initialize_failure_or_abort();
@@ -164,7 +165,18 @@ static int execute_host_command(
     hostpolicy_contract_t hostpolicy_contract{};
     corehost_main_with_output_buffer_fn host_main = nullptr;
 
-    int code = load_hostpolicy(impl_dll_dir, &hostpolicy_dll, hostpolicy_contract, "corehost_main_with_output_buffer", &host_main);
+    int code = load_hostpolicy(impl_dll_dir, &hostpolicy_dll, hostpolicy_contract);
+
+    // Obtain entrypoint symbol
+    if (code == StatusCode::Success)
+    {
+        host_main = hostpolicy_contract.corehost_main_with_output_buffer;
+        if (host_main == nullptr)
+        {
+            code = StatusCode::CoreHostEntryPointFailure;
+        }
+    }
+
     if (code != StatusCode::Success)
         return code;
 
@@ -471,7 +483,7 @@ namespace
 
         if (!hostpolicy_resolver::try_get_dir(mode, host_info.dotnet_root, fx_definitions, app_candidate, deps_file, probe_realpaths, &hostpolicy_dir))
         {
-            return CoreHostLibMissingFailure;
+            return StatusCode::CoreHostLibMissingFailure;
         }
 
         init.reset(new corehost_init_t(host_command, host_info, deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions));
index 4348a53..3512832 100644 (file)
@@ -20,6 +20,10 @@ struct hostpolicy_contract_t
     // 3.0+ contracts
     corehost_set_error_writer_fn set_error_writer;
     corehost_initialize_fn initialize;
+
+    // 5.0+ contracts
+    corehost_main_fn corehost_main;
+    corehost_main_with_output_buffer_fn corehost_main_with_output_buffer;
 };
 
 namespace hostpolicy_resolver
@@ -38,4 +42,4 @@ namespace hostpolicy_resolver
         pal::string_t* impl_dir);
 };
 
-#endif // __HOSTPOLICY_RESOLVER_H__
\ No newline at end of file
+#endif // __HOSTPOLICY_RESOLVER_H__
diff --git a/src/installer/corehost/cli/fxr/standalone/CMakeLists.txt b/src/installer/corehost/cli/fxr/standalone/CMakeLists.txt
new file mode 100644 (file)
index 0000000..0a2ea92
--- /dev/null
@@ -0,0 +1,65 @@
+# 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.
+
+project(hostfxr)
+
+set(DOTNET_PROJECT_NAME "hostfxr")
+
+# Include directories
+include_directories(../../json)
+include_directories(../../fxr)
+
+# CMake does not recommend using globbing since it messes with the freshness checks
+set(SOURCES
+    ./hostpolicy_resolver.cpp
+)
+
+set(HEADERS
+    ../command_line.h
+    ../corehost_init.h
+    ../fx_muxer.h
+    ../fx_resolver.h
+    ../framework_info.h
+    ../host_context.h
+    ../sdk_info.h
+    ../sdk_resolver.h
+    ../hostpolicy_resolver.h
+)
+
+if(CLR_CMAKE_TARGET_WIN32)
+    list(APPEND SOURCES
+        hostfxr.def)
+else(CLR_CMAKE_TARGET_WIN32)
+    set(DEF_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/hostfxr_unixexports.src)
+    set(EXPORTS_FILE ${CMAKE_CURRENT_BINARY_DIR}/hostfxr.exports)
+    generate_exports_file(${DEF_SOURCES} ${EXPORTS_FILE})
+
+    if(CLR_CMAKE_HOST_LINUX OR CLR_CMAKE_HOST_FREEBSD OR CLR_CMAKE_HOST_NETBSD)
+        # Add linker exports file option
+        set(EXPORTS_LINKER_OPTION -Wl,--version-script=${EXPORTS_FILE})
+    endif(CLR_CMAKE_HOST_LINUX OR CLR_CMAKE_HOST_FREEBSD OR CLR_CMAKE_HOST_NETBSD)
+
+    if(CLR_CMAKE_HOST_OSX)
+        # Add linker exports file option
+        set(EXPORTS_LINKER_OPTION -Wl,-exported_symbols_list,${EXPORTS_FILE})
+    endif(CLR_CMAKE_HOST_OSX)
+
+    if(CLR_CMAKE_HOST_SUNOS)
+        # Add linker exports file option
+        set(EXPORTS_LINKER_OPTION -Wl,-M,${EXPORTS_FILE})
+    endif(CLR_CMAKE_HOST_SUNOS)
+endif(CLR_CMAKE_TARGET_WIN32)
+
+include(../../lib.cmake)
+
+if(CLR_CMAKE_HOST_UNIX)
+    add_custom_target(hostfxr_exports DEPENDS ${EXPORTS_FILE})
+    add_dependencies(hostfxr hostfxr_exports)
+
+    set_property(TARGET hostfxr APPEND_STRING PROPERTY LINK_FLAGS ${EXPORTS_LINKER_OPTION})
+    set_property(TARGET hostfxr APPEND_STRING PROPERTY LINK_DEPENDS ${EXPORTS_FILE})
+endif(CLR_CMAKE_HOST_UNIX)
+
+install_with_stripped_symbols(hostfxr TARGETS corehost)
+target_link_libraries(hostfxr libhostcommon libhostfxr_static)
diff --git a/src/installer/corehost/cli/fxr/standalone/hostfxr.def b/src/installer/corehost/cli/fxr/standalone/hostfxr.def
new file mode 100644 (file)
index 0000000..c86139a
--- /dev/null
@@ -0,0 +1,21 @@
+; 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.
+
+EXPORTS
+    hostfxr_main_bundle_startupinfo
+    hostfxr_main_startupinfo
+    hostfxr_main
+    hostfxr_resolve_sdk
+    hostfxr_resolve_sdk2
+    hostfxr_get_available_sdks
+    hostfxr_get_native_search_directories
+    hostfxr_set_error_writer
+    hostfxr_initialize_for_dotnet_command_line
+    hostfxr_initialize_for_runtime_config
+    hostfxr_run_app
+    hostfxr_get_runtime_delegate
+    hostfxr_get_runtime_property_value
+    hostfxr_set_runtime_property_value
+    hostfxr_get_runtime_properties
+    hostfxr_close
diff --git a/src/installer/corehost/cli/fxr/standalone/hostfxr_unixexports.src b/src/installer/corehost/cli/fxr/standalone/hostfxr_unixexports.src
new file mode 100644 (file)
index 0000000..fcf85d0
--- /dev/null
@@ -0,0 +1,20 @@
+; 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.
+
+hostfxr_main_bundle_startupinfo
+hostfxr_main_startupinfo
+hostfxr_main
+hostfxr_resolve_sdk
+hostfxr_resolve_sdk2
+hostfxr_get_available_sdks
+hostfxr_get_native_search_directories
+hostfxr_set_error_writer
+hostfxr_initialize_for_dotnet_command_line
+hostfxr_initialize_for_runtime_config
+hostfxr_run_app
+hostfxr_get_runtime_delegate
+hostfxr_get_runtime_property_value
+hostfxr_set_runtime_property_value
+hostfxr_get_runtime_properties
+hostfxr_close
@@ -198,6 +198,9 @@ int hostpolicy_resolver::load(
         g_hostpolicy_contract.set_error_writer = reinterpret_cast<corehost_set_error_writer_fn>(pal::get_symbol(g_hostpolicy, "corehost_set_error_writer"));
         g_hostpolicy_contract.initialize = reinterpret_cast<corehost_initialize_fn>(pal::get_symbol(g_hostpolicy, "corehost_initialize"));
 
+        g_hostpolicy_contract.corehost_main = reinterpret_cast<corehost_main_fn>(pal::get_symbol(g_hostpolicy, "corehost_main"));
+        g_hostpolicy_contract.corehost_main_with_output_buffer = reinterpret_cast<corehost_main_with_output_buffer_fn>(pal::get_symbol(g_hostpolicy, "corehost_main_with_output_buffer"));
+
         // It's possible to not have corehost_set_error_writer and corehost_initialize. These were
         // introduced in 3.0, so 2.0 hostpolicy would not have the exports. In this case, we will
         // not propagate the error writer and errors will still be reported to stderr. Callers are
diff --git a/src/installer/corehost/cli/fxr/static/CMakeLists.txt b/src/installer/corehost/cli/fxr/static/CMakeLists.txt
new file mode 100644 (file)
index 0000000..16c0951
--- /dev/null
@@ -0,0 +1,45 @@
+# 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.
+
+project(hostfxr_static)
+
+set(DOTNET_PROJECT_NAME "hostfxr_static")
+
+# Include directories
+include_directories(../../json)
+include_directories(../../fxr)
+
+# CMake does not recommend using globbing since it messes with the freshness checks
+set(SOURCES
+    ../command_line.cpp
+    ../corehost_init.cpp
+    ../hostfxr.cpp
+    ../fx_muxer.cpp
+    ../fx_resolver.cpp
+    ../fx_resolver.messages.cpp
+    ../framework_info.cpp
+    ../host_context.cpp
+    ../sdk_info.cpp
+    ../sdk_resolver.cpp
+)
+
+set(HEADERS
+    ../../corehost_context_contract.h
+    ../../hostpolicy.h
+    ../../fx_definition.h
+    ../../fx_reference.h
+    ../../roll_fwd_on_no_candidate_fx_option.h
+    ../command_line.h
+    ../corehost_init.h
+    ../fx_muxer.h
+    ../fx_resolver.h
+    ../framework_info.h
+    ../host_context.h
+    ../sdk_info.h
+    ../sdk_resolver.h
+)
+
+set(SKIP_VERSIONING 1)
+set(BUILD_OBJECT_LIBRARY 1)
+include(../../lib_static.cmake)
index ca2c78f..ec7e8e3 100644 (file)
@@ -2,47 +2,5 @@
 # The .NET Foundation licenses this file to you under the MIT license.
 # See the LICENSE file in the project root for more information.
 
-project(hostpolicy)
-
-set(DOTNET_PROJECT_NAME "hostpolicy")
-
-# Include directories
-include_directories(../fxr)
-include_directories(../json)
-
-# CMake does not recommend using globbing since it messes with the freshness checks
-set(SOURCES
-    ./args.cpp
-    ./breadcrumbs.cpp
-    ./coreclr.cpp
-    ./deps_resolver.cpp
-    ./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
-    ./args.h
-    ./breadcrumbs.h
-    ./coreclr.h
-    ../corehost_context_contract.h
-    ./deps_resolver.h
-    ./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)
-
-install_with_stripped_symbols(hostpolicy TARGETS corehost)
-target_link_libraries(hostpolicy libhostcommon)
+add_subdirectory(static)
+add_subdirectory(standalone)
diff --git a/src/installer/corehost/cli/hostpolicy/standalone/CMakeLists.txt b/src/installer/corehost/cli/hostpolicy/standalone/CMakeLists.txt
new file mode 100644 (file)
index 0000000..d663154
--- /dev/null
@@ -0,0 +1,59 @@
+# 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.
+
+project(hostpolicy)
+
+set(DOTNET_PROJECT_NAME "hostpolicy")
+
+# CMake does not recommend using globbing since it messes with the freshness checks
+
+# Can't call add_library() without source files. Create an empty .c file,
+# then link with the static library just recently built.
+if (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/empty.cpp")
+       file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/empty.cpp" "")
+endif ()
+
+set(SOURCES
+    ${CMAKE_CURRENT_BINARY_DIR}/empty.cpp
+)
+
+set(HEADERS
+)
+
+if(CLR_CMAKE_TARGET_WIN32)
+    list(APPEND SOURCES
+        hostpolicy.def)
+else(CLR_CMAKE_TARGET_WIN32)
+    set(DEF_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/hostpolicy_unixexports.src)
+    set(EXPORTS_FILE ${CMAKE_CURRENT_BINARY_DIR}/hostpolicy.exports)
+    generate_exports_file(${DEF_SOURCES} ${EXPORTS_FILE})
+
+    if(CLR_CMAKE_HOST_LINUX OR CLR_CMAKE_HOST_FREEBSD OR CLR_CMAKE_HOST_NETBSD)
+        # Add linker exports file option
+        set(EXPORTS_LINKER_OPTION -Wl,--version-script=${EXPORTS_FILE})
+    endif(CLR_CMAKE_HOST_LINUX OR CLR_CMAKE_HOST_FREEBSD OR CLR_CMAKE_HOST_NETBSD)
+
+    if(CLR_CMAKE_HOST_OSX)
+        # Add linker exports file option
+        set(EXPORTS_LINKER_OPTION -Wl,-exported_symbols_list,${EXPORTS_FILE})
+    endif(CLR_CMAKE_HOST_OSX)
+
+    if(CLR_CMAKE_HOST_SUNOS)
+        # Add linker exports file option
+        set(EXPORTS_LINKER_OPTION -Wl,-M,${EXPORTS_FILE})
+    endif(CLR_CMAKE_HOST_SUNOS)
+endif(CLR_CMAKE_TARGET_WIN32)
+
+include(../../lib.cmake)
+
+if(CLR_CMAKE_HOST_UNIX)
+    add_custom_target(hostpolicy_exports DEPENDS ${EXPORTS_FILE})
+    add_dependencies(hostpolicy hostpolicy_exports)
+
+    set_property(TARGET hostpolicy APPEND_STRING PROPERTY LINK_FLAGS ${EXPORTS_LINKER_OPTION})
+    set_property(TARGET hostpolicy APPEND_STRING PROPERTY LINK_DEPENDS ${EXPORTS_FILE})
+endif(CLR_CMAKE_HOST_UNIX)
+
+install_with_stripped_symbols(hostpolicy TARGETS corehost)
+target_link_libraries(hostpolicy libhostcommon libhostpolicy_static)
diff --git a/src/installer/corehost/cli/hostpolicy/standalone/hostpolicy.def b/src/installer/corehost/cli/hostpolicy/standalone/hostpolicy.def
new file mode 100644 (file)
index 0000000..af03ab9
--- /dev/null
@@ -0,0 +1,12 @@
+; 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.
+
+EXPORTS
+    corehost_initialize
+    corehost_load
+    corehost_main
+    corehost_main_with_output_buffer
+    corehost_resolve_component_dependencies
+    corehost_set_error_writer
+    corehost_unload
diff --git a/src/installer/corehost/cli/hostpolicy/standalone/hostpolicy_unixexports.src b/src/installer/corehost/cli/hostpolicy/standalone/hostpolicy_unixexports.src
new file mode 100644 (file)
index 0000000..98f3e61
--- /dev/null
@@ -0,0 +1,11 @@
+; 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.
+
+corehost_initialize
+corehost_load
+corehost_main
+corehost_main_with_output_buffer
+corehost_resolve_component_dependencies
+corehost_set_error_writer
+corehost_unload
diff --git a/src/installer/corehost/cli/hostpolicy/static/CMakeLists.txt b/src/installer/corehost/cli/hostpolicy/static/CMakeLists.txt
new file mode 100644 (file)
index 0000000..6845e36
--- /dev/null
@@ -0,0 +1,47 @@
+# 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.
+
+project(hostpolicy_static)
+
+set(DOTNET_PROJECT_NAME "hostpolicy_static")
+
+# Include directories
+include_directories(../../fxr)
+include_directories(../../json)
+
+# CMake does not recommend using globbing since it messes with the freshness checks
+set(SOURCES
+    ../args.cpp
+    ../breadcrumbs.cpp
+    ../coreclr.cpp
+    ../deps_resolver.cpp
+    ../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
+    ../args.h
+    ../breadcrumbs.h
+    ../coreclr.h
+    ../deps_resolver.h
+    ../hostpolicy_context.h
+    ../hostpolicy_init.h
+    ../../hostpolicy.h
+    ../../corehost_context_contract.h
+    ../../bundle/dir_utils.h
+    ../../bundle/extractor.h
+    ../../bundle/file_entry.h
+    ../../bundle/manifest.h
+    ../../bundle/runner.h
+)
+
+set(SKIP_VERSIONING 1)
+set(BUILD_OBJECT_LIBRARY 1)
+include(../../lib_static.cmake)
index 8135e9c..00204df 100644 (file)
@@ -10,7 +10,11 @@ add_definitions(-D_NO_ASYNCRTIMP)
 add_definitions(-D_NO_PPLXIMP)
 add_definitions(-DEXPORT_SHARED_API=1)
 
-add_library(lib${DOTNET_PROJECT_NAME} STATIC ${SOURCES} ${RESOURCES})
+if (BUILD_OBJECT_LIBRARY)
+    add_library(lib${DOTNET_PROJECT_NAME} OBJECT ${SOURCES} ${RESOURCES})
+else ()
+    add_library(lib${DOTNET_PROJECT_NAME} STATIC ${SOURCES} ${RESOURCES})
+endif ()
 
 set_target_properties(lib${DOTNET_PROJECT_NAME} PROPERTIES MACOSX_RPATH TRUE)
 set_target_properties(lib${DOTNET_PROJECT_NAME} PROPERTIES PREFIX "")
index 55b8ed6..65cfb61 100644 (file)
@@ -9,6 +9,7 @@
 #include "fx_ver.h"
 #include "trace.h"
 #include "utils.h"
+#include "hostfxr_resolver_t.h"
 
 #if defined(FEATURE_APPHOST)
 #include "bundle_marker.h"
@@ -176,51 +177,41 @@ int exe_start(const int argc, const pal::char_t* argv[])
     app_path.append(_X(".dll"));
 #endif
 
-    pal::string_t dotnet_root;
-    pal::string_t fxr_path;
-    if (!fxr_resolver::try_get_path(app_root, &dotnet_root, &fxr_path))
-    {
-        return StatusCode::CoreHostLibMissingFailure;
-    }
+    hostfxr_resolver_t fxr{app_root};
 
-    // Load library
-    pal::dll_t fxr;
-    if (!pal::load_library(&fxr_path, &fxr))
+    // Obtain the entrypoints.
+    int rc = fxr.status_code();
+    if (rc != StatusCode::Success)
     {
-        trace::error(_X("The library %s was found, but loading it from %s failed"), LIBFXR_NAME, fxr_path.c_str());
-        trace::error(_X("  - Installing .NET prerequisites might help resolve this problem."));
-        trace::error(_X("     %s"), DOTNET_CORE_INSTALL_PREREQUISITES_URL);
-        return StatusCode::CoreHostLibLoadFailure;
+        return rc;
     }
 
-    // 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"));
+        auto hostfxr_main_bundle_startupinfo = fxr.resolve_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* dotnet_root_cstr = fxr.dotnet_root().empty() ? nullptr : fxr.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("Invoking fx resolver [%s] hostfxr_main_bundle_startupinfo"), fxr.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("Dotnet path: [%s]"), fxr.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);
+            auto set_error_writer = fxr.resolve_set_error_writer();
+            propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer);
             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());                  
+            trace::error(_X("The required library %s does not support single-file apps."), fxr.fxr_path().c_str());                    
             need_newer_framework_error();
             rc = StatusCode::FrameworkMissingFailure;
         }
@@ -228,60 +219,61 @@ int exe_start(const int argc, const pal::char_t* argv[])
     else
 #endif // defined(FEATURE_APPHOST)
     {
-        hostfxr_main_startupinfo_fn hostfxr_main_startupinfo = reinterpret_cast<hostfxr_main_startupinfo_fn>(pal::get_symbol(fxr, "hostfxr_main_startupinfo"));
+        auto hostfxr_main_startupinfo = fxr.resolve_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* dotnet_root_cstr = fxr.dotnet_root().empty() ? nullptr : fxr.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("Invoking fx resolver [%s] hostfxr_main_startupinfo"), fxr.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("Dotnet path: [%s]"), fxr.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"));
-            propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer_fn);
+            auto set_error_writer = fxr.resolve_set_error_writer();
+            propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer);
 
             rc = hostfxr_main_startupinfo(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr);
 
             // This check exists to provide an error message for UI apps when running 3.0 apps on 2.0 only hostfxr, which doesn't support error writer redirection. 
-            if (trace::get_error_writer() != nullptr && rc == static_cast<int>(StatusCode::FrameworkMissingFailure) && !set_error_writer_fn)
+            if (trace::get_error_writer() != nullptr && rc == static_cast<int>(StatusCode::FrameworkMissingFailure) && set_error_writer == nullptr)
             {
                 need_newer_framework_error();
             }
         }
+#if !defined(FEATURE_STATIC_HOST)
         else
         {
             if (requires_hostfxr_startupinfo_interface)
             {
-                trace::error(_X("The required library %s does not support relative app dll paths."), fxr_path.c_str());
+                trace::error(_X("The required library %s does not support relative app dll paths."), fxr.fxr_path().c_str());
                 rc = StatusCode::CoreHostEntryPointFailure;
             }
             else
             {
-                trace::info(_X("Invoking fx resolver [%s] v1"), fxr_path.c_str());
+                trace::info(_X("Invoking fx resolver [%s] v1"), fxr.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"));
+                auto main_fn_v1 = fxr.resolve_main_v1();
                 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());
+                    trace::error(_X("The required library %s does not contain the expected entry point."), fxr.fxr_path().c_str());
                     rc = StatusCode::CoreHostEntryPointFailure;
                 }
             }
         }
+#endif // defined(FEATURE_STATIC_HOST)
     }
 
-    pal::unload_library(fxr);
     return rc;
 }
 
diff --git a/src/installer/corehost/hostfxr_resolver_t.h b/src/installer/corehost/hostfxr_resolver_t.h
new file mode 100644 (file)
index 0000000..21047b5
--- /dev/null
@@ -0,0 +1,40 @@
+// 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 __HOSTFXR_RESOLVER_T_H__
+#define __HOSTFXR_RESOLVER_T_H__
+
+#include "hostfxr.h"
+#include "pal.h"
+#include "error_codes.h"
+
+class hostfxr_resolver_t
+{
+    public:
+        hostfxr_resolver_t(const pal::string_t& app_root);
+        ~hostfxr_resolver_t();
+
+        StatusCode status_code() const { return m_status_code; }
+
+        const pal::string_t& host_path() const { return m_host_path; }
+        const pal::string_t& dotnet_root() const { return m_dotnet_root; }
+        const pal::string_t& fxr_path() const { return m_fxr_path; }
+
+        hostfxr_main_bundle_startupinfo_fn resolve_main_bundle_startupinfo();
+        hostfxr_set_error_writer_fn resolve_set_error_writer();
+        hostfxr_main_startupinfo_fn resolve_main_startupinfo();
+        hostfxr_main_fn resolve_main_v1();
+
+    private:
+        pal::dll_t m_hostfxr_dll{nullptr};
+
+        pal::string_t m_host_path;
+        pal::string_t m_dotnet_root;
+        pal::string_t m_fxr_path;
+
+        bool m_requires_startupinfo_iface{false};
+        StatusCode m_status_code;
+};
+
+#endif // __HOSTFXR_RESOLVER_T_H__
index c74da91..6d52e19 100644 (file)
@@ -2,6 +2,7 @@
 <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
     <ArchitectureSpecificNativeFile Include="$(DotNetHostBinDir)/apphost.exe" />
+    <ArchitectureSpecificNativeFile Include="$(DotNetHostBinDir)/singlefilehost.exe" />
     <ArchitectureSpecificNativeFile Include="$(DotNetHostBinDir)/comhost.dll" />
     <ArchitectureSpecificNativeFile Include="$(DotNetHostBinDir)/ijwhost.dll" />
     <ArchitectureSpecificNativeFile Include="$(DotNetHostBinDir)/ijwhost.lib" />
index 10915e8..8daa78d 100644 (file)
@@ -15,6 +15,7 @@
   -->
   <ItemGroup>
     <HeatOutputFileElementToStabilize Include="native\apphost.exe" ReplacementId="apphosttemplateapphostexe" />
+    <HeatOutputFileElementToStabilize Include="native\singlefilehost.exe" ReplacementId="staticapphosttemplateapphostexe" />
     <HeatOutputFileElementToStabilize Include="native\comhost.dll" ReplacementId="comhosttemplatecomhostdll" />
   </ItemGroup>
 
@@ -29,4 +30,4 @@
   <PropertyGroup>
     <SkipBuildOnRuntimePackOnlyOS>true</SkipBuildOnRuntimePackOnlyOS>
   </PropertyGroup>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/src/installer/test/Assets/TestProjects/StaticHostApp/Program.cs b/src/installer/test/Assets/TestProjects/StaticHostApp/Program.cs
new file mode 100644 (file)
index 0000000..c631d81
--- /dev/null
@@ -0,0 +1,16 @@
+// 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.
+
+using System;
+
+namespace StaticHostApp
+{
+    public static class Program
+    {
+        public static void Main(string[] args)
+        {
+            Console.WriteLine("Hello World!");
+        }
+    }
+}
diff --git a/src/installer/test/Assets/TestProjects/StaticHostApp/StaticHostApp.csproj b/src/installer/test/Assets/TestProjects/StaticHostApp/StaticHostApp.csproj
new file mode 100644 (file)
index 0000000..eff31e8
--- /dev/null
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>$(NETCoreAppFramework)</TargetFramework>
+    <OutputType>Exe</OutputType>
+    <RuntimeIdentifier>$(TestTargetRid)</RuntimeIdentifier>
+    <RuntimeFrameworkVersion>$(MNAVersion)</RuntimeFrameworkVersion>
+  </PropertyGroup>
+
+  <ItemGroup>
+  </ItemGroup>
+
+</Project>
diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/StaticHost.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/StaticHost.cs
new file mode 100644 (file)
index 0000000..78b5c94
--- /dev/null
@@ -0,0 +1,98 @@
+// 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.
+
+using BundleTests.Helpers;
+using Microsoft.DotNet.Cli.Build.Framework;
+using Microsoft.DotNet.CoreSetup.Test;
+using Microsoft.NET.HostModel.AppHost;
+using Microsoft.NET.HostModel.Bundle;
+using System;
+using System.IO;
+using System.Threading;
+using Xunit;
+
+namespace AppHost.Bundle.Tests
+{
+    public class StaticHost : IClassFixture<StaticHost.SharedTestState>
+    {
+        private SharedTestState sharedTestState;
+
+        public StaticHost(SharedTestState fixture)
+        {
+            sharedTestState = fixture;
+        }
+
+        // This helper is used in lieu of SDK support for publishing apps using the singlefilehost.
+        // It replaces the apphost with singlefilehost, and along with appropriate app.dll updates in the host.
+        // For now, we leave behind the hostpolicy and hostfxr DLLs in the publish directory, because
+        // removing them requires deps.json update.
+        void ReplaceApphostWithStaticHost(TestProjectFixture fixture)
+        {
+            var staticHost = Path.Combine(fixture.RepoDirProvider.HostArtifacts,
+                                          RuntimeInformationExtensions.GetExeFileNameForCurrentPlatform("singlefilehost"));
+            HostWriter.CreateAppHost(staticHost,
+                                     BundleHelper.GetHostPath(fixture),
+                                     BundleHelper.GetAppPath(fixture));
+
+        }
+
+        [Fact]
+        private void Can_Run_App_With_StatiHost()
+        {
+            var fixture = sharedTestState.TestFixture.Copy();
+            var appExe = BundleHelper.GetHostPath(fixture);
+
+            ReplaceApphostWithStaticHost(fixture);
+
+            Command.Create(appExe)
+                .CaptureStdErr()
+                .CaptureStdOut()
+                .Execute()
+                .Should()
+                .Pass()
+                .And
+                .HaveStdOutContaining("Hello World");
+        }
+
+        [Fact]
+        private void Can_Run_SingleFile_App_With_StatiHost()
+        {
+            var fixture = sharedTestState.TestFixture.Copy();
+
+            ReplaceApphostWithStaticHost(fixture);
+
+            string singleFile = BundleHelper.BundleApp(fixture);
+
+            Command.Create(singleFile)
+                .CaptureStdErr()
+                .CaptureStdOut()
+                .Execute()
+                .Should()
+                .Pass()
+                .And
+                .HaveStdOutContaining("Hello World");
+        }
+
+        public class SharedTestState : IDisposable
+        {
+            public TestProjectFixture TestFixture { get; set; }
+            public RepoDirectoriesProvider RepoDirectories { get; set; }
+
+            public SharedTestState()
+            {
+                RepoDirectories = new RepoDirectoriesProvider();
+                TestFixture = new TestProjectFixture("StaticHostApp", RepoDirectories);
+                TestFixture
+                    .EnsureRestoredForRid(TestFixture.CurrentRid, RepoDirectories.CorehostPackages)
+                    .PublishProject(runtime: TestFixture.CurrentRid, 
+                                    outputDirectory: BundleHelper.GetPublishPath(TestFixture));
+            }
+
+            public void Dispose()
+            {
+                TestFixture.Dispose();
+            }
+        }
+    }
+}