diff --git a/Cargo.toml b/Cargo.toml index ae85d7f..5fe9ca4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ash-molten" description = "Statically linked MoltenVK for Vulkan on Mac using Ash" -version = "0.7.2+1.1.0" +version = "0.8.2+1.1.2-f28ab1c" authors = ["Embark ", "Maik Klein "] edition = "2018" license = "MIT OR Apache-2.0" @@ -11,10 +11,16 @@ categories = ["api-bindings", "rendering", "os::macos-apis"] repository = "https://github.com/EmbarkStudios/ash-molten" homepage = "https://github.com/EmbarkStudios/ash-molten" documentation = "https://docs.rs/ash-molten" +build = "build/build.rs" [dependencies] ash = "0.31" +[build-dependencies] +anyhow = "1.0" +serde = { version = "1.0", features = ["derive"] } +plist = { version = "1.0" } + [features] # Build features # Without build feature enabled MoltenVK will be build from source diff --git a/build/.DS_Store b/build/.DS_Store new file mode 100644 index 0000000..30666a6 Binary files /dev/null and b/build/.DS_Store differ diff --git a/build.rs b/build/build.rs similarity index 65% rename from build.rs rename to build/build.rs index 562f749..c352ea5 100644 --- a/build.rs +++ b/build/build.rs @@ -1,9 +1,22 @@ +mod xcframework; + #[cfg(any(target_os = "macos", target_os = "ios"))] mod mac { + use std::path::Path; // MoltenVK git tagged release to use - pub static VERSION: &str = "1.1.0"; + pub static MOLTEN_VK_VERSION: &str = "1.1.2"; + pub static MOLTEN_VK_PATCH: Option<&str> = Some("f28ab1c"); + + // Return the artifact tag in the form of "x.x.x" or if there is a patch specified "x.x.x#yyyyyyy" + pub(crate) fn get_artifact_tag() -> String { + if let Some(patch) = MOLTEN_VK_PATCH { + format!("{}#{}", MOLTEN_VK_VERSION, patch) + } else { + MOLTEN_VK_VERSION.to_owned() + } + } // Features are not used inside build scripts, so we have to explicitly query them from the // enviroment @@ -31,7 +44,7 @@ mod mac { }; let checkout_dir = Path::new(&std::env::var("OUT_DIR").expect("Couldn't find OUT_DIR")) - .join(format!("MoltenVK-{}", VERSION)); + .join(format!("MoltenVK-{}", get_artifact_tag())); let exit = Arc::new(AtomicBool::new(false)); let wants_exit = exit.clone(); @@ -50,30 +63,49 @@ mod mac { } }); - let git_status = if Path::new(&checkout_dir).exists() { - Command::new("git") - .current_dir(&checkout_dir) - .arg("pull") - .spawn() - .expect("failed to spawn git") - .wait() - .expect("failed to pull MoltenVK") + if Path::new(&checkout_dir).exists() { + // Don't pull if a specific hash has been checkedout + if MOLTEN_VK_PATCH.is_none() { + let git_status = Command::new("git") + .current_dir(&checkout_dir) + .arg("pull") + .spawn() + .expect("failed to spawn git") + .wait() + .expect("failed to pull MoltenVK"); + + assert!(git_status.success(), "failed to get MoltenVK"); + } } else { - Command::new("git") + let branch = format!("v{}", MOLTEN_VK_VERSION.to_owned()); + let clone_args = if MOLTEN_VK_PATCH.is_none() { + vec!["--branch", branch.as_str(), "--depth", "1"] + } else { + vec!["--single-branch", "--branch", "master"] // Can't specify depth if you switch to a different commit hash later. + }; + let git_status = Command::new("git") .arg("clone") - .arg("--branch") - .arg(format!("v{}", VERSION.to_owned())) - .arg("--depth") - .arg("1") + .args(clone_args) .arg("https://github.com/KhronosGroup/MoltenVK.git") .arg(&checkout_dir) .spawn() .expect("failed to spawn git") .wait() - .expect("failed to clone MoltenVK") + .expect("failed to clone MoltenVK"); + + assert!(git_status.success(), "failed to get MoltenVK"); }; - assert!(git_status.success(), "failed to get MoltenVK"); + if let Some(patch) = MOLTEN_VK_PATCH { + let git_status = Command::new("git") + .current_dir(&checkout_dir) + .arg("checkout") + .arg(patch) + .status() + .expect("failed to spawn git"); + + assert!(git_status.success(), "failed to checkout patch"); + } // These (currently) match the identifiers used by moltenvk let (target_name, _dir) = match std::env::var("CARGO_CFG_TARGET_OS") { @@ -124,7 +156,7 @@ mod mac { .arg("-s") .arg(format!( "https://api.github.com/repos/EmbarkStudios/ash-molten/releases/tags/MoltenVK-{}", - VERSION + get_artifact_tag().replace("#", "%23") )) .stdout(Stdio::piped()) .spawn() @@ -155,7 +187,7 @@ mod mac { .stdin(Stdio::from(cut_out)) .stdout(Stdio::piped()) .spawn() - .expect("Couldn't launch cut"); + .expect("Couldn't launch tr"); let tr_out = tr.stdout.expect("Failed to open grep stdout"); @@ -164,7 +196,7 @@ mod mac { .stdin(Stdio::from(tr_out)) .stdout(Stdio::piped()) .spawn() - .expect("Couldn't launch cut") + .expect("Couldn't launch xargs") .wait_with_output() .expect("failed to wait on xargs"); @@ -191,59 +223,78 @@ mod mac { } } +use std::{ + collections::{hash_map::RandomState, HashMap}, + path::{Path, PathBuf}, +}; + #[cfg(any(target_os = "macos", target_os = "ios"))] fn main() { use crate::mac::*; - use std::path::{Path, PathBuf}; - // The 'external' feature was not enabled. Molten will be built automaticaly. let external_enabled = is_feature_enabled("EXTERNAL"); let pre_built_enabled = is_feature_enabled("PRE_BUILT"); + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + assert!( !(external_enabled && pre_built_enabled), "external and prebuild cannot be active at the same time" ); - if !external_enabled && !pre_built_enabled { - let target_dir = Path::new(&std::env::var("OUT_DIR").unwrap()) - .join(format!("MoltenVK-{}", crate::mac::VERSION,)); - let _target_name = build_molten(&target_dir); + if !external_enabled { + let mut project_dir = if pre_built_enabled { + let target_dir = Path::new(&std::env::var("OUT_DIR").unwrap()).join(format!( + "Prebuilt-MoltenVK-{}", + crate::mac::get_artifact_tag() + )); + + download_prebuilt_molten(&target_dir); - let project_dir = { let mut pb = PathBuf::from( std::env::var("CARGO_MANIFEST_DIR").expect("unable to find env:CARGO_MANIFEST_DIR"), ); pb.push(target_dir); - pb.push(format!( - "Package/Latest/MoltenVK/MoltenVK.xcframework/{}-{}", - std::env::var("CARGO_CFG_TARGET_OS").unwrap(), - std::env::var("CARGO_CFG_TARGET_ARCH").unwrap() - )); - + pb.push("MoltenVK.xcframework"); pb - }; + } else { + let target_dir = Path::new(&std::env::var("OUT_DIR").unwrap()) + .join(format!("MoltenVK-{}", crate::mac::get_artifact_tag())); + let _target_name = build_molten(&target_dir); - println!("cargo:rustc-link-search=native={}", project_dir.display()); - } else if pre_built_enabled { - let target_dir = Path::new(&std::env::var("OUT_DIR").unwrap()) - .join(format!("Prebuilt-MoltenVK-{}", crate::mac::VERSION)); - - download_prebuilt_molten(&target_dir); - - let project_dir = { let mut pb = PathBuf::from( std::env::var("CARGO_MANIFEST_DIR").expect("unable to find env:CARGO_MANIFEST_DIR"), ); pb.push(target_dir); - pb.push(format!( - "{}-{}", - std::env::var("CARGO_CFG_TARGET_OS").unwrap(), - std::env::var("CARGO_CFG_TARGET_ARCH").unwrap() - )); + pb.push("Package/Latest/MoltenVK/MoltenVK.xcframework"); pb }; + + let xcframework = + xcframework::XcFramework::parse(&project_dir).expect("Failed to parse XCFramework"); + let native_libs = xcframework + .AvailableLibraries + .into_iter() + .map(|lib| { + lib.universal_to_native(project_dir.clone()) + .expect("Failed to get native library") + }) + .flatten() + .map(|lib| (lib.identifier(), lib.path())) + .collect::>(); + + let id = xcframework::Identifier::new( + target_arch.into(), + target_os.into(), + xcframework::Variant::Default, + ); + + let lib_path = native_libs.get(&id).expect("Library was not found"); + let lib_dir = lib_path.parent().unwrap(); + project_dir.push(lib_dir); + println!("cargo:rustc-link-search=native={}", project_dir.display()); } diff --git a/build/xcframework/.DS_Store b/build/xcframework/.DS_Store new file mode 100644 index 0000000..9fc1c82 Binary files /dev/null and b/build/xcframework/.DS_Store differ diff --git a/build/xcframework/common.rs b/build/xcframework/common.rs new file mode 100644 index 0000000..db286b6 --- /dev/null +++ b/build/xcframework/common.rs @@ -0,0 +1,96 @@ +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Deserialize)] +#[serde(into = "&'static str")] +#[serde(from = "String")] +pub enum Arch { + X86, + Amd64, + Arm64, + Arm64e, + Unknown, +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Deserialize)] +#[serde(into = "&'static str")] +#[serde(from = "String")] +pub enum Platform { + MacOS, + IOS, + TvOS, + WatchOS, + Unknown, +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Deserialize)] +#[serde(into = "&'static str")] +#[serde(from = "String")] +pub enum Variant { + Default, + Simulator, +} + +impl> From for Arch { + fn from(arch: T) -> Self { + match arch.as_ref() { + "x86_64" => Arch::Amd64, + "x86" => Arch::X86, + "arm64" => Arch::Arm64, + "aarch64" => Arch::Arm64, + "arm64e" => Arch::Arm64e, + _ => Arch::Unknown, + } + } +} + +impl<'a> From for &'a str { + fn from(arch: Arch) -> Self { + match arch { + Arch::Amd64 => "x86_64", + Arch::X86 => "x86", + Arch::Arm64 => "arm64", + Arch::Arm64e => "arm64e", + Arch::Unknown => "", + } + } +} + +impl> From for Platform { + fn from(platform: T) -> Self { + match platform.as_ref() { + "tvos" => Platform::TvOS, + "macos" => Platform::MacOS, + "ios" => Platform::IOS, + "watchos" => Platform::WatchOS, + _ => Platform::Unknown, + } + } +} + +impl<'a> From for &'a str { + fn from(platform: Platform) -> Self { + match platform { + Platform::TvOS => "tvos", + Platform::MacOS => "macos", + Platform::IOS => "ios", + Platform::WatchOS => "watchos", + Platform::Unknown => "", + } + } +} + +impl> From for Variant { + fn from(variant: T) -> Self { + match variant.as_ref() { + "simulator" => Variant::Simulator, + _ => Variant::Default, + } + } +} + +impl<'a> From for &'a str { + fn from(variant: Variant) -> Self { + match variant { + Variant::Simulator => "simulator", + Variant::Default => "", + } + } +} diff --git a/build/xcframework/library.rs b/build/xcframework/library.rs new file mode 100644 index 0000000..7d4f418 --- /dev/null +++ b/build/xcframework/library.rs @@ -0,0 +1,121 @@ +use anyhow::Error; + +use super::common::{Arch, Platform, Variant}; +use std::{ + path::{Path, PathBuf}, + process::Command, + string::String, +}; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct Identifier { + pub arch: Arch, + pub platform: Platform, + pub variant: Variant, +} + +impl Identifier { + pub fn new(arch: Arch, platform: Platform, variant: Variant) -> Self { + Self { + arch, + platform, + variant, + } + } +} + +#[allow(non_snake_case)] +#[derive(Debug, serde::Deserialize)] +pub struct UniversalLibrary { + LibraryPath: String, + SupportedArchitectures: Vec, + SupportedPlatformVariant: Option, + SupportedPlatform: Platform, + LibraryIdentifier: String, +} + +#[allow(non_snake_case)] +#[derive(Debug)] +pub struct NativeLibrary { + LibraryPath: String, + SupportedArchitectures: Arch, + SupportedPlatformVariant: Option, + SupportedPlatform: Platform, + LibraryIdentifier: String, +} + +impl UniversalLibrary { + pub fn universal_to_native>( + self, + xcframework_dir: P, + ) -> Result, Error> { + let lib_id = &self.LibraryIdentifier; + let platform = &self.SupportedPlatform; + let variant = self.SupportedPlatformVariant.as_ref(); + let lib_path = &self.LibraryPath; + + if self.SupportedArchitectures.len() == 1 { + let arch = &self.SupportedArchitectures[0]; + Ok(vec![NativeLibrary { + LibraryPath: lib_path.into(), + SupportedArchitectures: *arch, + SupportedPlatformVariant: variant.cloned(), + SupportedPlatform: *platform, + LibraryIdentifier: lib_id.into(), + }]) + } else { + let mut native_libs = Vec::new(); + let xcframework_dir = xcframework_dir.as_ref().to_path_buf(); + let full_path = xcframework_dir.join(lib_id).join(lib_path); + + for arch in &self.SupportedArchitectures { + let platform_str: &str = (*platform).into(); + let arch_str: &str = (*arch).into(); + let mut new_identifier = format!("{}-{}", platform_str, arch_str); + + if let Some(variant) = variant { + let variant_str: &str = (*variant).into(); + new_identifier.push_str(format!("-{}", variant_str).as_str()); + } + + let mut out_path = xcframework_dir.join(new_identifier.clone()); + std::fs::create_dir_all(&out_path)?; + out_path.push(&lib_path); + + assert!(Command::new("lipo") + .arg(&full_path) + .arg("-thin") + .arg(arch_str) + .arg("-output") + .arg(out_path) + .status() + .expect("Failed to spawn lipo") + .success()); + + native_libs.push(NativeLibrary { + LibraryPath: lib_path.into(), + SupportedArchitectures: *arch, + SupportedPlatformVariant: variant.copied(), + SupportedPlatform: *platform, + LibraryIdentifier: new_identifier, + }); + } + + Ok(native_libs) + } + } +} + +impl NativeLibrary { + pub fn path(&self) -> PathBuf { + Path::new(&format!("{}/{}", self.LibraryIdentifier, self.LibraryPath)).to_path_buf() + } + + pub fn identifier(&self) -> Identifier { + Identifier::new( + self.SupportedArchitectures, + self.SupportedPlatform, + self.SupportedPlatformVariant.unwrap_or(Variant::Default), + ) + } +} diff --git a/build/xcframework/mod.rs b/build/xcframework/mod.rs new file mode 100644 index 0000000..d3b7157 --- /dev/null +++ b/build/xcframework/mod.rs @@ -0,0 +1,23 @@ +use anyhow::Error; +use std::{fs::File, io::BufReader, path::Path}; + +mod common; +mod library; + +pub use common::*; +pub use library::*; + +#[allow(non_snake_case)] +#[derive(Debug, serde::Deserialize)] +pub struct XcFramework { + pub AvailableLibraries: Vec, + pub CFBundlePackageType: String, + pub XCFrameworkFormatVersion: String, +} + +impl XcFramework { + pub fn parse>(path: P) -> Result { + let mut reader = BufReader::new(File::open(path.as_ref().join("Info.plist"))?); + Ok(plist::from_reader(&mut reader)?) + } +}