use std::{
borrow::Cow,
env,
error::Error,
fs::{create_dir_all, File},
io::{self, Cursor, Read},
path::Path,
str::FromStr,
};
use build_target::{Arch, Env, Os};
use semver::Version;
use serde::{Deserialize, Serialize};
use zip::ZipArchive;
#[derive(Debug, Serialize, Deserialize)]
struct ResourceIndex<'a> {
resources: Vec<Resource<'a>>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Resource<'a> {
#[serde(rename = "@id")]
url: Cow<'a, str>,
#[serde(rename = "@type")]
r#type: Cow<'a, str>,
}
#[derive(Debug, Serialize, Deserialize)]
struct PackageInfoIndex<'a> {
#[serde(rename = "items")]
pages: Vec<PackageInfoCatalogPage<'a>>,
}
#[derive(Debug, Serialize, Deserialize)]
struct PackageInfoCatalogPage<'a> {
#[serde(rename = "@id")]
url: Cow<'a, str>,
lower: Cow<'a, str>,
upper: Cow<'a, str>,
items: Vec<PackageInfoCatalogPageEntry<'a>>,
}
#[derive(Debug, Serialize, Deserialize)]
struct PackageInfoCatalogPageEntry<'a> {
#[serde(rename = "catalogEntry")]
inner: PackageInfoCatalogEntry<'a>,
}
#[derive(Debug, Serialize, Deserialize)]
struct PackageInfoCatalogEntry<'a> {
#[serde(rename = "@id")]
url: Cow<'a, str>,
listed: bool,
#[serde(rename = "packageContent")]
content: Cow<'a, str>,
version: Cow<'a, str>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
if cfg!(feature = "private-docs-rs") {
println!("Returning early because private-docs-rs feature was enabled");
return Ok(());
}
let os = Os::target().unwrap();
let arch = Arch::target().unwrap();
let env = Env::target().unwrap();
let target = match (&os, arch, env) {
(Os::Windows, Arch::X86, _) => "win-x86",
(Os::Windows, Arch::X86_64, _) => "win-x64",
(Os::Windows, Arch::ARM, _) => "win-arm",
(Os::Windows, Arch::AARCH64, _) => "win-arm64",
(Os::Linux, Arch::X86_64, Env::Musl) => "linux-musl-x64",
(Os::Linux, Arch::ARM, Env::Musl) => "linux-musl-arm",
(Os::Linux, Arch::AARCH64, Env::Musl) => "linux-musl-arm64",
(Os::Linux, Arch::X86_64, _) => "linux-x64",
(Os::Linux, Arch::ARM, _) => "linux-arm",
(Os::Linux, Arch::AARCH64, _) => "linux-arm64",
(Os::MacOs, Arch::X86_64, _) => "osx-x64",
_ => panic!("platform not supported."),
};
let out_dir = env::var("OUT_DIR")?;
let runtime_dir = Path::new(&out_dir)
.parent()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.join("nethost")
.join(target);
if !runtime_dir.exists() || runtime_dir.read_dir()?.next().is_none() {
create_dir_all(&runtime_dir)?;
download_nethost(target, &runtime_dir)?;
}
println!("cargo:rerun-if-changed={}", runtime_dir.to_str().unwrap());
println!("cargo:rustc-link-search={}", runtime_dir.to_str().unwrap());
match os {
Os::Windows => {
println!("cargo:rustc-link-lib=libnethost");
}
Os::MacOs => {
}
_ => {
}
}
Ok(())
}
fn download_nethost(target: &str, target_path: &Path) -> Result<(), Box<dyn Error>> {
let client = reqwest::blocking::Client::new();
let index = client
.get("https://api.nuget.org/v3/index.json")
.send()
.expect("Failed to query nuget.org index for nethost package. Are you connected to the internet?")
.json::<ResourceIndex>()
.expect("Failed to parse json response from nuget.org2.");
let registrations_base_url = index
.resources
.into_iter()
.find(|res| res.r#type == "RegistrationsBaseUrl")
.expect("Unable to find nuget.org query endpoint.")
.url;
let package_info = client
.get(format!(
"{}runtime.{}.microsoft.netcore.dotnetapphost/index.json",
registrations_base_url, target
))
.send()
.expect("Failed to find package on nuget.org.")
.json::<PackageInfoIndex>()
.expect("Failed to parse json response from nuget.org.")
.pages
.into_iter()
.max_by_key(|page| Version::from_str(page.upper.as_ref()).unwrap())
.expect("Unable to find package page.")
.items
.into_iter()
.map(|e| e.inner)
.filter(|e| e.listed)
.max_by_key(|e| Version::from_str(e.version.as_ref()).unwrap())
.unwrap();
let mut package_content_response = client
.get(package_info.content.as_ref())
.send()
.expect("Failed to download nethost nuget package.");
let mut buf: Vec<u8> = Vec::new();
package_content_response.read_to_end(&mut buf)?;
let reader = Cursor::new(buf);
let mut archive = ZipArchive::new(reader)?;
let runtime_dir_path = format!("runtimes/{}/native", target);
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
let out_path = match file.enclosed_name() {
Some(path) => path,
None => continue,
};
if !out_path.starts_with(&runtime_dir_path) {
continue;
}
if let Some(ext) = out_path.extension() {
if !(ext == "a" || ext == "lib") {
continue;
}
} else {
continue;
}
if let Some(name) = out_path.file_stem() {
if !name.to_string_lossy().contains("nethost") {
continue;
}
} else {
continue;
}
let mut out_file = File::create(target_path.join(out_path.components().last().unwrap()))?;
io::copy(&mut file, &mut out_file)?;
}
Ok(())
}