1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use crate::{
    dlopen2::wrapper::Container,
    error::{HostingError, HostingResult},
    nethost::LoadHostfxrError,
    pdcstring::PdCString,
};
use derive_more::From;
use std::{
    env::consts::EXE_SUFFIX,
    ffi::OsString,
    path::{Path, PathBuf},
    sync::Arc,
};

pub(crate) type HostfxrLibrary = Container<crate::bindings::hostfxr::wrapper_option::Hostfxr>;
pub(crate) type SharedHostfxrLibrary = Arc<HostfxrLibrary>;
#[allow(clippy::cast_possible_wrap)]
pub(crate) const UNSUPPORTED_HOST_VERSION_ERROR_CODE: i32 =
    HostingError::HostApiUnsupportedVersion.value() as i32;

/// A struct representing a loaded hostfxr library.
#[derive(Clone, From)]
pub struct Hostfxr {
    /// The underlying hostfxr library.
    pub lib: SharedHostfxrLibrary,
    pub(crate) dotnet_exe: PdCString,
}

fn find_dotnet_bin(hostfxr_path: impl AsRef<Path>) -> PathBuf {
    let mut p = hostfxr_path.as_ref().to_path_buf();
    loop {
        if let Some(dir) = p.file_name() {
            if dir == "dotnet" || dir == ".dotnet" {
                break;
            }
            p.pop();
        } else {
            p.clear();
            break;
        }
    }
    p.push("dotnet");
    let mut p = OsString::from(p);
    p.extend(Path::new(EXE_SUFFIX));
    PathBuf::from(p)
}

impl Hostfxr {
    /// Loads the hostfxr library from the given path.
    pub fn load_from_path(path: impl AsRef<Path>) -> Result<Self, crate::dlopen2::Error> {
        let path = path.as_ref();
        let lib = SharedHostfxrLibrary::new(unsafe { Container::load(path) }?);

        // Some APIs of hostfxr.dll require a path to the dotnet executable, so we try to locate it here based on the hostfxr path.
        let dotnet_exe = PdCString::from_os_str(find_dotnet_bin(path)).unwrap();

        Ok(Self { lib, dotnet_exe })
    }

    /// Locates the hostfxr library using [`nethost`](crate::nethost) and loads it.
    #[cfg(feature = "nethost")]
    pub fn load_with_nethost() -> Result<Self, LoadHostfxrError> {
        crate::nethost::load_hostfxr()
    }

    /// Returns the path to the dotnet root.
    #[must_use]
    pub fn get_dotnet_root(&self) -> PathBuf {
        self.get_dotnet_exe().parent().unwrap().to_owned()
    }

    /// Returns the path to the dotnet executable of the same installation as hostfxr.
    #[must_use]
    pub fn get_dotnet_exe(&self) -> PathBuf {
        self.dotnet_exe.to_os_string().into()
    }
}

/// Either the exit code of the app if it ran successful, otherwise the error from the hosting components.
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AppOrHostingResult(i32);

impl AppOrHostingResult {
    /// Gets the raw value of the result.
    #[must_use]
    pub const fn value(&self) -> i32 {
        self.0
    }

    /// Converts the result to an hosting exit code.
    pub fn as_hosting_exit_code(self) -> HostingResult {
        HostingResult::from(self.0)
    }
}

impl From<AppOrHostingResult> for i32 {
    fn from(code: AppOrHostingResult) -> Self {
        code.value()
    }
}

impl From<i32> for AppOrHostingResult {
    fn from(code: i32) -> Self {
        Self(code)
    }
}