use crate::{
bindings::hostfxr::{
PATH_LIST_SEPARATOR, hostfxr_resolve_sdk2_flags_t, hostfxr_resolve_sdk2_result_key_t,
},
error::{HostingError, HostingResult},
hostfxr::{AppOrHostingResult, Hostfxr},
pdcstring::{PdCStr, PdUChar},
};
use coreclr_hosting_shared::char_t;
use std::{cell::RefCell, io, mem::MaybeUninit, path::PathBuf, ptr, slice};
use super::UNSUPPORTED_HOST_VERSION_ERROR_CODE;
impl Hostfxr {
#[cfg_attr(
feature = "netcore3_0",
deprecated(note = "Use `HostfxrContext::run_app` instead"),
allow(deprecated)
)]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))]
pub fn run_app_with_args_and_startup_info<'a, A: AsRef<PdCStr>>(
&'a self,
app_path: &'a PdCStr,
args: impl IntoIterator<Item = &'a PdCStr>,
host_path: &PdCStr,
dotnet_root: &PdCStr,
) -> io::Result<AppOrHostingResult> {
let args = [&self.dotnet_exe, app_path]
.into_iter()
.chain(args)
.map(|s| s.as_ptr())
.collect::<Vec<_>>();
let result = unsafe {
self.lib.hostfxr_main_startupinfo(
args.len().try_into().unwrap(),
args.as_ptr(),
host_path.as_ptr(),
dotnet_root.as_ptr(),
app_path.as_ptr(),
)
}
.unwrap_or(UNSUPPORTED_HOST_VERSION_ERROR_CODE);
Ok(AppOrHostingResult::from(result))
}
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))]
pub fn resolve_sdk(
&self,
sdk_dir: &PdCStr,
working_dir: &PdCStr,
allow_prerelease: bool,
) -> Result<ResolveSdkResult, HostingError> {
let flags = if allow_prerelease {
hostfxr_resolve_sdk2_flags_t::none
} else {
hostfxr_resolve_sdk2_flags_t::disallow_prerelease
};
let raw_result = RawResolveSdkResult::default();
RESOLVE_SDK2_DATA.with(|sdk| *sdk.borrow_mut() = Some(raw_result));
let result = unsafe {
self.lib.hostfxr_resolve_sdk2(
sdk_dir.as_ptr(),
working_dir.as_ptr(),
flags,
resolve_sdk2_callback,
)
}
.unwrap_or(UNSUPPORTED_HOST_VERSION_ERROR_CODE);
HostingResult::from(result).into_result()?;
let raw_result = RESOLVE_SDK2_DATA
.with(|sdk| sdk.borrow_mut().take())
.unwrap();
Ok(ResolveSdkResult::new(raw_result))
}
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))]
#[must_use]
pub fn get_available_sdks(&self) -> Vec<PathBuf> {
self.get_available_sdks_raw(None)
}
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))]
#[must_use]
pub fn get_available_sdks_with_dotnet_path(&self, dotnet_path: &PdCStr) -> Vec<PathBuf> {
self.get_available_sdks_raw(Some(dotnet_path))
}
#[must_use]
fn get_available_sdks_raw(&self, dotnet_path: Option<&PdCStr>) -> Vec<PathBuf> {
let dotnet_path = dotnet_path.map_or_else(ptr::null, |s| s.as_ptr());
unsafe {
self.lib
.hostfxr_get_available_sdks(dotnet_path, get_available_sdks_callback)
};
GET_AVAILABLE_SDKS_DATA
.with(|sdks| sdks.borrow_mut().take())
.unwrap()
}
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))]
pub fn get_native_search_directories(
&self,
app_path: &PdCStr,
) -> Result<Vec<PathBuf>, HostingError> {
let mut buffer = Vec::<PdUChar>::new();
let args = [self.dotnet_exe.as_ptr(), app_path.as_ptr()];
let mut required_buffer_size = MaybeUninit::uninit();
unsafe {
self.lib.hostfxr_get_native_search_directories(
args.len().try_into().unwrap(),
args.as_ptr(),
buffer.as_mut_ptr().cast(),
0,
required_buffer_size.as_mut_ptr(),
)
};
let mut required_buffer_size = unsafe { required_buffer_size.assume_init() };
buffer.reserve(required_buffer_size.try_into().unwrap());
let result = unsafe {
self.lib.hostfxr_get_native_search_directories(
args.len().try_into().unwrap(),
args.as_ptr(),
buffer.spare_capacity_mut().as_mut_ptr().cast(),
buffer.spare_capacity_mut().len().try_into().unwrap(),
&raw mut required_buffer_size,
)
}
.unwrap_or(UNSUPPORTED_HOST_VERSION_ERROR_CODE);
HostingResult::from(result).into_result()?;
unsafe { buffer.set_len(required_buffer_size.try_into().unwrap()) };
let mut directories = Vec::new();
let last_start = 0;
for i in 0..buffer.len() {
if buffer[i] == PATH_LIST_SEPARATOR as PdUChar || buffer[i] == 0 {
buffer[i] = 0;
let directory = PdCStr::from_slice_with_nul(&buffer[last_start..=i]).unwrap();
directories.push(PathBuf::from(directory.to_os_string()));
break;
}
}
Ok(directories)
}
}
thread_local! {
static GET_AVAILABLE_SDKS_DATA: RefCell<Option<Vec<PathBuf>>> = const { RefCell::new(None) };
static RESOLVE_SDK2_DATA: RefCell<Option<RawResolveSdkResult>> = const { RefCell::new(None) };
}
extern "C" fn get_available_sdks_callback(sdk_count: i32, sdks_ptr: *const *const char_t) {
GET_AVAILABLE_SDKS_DATA.with(|sdks| {
let mut sdks_opt = sdks.borrow_mut();
let sdks = sdks_opt.get_or_insert_with(Vec::new);
let raw_sdks = unsafe { slice::from_raw_parts(sdks_ptr, sdk_count as usize) };
sdks.extend(raw_sdks.iter().copied().map(|raw_sdk| {
unsafe { PdCStr::from_str_ptr(raw_sdk) }
.to_os_string()
.into()
}));
});
}
extern "C" fn resolve_sdk2_callback(key: hostfxr_resolve_sdk2_result_key_t, value: *const char_t) {
RESOLVE_SDK2_DATA.with(|sdks| {
let path: PathBuf = unsafe { PdCStr::from_str_ptr(value) }.to_os_string().into();
let mut guard = sdks.borrow_mut();
let raw_result = guard.as_mut().unwrap();
match key {
hostfxr_resolve_sdk2_result_key_t::resolved_sdk_dir => {
assert_eq!(raw_result.resolved_sdk_dir, None);
raw_result.resolved_sdk_dir = Some(path);
}
hostfxr_resolve_sdk2_result_key_t::global_json_path => {
assert_eq!(raw_result.global_json_path, None);
raw_result.global_json_path = Some(path);
}
hostfxr_resolve_sdk2_result_key_t::requested_version => {
assert_eq!(raw_result.requested_version, None);
raw_result.requested_version = Some(path);
}
hostfxr_resolve_sdk2_result_key_t::global_json_state => {
assert_eq!(raw_result.global_json_state, None);
raw_result.global_json_state = Some(path);
}
_ => {
}
}
});
}
#[derive(Debug, Default)]
struct RawResolveSdkResult {
pub resolved_sdk_dir: Option<PathBuf>,
pub global_json_path: Option<PathBuf>,
pub requested_version: Option<PathBuf>,
pub global_json_state: Option<PathBuf>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))]
#[must_use]
pub struct ResolveSdkResult {
pub sdk_dir: PathBuf,
pub global_json: GlobalJsonState,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GlobalJsonState {
InvalidData,
InvalidJson,
NotFound,
Found(GlobalJsonInfo),
}
impl ResolveSdkResult {
fn new(raw: RawResolveSdkResult) -> Self {
use hostfxr_sys::hostfxr_resolve_sdk2_global_json_state;
let global_json = match raw.global_json_state {
None => GlobalJsonState::NotFound, Some(s) => match s.to_string_lossy().as_ref() {
hostfxr_resolve_sdk2_global_json_state::INVALID_DATA => {
GlobalJsonState::InvalidData
}
hostfxr_resolve_sdk2_global_json_state::INVALID_JSON => {
GlobalJsonState::InvalidJson
}
hostfxr_resolve_sdk2_global_json_state::VALID => {
GlobalJsonState::Found(GlobalJsonInfo {
path: raw
.global_json_path
.expect("global.json found but no path provided"),
requested_version: raw
.requested_version
.expect("global.json found but requested version not provided")
.to_string_lossy()
.to_string(),
})
}
_ => GlobalJsonState::NotFound,
},
};
let sdk_dir = raw
.resolved_sdk_dir
.expect("resolve_sdk2 succeeded but no sdk_dir provided.");
Self {
sdk_dir,
global_json,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GlobalJsonInfo {
path: PathBuf,
requested_version: String,
}