diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..dbfa4063 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ + +[target.aarch64-apple-ios] +runner = "cargo-box runner" diff --git a/.gitignore b/.gitignore index e71062d6..bede7500 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,6 @@ -/target - - -# Added by cargo -# -# already existing elements were commented out - xcuserdata /target .DS_Store +.box Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 5c71ff88..fd8844b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,10 @@ members = [ "cidre-macros", ] +exclude = [ + "cargo-box" +] + # [profile.dev] # opt-level = 1 diff --git a/README.md b/README.md index 094b3a54..eed6d590 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,25 @@ - [x] macOS - [x] iOS/iPadOS - [x] tvOS -- [x] watchOS +- [ ] watchOS - [ ] visionOS +### iOS devices runner + +Run tests on your iPhone or iPad. + +1. Run `cargo install --path ./cargo-box` to install cargo box plugin +2. Run `cargo box teams` to find out your DEVELOMPENT_TEAM id +3. Run `cargo box devices` to find out your DEVICE_ID +4. Create `.box` file with contents: +``` +BOX_ORG_ID = unique org id, for instance org.cidre +DEVELOPMENT_TEAM = team id from steep 2 +DEVICE_ID = device id from step 3 +``` +5. Run `cargo t --target aarch64-apple-ios` +6. Run `cargo r --target aarch64-apple-ios --example` + ### Versioning (API Availability) Deployment targets are controlled via features `macos_x_x`, `ios_x_x`, `tvos_x_x`, `watchos_x_x`, `visionos_x_x`. diff --git a/build.sh b/build.sh index 623239f7..5a54a5e5 100755 --- a/build.sh +++ b/build.sh @@ -5,6 +5,7 @@ cargo b --target aarch64-apple-ios cargo b --target aarch64-apple-ios-sim cargo +nightly b -Zbuild-std --target aarch64-apple-tvos cargo +nightly b -Zbuild-std --target aarch64-apple-tvos-sim +# cargo +nightly b -Zbuild-std --target arm64_32-apple-watchos cargo b --target aarch64-apple-darwin cargo b --target x86_64-apple-darwin # cargo +nightly b -Zbuild-std --target aarch64-apple-visionos diff --git a/cargo-box/.gitignore b/cargo-box/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/cargo-box/.gitignore @@ -0,0 +1 @@ +/target diff --git a/cargo-box/Cargo.toml b/cargo-box/Cargo.toml new file mode 100644 index 00000000..be29576a --- /dev/null +++ b/cargo-box/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "cargo-box" +version = "0.1.0" +edition = "2021" +description = "iOS runner" +license = "MIT" + +[dependencies] +clap = { version = "4.5", features = ["derive"] } +serde = { version = "1.0.215", features = ["derive"] } +serde_json = "1.0.133" +cargo_toml = "0.21" +dotenv = "0.15.0" + +cidre = { path = "../cidre", default-features = false, features = ["ns", "cg", "cf", "sec"] } diff --git a/cargo-box/box/box.entitlements b/cargo-box/box/box.entitlements new file mode 100644 index 00000000..18aff0ce --- /dev/null +++ b/cargo-box/box/box.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/cargo-box/box/box.xcodeproj/project.pbxproj b/cargo-box/box/box.xcodeproj/project.pbxproj new file mode 100644 index 00000000..7b09c2fd --- /dev/null +++ b/cargo-box/box/box.xcodeproj/project.pbxproj @@ -0,0 +1,190 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + D2B940072D12B915004F24BF /* bin in CopyFiles */ = {isa = PBXBuildFile; fileRef = D2B940052D12B900004F24BF /* bin */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + D2B93FFB2D11B5E0004F24BF /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 6; + files = ( + D2B940072D12B915004F24BF /* bin in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + D2798B842CFCC59D00A4ABEF /* cfg.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = cfg.xcconfig; sourceTree = ""; }; + D28FC8DE2CFCB6E200FE81B8 /* box.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = box.entitlements; sourceTree = ""; }; + D2B940052D12B900004F24BF /* bin */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = bin; path = "$(PRODUCT_NAME)"; sourceTree = ""; }; + D2BD41F22CFC8A5D000CF128 /* box.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = box.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D2BD41EF2CFC8A5D000CF128 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D2BD41E92CFC8A5D000CF128 = { + isa = PBXGroup; + children = ( + D2798B842CFCC59D00A4ABEF /* cfg.xcconfig */, + D28FC8DE2CFCB6E200FE81B8 /* box.entitlements */, + D2B940052D12B900004F24BF /* bin */, + D2BD41F32CFC8A5D000CF128 /* Products */, + ); + sourceTree = ""; + }; + D2BD41F32CFC8A5D000CF128 /* Products */ = { + isa = PBXGroup; + children = ( + D2BD41F22CFC8A5D000CF128 /* box.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + D2BD41F12CFC8A5D000CF128 /* box */ = { + isa = PBXNativeTarget; + buildConfigurationList = D2BD42012CFC8A5E000CF128 /* Build configuration list for PBXNativeTarget "box" */; + buildPhases = ( + D2BD41EE2CFC8A5D000CF128 /* Sources */, + D2BD41EF2CFC8A5D000CF128 /* Frameworks */, + D2BD41F02CFC8A5D000CF128 /* Resources */, + D2B93FFB2D11B5E0004F24BF /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = box; + packageProductDependencies = ( + ); + productName = box; + productReference = D2BD41F22CFC8A5D000CF128 /* box.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D2BD41EA2CFC8A5D000CF128 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1620; + LastUpgradeCheck = 1620; + TargetAttributes = { + D2BD41F12CFC8A5D000CF128 = { + CreatedOnToolsVersion = 16.2; + }; + }; + }; + buildConfigurationList = D2BD41ED2CFC8A5D000CF128 /* Build configuration list for PBXProject "box" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = D2BD41E92CFC8A5D000CF128; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = D2BD41F32CFC8A5D000CF128 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D2BD41F12CFC8A5D000CF128 /* box */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D2BD41F02CFC8A5D000CF128 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D2BD41EE2CFC8A5D000CF128 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + D2BD41FF2CFC8A5E000CF128 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D2798B842CFCC59D00A4ABEF /* cfg.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + D2BD42002CFC8A5E000CF128 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D2798B842CFCC59D00A4ABEF /* cfg.xcconfig */; + buildSettings = { + }; + name = Release; + }; + D2BD42022CFC8A5E000CF128 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Debug; + }; + D2BD42032CFC8A5E000CF128 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D2BD41ED2CFC8A5D000CF128 /* Build configuration list for PBXProject "box" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D2BD41FF2CFC8A5E000CF128 /* Debug */, + D2BD42002CFC8A5E000CF128 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D2BD42012CFC8A5E000CF128 /* Build configuration list for PBXNativeTarget "box" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D2BD42022CFC8A5E000CF128 /* Debug */, + D2BD42032CFC8A5E000CF128 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D2BD41EA2CFC8A5D000CF128 /* Project object */; +} diff --git a/cargo-box/box/box.xcodeproj/xcshareddata/xcschemes/box.xcscheme b/cargo-box/box/box.xcodeproj/xcshareddata/xcschemes/box.xcscheme new file mode 100644 index 00000000..457eade0 --- /dev/null +++ b/cargo-box/box/box.xcodeproj/xcshareddata/xcschemes/box.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cargo-box/box/cfg.xcconfig b/cargo-box/box/cfg.xcconfig new file mode 100644 index 00000000..43945e5d --- /dev/null +++ b/cargo-box/box/cfg.xcconfig @@ -0,0 +1,49 @@ +// +// cfg.xcconfig + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 +// https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project + +EXCLUDED_ARCHS[sdk=macos*] = x86_64 + +ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES +SDKROOT = auto +SUPPORTED_PLATFORMS = xrsimulator xros watchsimulator watchos macosx iphonesimulator iphoneos appletvsimulator appletvos +SUPPORTS_MACCATALYST = YES + +CODE_SIGN_ENTITLEMENTS = box.entitlements +PROVISIONING_PROFILE_SPECIFIER = +CODE_SIGN_IDENTITY = Apple Development +CODE_SIGN_STYLE = Automatic +ENABLE_HARDENED_RUNTIME = YES +GENERATE_INFOPLIST_FILE = YES +LD_RUNPATH_SEARCH_PATHS = @executable_path/Frameworks +LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = @executable_path/../Frameworks +TARGETED_DEVICE_FAMILY = 1,2,7 + +ALWAYS_SEARCH_USER_PATHS = NO + +MARKETING_VERSION = 1.0.0 +CURRENT_PROJECT_VERSION = 1 + +PRODUCT_NAME = box +PRODUCT_DISPLAY_NAME = $(PRODUCT_NAME) + + +INFOPLIST_KEY_NSCameraUsageDescription = Testing Camera + +BOX_ID = box +BOX_ORG_ID = org.cidre + +// Anjlab +//DEVELOPMENT_TEAM = FD6ZML48V9 +BOX_ORG_ID = org.cidre + + +// Custom box overrides generated by cargo-bx +#include? "box.xcconfig" + +PRODUCT_BUNDLE_IDENTIFIER = $(BOX_ORG_ID).$(BOX_ID) +INFOPLIST_KEY_CFBundleDisplayName = $(PRODUCT_NAME) +BOX_BIN_PATH = $(PRODUCT_NAME) diff --git a/cargo-box/src/main.rs b/cargo-box/src/main.rs new file mode 100644 index 00000000..5bc466b9 --- /dev/null +++ b/cargo-box/src/main.rs @@ -0,0 +1,708 @@ +use clap::{Parser, Subcommand}; +use std::env; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Cli { + #[command(subcommand)] + cmd: Cmd, +} + +#[derive(Subcommand, Debug)] +enum Cmd { + /// Used via cargo.toml runner + #[command()] + Runner(runner::Args), + + /// List dev teams on this mac + #[command()] + Teams, + + /// List connected devices on this mac + #[command()] + Devices, + + /// Create configured xcode project for binary, test or example + /// in target/boxes for runner. + #[command()] + Proj(xcode::ProjArgs), +} + +fn main() { + // Handle different args from different calls: + // + // cargo-box -> cargo-bx (no extra box) + // cargo box -> cargo-bx bx (extra box) + let mut args: Vec<_> = env::args().collect(); + if args.get(1).map(|v| v == "box") == Some(true) { + args.remove(1); + } + + // special case for runner to skip Cli::parse_from + if args.get(1).map(|v| v == "runner") == Some(true) { + return runner::run(runner::Args { args }); + } + + match Cli::parse_from(args).cmd { + Cmd::Teams => teams::list(), + Cmd::Devices => device_ctl::list_devices(), + Cmd::Proj(args) => xcode::proj(args), + _ => panic!("unknown command"), + } +} + +mod runner { + use std::{ + env, + path::{Path, PathBuf}, + }; + + use clap::Parser; + + use crate::macos::{device_ctl, xcode}; + + #[derive(Parser, Debug)] + pub(crate) struct Args { + /// Arguments for the runner + #[arg()] + pub(crate) args: Vec, + } + + pub(crate) fn run(args: Args) { + // println!("{args:?}"); + assert!(args.args.len() > 2); + let binary = &args.args[2]; + let path = Path::new(binary); + + let err = "can't parse filename"; + + let Some(name) = path.file_name() else { + panic!("{err}"); + }; + + let Some(name) = name.to_str() else { + panic!("{err}"); + }; + + let mut name = name.to_string(); + + let Some(parent) = path.parent() else { + panic!("{err}"); + }; + + let Some(folder) = parent.file_name() else { + panic!("{err}"); + }; + + let Some(folder) = folder.to_str() else { + panic!("{err}"); + }; + let mut is_example = false; + let mut is_dep = false; + let mut is_binary = false; + let parent = match folder { + "examples" => { + // target/[optional-tripple/]profile|/examples/example + is_example = true; + parent.parent() + } + "deps" => { + // [fullpath/]target/[optional-tripple/]profile|/deps/test-or-bench + is_dep = true; + parent.parent() + } + "debug" => { + // target/[optional-tripple/]profile|/binary + // binary + is_binary = true; + Some(parent) + } + "release" => { + // target/[optional-tripple/]profile|/binary + // binary + is_binary = true; + Some(parent) + } + _ => panic!("{err}"), + }; + + let Some(parent) = parent else { + panic!("{err}"); + }; + let Some(folder) = parent.file_name() else { + panic!("{err}"); + }; + let Some(folder) = folder.to_str() else { + panic!("{err}"); + }; + + let config = match folder { + "debug" => "Debug", + "release" => "Release", + _ => panic!("{err}"), + }; + + let Some(mut parent) = parent.parent() else { + panic!("{err}"); + }; + let Some(folder) = parent.file_name() else { + panic!("{err}"); + }; + + let Some(folder) = folder.to_str() else { + panic!("{err}"); + }; + + let sdk = match folder { + "target" | "aarch64-apple-darwin" | "x86_64-apple-darwin" => "macos", + "aarch64-apple-ios" => "iphoneos", + "x86_64-apple-ios" | "aarch64-apple-ios-sim" => "iphonesimulator", + "aarch64-apple-tvos" => "appletvos", + "aarch64-apple-tvos-sim" => "appletvsimulator", + "arm64_32-apple-watchos" => "watchos", + "aarch64-apple-watchos" => "watchos", + "aarch64-apple-watchos-sim" => "watchsimulator", + "aarch64-apple-visionos" => "xros", + "aarch64-apple-visionos-sim" => "xrsimulator", + _ => panic!("{err}"), + }; + + let platform = match sdk { + "macos" => "macOS", + "iphoneos" => "iOS", + "tvos" => "tvOS", + "watchos" => "watchOS", + "xros" => "visionOS", + "iphoneosimulator" => "iOS Simulator", + "appletvsimulator" => "tvOS Simulator", + "watchsimulator" => "watchOS Simulator", + "xrsimulator" => "visionOS Simulator", + x => panic!("unknown platform {x}"), + }; + + if folder != "target" { + parent = parent.parent().unwrap(); + } + + let Some(folder) = parent.file_name() else { + panic!("{err}"); + }; + + if folder.to_os_string() == std::ffi::OsStr::new("target") { + parent = parent.parent().unwrap(); + } + + if !parent.as_os_str().is_empty() { + env::set_current_dir(parent).unwrap(); + } + + if is_dep { + // /Users/yury/Projects/cidre/target/aarch64-apple-ios/release/deps/uuid-3968b503a26aa57f + if let Some((name_part, _hash)) = name.rsplit_once('-') { + name = name_part.to_string(); + } + } + + let mut proj_args = xcode::ProjArgs { + bin: None, + example: None, + dep: None, + }; + + if is_example { + proj_args.example = Some(name.clone()); + } else if is_binary { + proj_args.bin = Some(name.clone()); + } else if is_dep { + proj_args.dep = Some(name.clone()); + } + + xcode::proj(proj_args); + + let mut project = PathBuf::from("./target/boxes"); + if is_example { + project.push("examples"); + } + if is_dep { + project.push("deps"); + } + + let mut target = project.clone(); + target.push(format!("build-{name}")); + + project.push(&name); + project.push(&name); + + // TODO: if is_binary, try replace with BOX_BIN_PATH instead of copy + std::fs::copy(binary, &project).unwrap(); + project.pop(); + + project.push("box.xcodeproj"); + + xcode::build( + project.to_str().unwrap(), + platform, + config, + target.to_str().unwrap(), + ); + + target.push("Build"); + target.push("Products"); + + target.push(&format!("{config}-{sdk}")); + target.push(&format!("{name}.app")); + + let box_org_id = std::env::var("BOX_ORG_ID").unwrap(); + + let device_id = std::env::var("DEVICE_ID").unwrap(); + + device_ctl::install_app(&device_id, target.to_str().unwrap()); + device_ctl::run_app(&device_id, &format!("{box_org_id}.{name}"), &args.args[3..]); + } +} + +mod teams { + use cidre::{arc, cf, sec}; + + pub(crate) fn list() { + let query = cf::DictionaryOf::with_keys_values( + &[ + sec::class_key(), + sec::match_keys::limit(), + sec::match_keys::policy(), + ], + &[ + sec::class::certificate().as_type_ref(), + sec::match_limit::all(), + &sec::Policy::with_props(sec::Policy::apple_code_signing(), None).unwrap(), + ], + ); + + let certs = sec::item_matching(&query).unwrap(); + + assert_eq!(certs.get_type_id(), cf::Array::type_id()); + let certs: arc::R> = unsafe { std::mem::transmute(certs) }; + + let mut filter_set = std::collections::HashSet::new(); + let subject_key = sec::cert_oids::x509_v1_subject_name(); + let org_name_label = sec::cert_oids::organization_name(); + let unit_name_label = sec::cert_oids::organizational_unit_name(); + let prop_value_key = sec::prop_keys::value(); + let prop_label_key = sec::prop_keys::label(); + let keys = cf::ArrayOf::from_slice(&[subject_key]); + for cert in certs.iter() { + let Ok(vals) = cert.values(&keys) else { + continue; + }; + let Some(value) = vals.get(subject_key) else { + continue; + }; + let Some(section) = value.get(prop_value_key) else { + continue; + }; + assert_eq!(section.get_type_id(), cf::Array::type_id()); + + let section: &cf::ArrayOf> = + unsafe { std::mem::transmute(section) }; + + let mut team_id = None; + let mut team_name = None; + for dict in section.iter() { + let Some(label) = dict.get(prop_label_key) else { + continue; + }; + let Some(value) = dict.get(prop_value_key) else { + continue; + }; + + // todo: build safer api + if value.get_type_id() != cf::String::type_id() { + continue; + } + let value: &cf::String = unsafe { std::mem::transmute(value) }; + + if label.equal(org_name_label) { + team_name = Some(value); + } else if label.equal(unit_name_label) { + team_id = Some(value); + } + } + + if let (Some(id), Some(name)) = (team_id, team_name) { + if filter_set.contains(id) { + continue; + }; + println!("{id}: {name}"); + filter_set.insert(id); + } + } + if filter_set.is_empty() { + println!("no teams are found"); + } + } +} + +mod cargo { + use std::{env, path::PathBuf}; + + use cargo_toml::{Manifest, Workspace}; + + pub(crate) fn manifests() -> Option<(PathBuf, Vec, Option)> { + let mut path = root()?.join("Cargo.toml"); + if let Ok(mut res) = Manifest::from_path(&path) { + path.pop(); + res.complete_from_path(&path).unwrap(); + if let Some(ws) = res.workspace { + let mut vec = Vec::with_capacity(ws.members.len()); + for member in ws.members.iter() { + path = path.join(&format!("{member}/Cargo.toml")); + let mut man = Manifest::from_path(&path).unwrap(); + path.pop(); + man.complete_from_path(&path).unwrap(); + path.pop(); + vec.push(man); + } + Some((path, vec, Some(ws))) + } else { + // println!("workspace: {:?}", res.workspace); + Some((path, vec![res], None)) + } + } else { + None + } + } + + fn root() -> Option { + let cwd = env::current_dir().unwrap(); + let mut cwd_clone = cwd.clone(); + cwd_clone.push("target"); + if cwd_clone.exists() { + // we are good + return Some(cwd); + } + cwd_clone.pop(); // target + cwd_clone.pop(); // .. + cwd_clone.push("target"); + if cwd_clone.exists() { + // we are good + cwd_clone.pop(); + return Some(cwd_clone); + } + None + } +} + +mod xcode { + use std::fs; + + use cargo_toml::{Manifest, Product}; + + use crate::macos::cargo; + + pub(crate) fn build(project: &str, platform: &str, conf: &str, target: &str) { + std::process::Command::new("xcodebuild") + .args(["-project", project]) + .args(["-destination", &format!("generic/platform={platform}")]) + .args(["-configuration", conf]) + .args(["-scheme", "box", "-quiet"]) + .args(["-derivedDataPath", target]) + // .args(env_args) + .status() + .unwrap(); + } + + #[derive(clap::Args, Debug)] + pub(crate) struct ProjArgs { + #[arg(long)] + pub(crate) bin: Option, + #[arg(long)] + pub(crate) example: Option, + #[arg(long)] + pub(crate) dep: Option, + } + + fn find_product<'a>( + mans: &'a [Manifest], + args: &ProjArgs, + ) -> Option<(&'a Manifest, &'a Product)> { + let (is_bin, name, nothing, not_found, available) = if args.example.is_some() { + ( + false, + args.example.as_ref(), + "No examples.", + "No examples found with name", + "Available examples:", + ) + } else { + ( + true, + args.bin.as_ref(), + "No binaries.", + "No binary found with name", + "Available binaries:", + ) + }; + + let products = |man: &'a Manifest| -> &'a [Product] { + if is_bin { + &man.bin + } else { + &man.example + } + }; + + if let Some(product_name) = name.as_ref() { + let mut count = 0; + for man in mans { + for product in products(man) { + if product.name.as_ref() == name { + return Some((man, product)); + } + count += 1; + } + } + if count == 0 { + println!("{nothing}"); + } else { + println!("{not_found} `{product_name}`"); + println!("{available}"); + for man in mans { + for product in products(man) { + if let Some(name) = product.name.as_ref() { + println!("\t{name}"); + } + } + } + } + + return None; + } + + for man in mans { + for product in man.bin.iter() { + if product.name.is_some() { + return Some((man, product)); + } + } + } + + println!("No binaries"); + + None + } + + pub(crate) fn proj(args: ProjArgs) { + let (mut path, mans, _ws) = cargo::manifests().unwrap(); + let product = if args.dep.is_some() { + &Product { + path: None, + name: args.dep.clone(), + test: false, + doctest: false, + bench: false, + doc: false, + plugin: false, + proc_macro: false, + harness: true, + edition: None, + crate_type: vec![], + required_features: vec![], + } + } else { + let Some((_man, product)) = find_product(&mans, &args) else { + return; + }; + product + }; + + path.push(".box"); + _ = dotenv::from_filename(".box"); + path.pop(); + + let Ok(box_org_id) = std::env::var("BOX_ORG_ID") else { + println!("BOX_ORG_ID env is required. You can add it .box file"); + return; + }; + let Ok(dev_team_id) = std::env::var("DEVELOPMENT_TEAM") else { + println!("DEVELOPMENT_TEAM env is required. You can add it .box file"); + println!("use cidre-box teams command to list available team ids"); + return; + }; + + path.push("target/boxes"); + if args.example.is_some() { + path.push("examples") + } + if args.dep.is_some() { + path.push("deps") + } + let product_name = product.name.as_ref().unwrap(); + path.push(product_name); + + fs::create_dir_all(&path).unwrap(); + path.push("box.entitlements"); + fs::write(&path, include_str!("../box/box.entitlements")).unwrap(); + path.pop(); + path.push("cfg.xcconfig"); + fs::write(&path, include_str!("../box/cfg.xcconfig")).unwrap(); + path.pop(); + path.push("box.xcconfig"); + fs::write( + &path, + format!( + r#" +PRODUCT_NAME = {product_name} +BOX_ID = {product_name} +DEVELOPMENT_TEAM = {dev_team_id} +BOX_ORG_ID = {box_org_id} +"# + ), + ) + .unwrap(); + + path.pop(); + + path.push("box.xcodeproj"); + fs::create_dir_all(&path).unwrap(); + path.push("project.pbxproj"); + fs::write(&path, include_str!("../box/box.xcodeproj/project.pbxproj")).unwrap(); + path.pop(); + path.push("xcshareddata/xcschemes"); + fs::create_dir_all(&path).unwrap(); + path.push("box.xcscheme"); + fs::write( + &path, + include_str!("../box/box.xcodeproj/xcshareddata/xcschemes/box.xcscheme"), + ) + .unwrap(); + } +} + +mod device_ctl { + use std::{env, fs, process}; + + fn run_cmd(args: &[&str]) -> String { + let json_output_path = env::temp_dir().join(format!("devicectl-{}.json", process::id())); + let mut child = process::Command::new("xcrun") + .args(["devicectl", "-q", "--json-output"]) + .arg(&json_output_path) + .args(args) + .spawn() + .unwrap(); + child.wait().unwrap(); + let buf = fs::read_to_string(&json_output_path).unwrap(); + let _ = fs::remove_file(json_output_path); + buf + } + + pub(crate) fn install_app(device_id: &str, bundle: &str) { + run_cmd(&["device", "install", "app", "-d", device_id, bundle]); + } + + pub(crate) fn run_app(device_id: &str, id: &str, args: &[String]) { + let mut args_vec = vec![ + "device", + "process", + "launch", + "--console", + "-d", + device_id, + "--terminate-existing", + id, + ]; + for s in args { + args_vec.push(&s); + } + let buf = run_cmd(&args_vec); + let run = serde_json::from_str::(&buf).unwrap(); + let code = match (run.result.termination.signal, run.result.termination.code) { + (Some(signal), None) => signal, + (None, Some(code)) => code, + _ => 0, + }; + std::process::exit(code); + } + + pub(crate) fn list_devices() { + let buf = run_cmd(&["list", "devices"]); + let list = serde_json::from_str::(&buf).unwrap(); + println!("{:#?}", list.result.devices); + } + + mod json { + use serde::Deserialize; + use std::borrow::Cow; + + #[derive(Deserialize, Debug)] + pub(crate) struct AppRun { + pub(crate) result: AppRunResult, + } + + #[derive(Deserialize, Debug)] + pub(crate) struct AppRunResult { + #[serde(rename = "terminationResult")] + pub(crate) termination: AppRunTermination, + } + + #[derive(Deserialize, Debug)] + pub(crate) struct AppRunTermination { + #[serde(rename = "terminatingSignal")] + pub(crate) signal: Option, + #[serde(rename = "exitCode")] + pub(crate) code: Option, + } + + #[derive(Deserialize, Debug)] + pub(crate) struct DeviceList<'a> { + #[serde(borrow)] + pub(crate) result: ListResult<'a>, + } + + #[derive(Deserialize, Debug)] + pub(crate) struct ListResult<'a> { + #[serde(borrow)] + pub(crate) devices: Vec>, + } + + // #[serde(rename_all = "camelCase")] + #[derive(Deserialize, Debug)] + pub(crate) struct Device<'a> { + #[serde(rename = "identifier")] + pub(crate) id: &'a str, + + #[serde(borrow)] + #[serde(rename = "deviceProperties")] + pub(crate) props: DeviceProps<'a>, + + // pub(crate) visibility_class: &'a str, + #[serde(borrow)] + #[serde(rename = "connectionProperties")] + pub(crate) connection: ConnectionProps<'a>, + + #[serde(borrow)] + #[serde(rename = "hardwareProperties")] + pub(crate) hardware: HwProps<'a>, + } + + #[derive(Deserialize, Debug)] + pub(crate) struct DeviceProps<'a> { + pub(crate) name: Cow<'a, str>, + #[serde(rename = "developerModeStatus")] + pub(crate) dev_mode_status: Option<&'a str>, + } + + #[derive(Deserialize, Debug)] + #[serde(rename_all = "camelCase")] + pub(crate) struct HwProps<'a> { + pub(crate) device_type: &'a str, + pub(crate) platform: &'a str, + pub(crate) udid: &'a str, + } + + #[derive(Deserialize, Debug)] + #[serde(rename_all = "camelCase")] + pub(crate) struct ConnectionProps<'a> { + pub(crate) pairing_state: &'a str, + } + } +} diff --git a/cidre/examples/nw-connection/main.rs b/cidre/examples/nw-connection/main.rs index aa9f3014..aefd872a 100644 --- a/cidre/examples/nw-connection/main.rs +++ b/cidre/examples/nw-connection/main.rs @@ -75,5 +75,5 @@ pub use macos::main; #[cfg(not(target_os = "macos"))] fn main() { - todo!() + println!("todo"); } diff --git a/cidre/src/api.rs b/cidre/src/api.rs index 3d5bb008..f7e3a371 100644 --- a/cidre/src/api.rs +++ b/cidre/src/api.rs @@ -245,6 +245,7 @@ mod tests { assert!(SHOULD_BE_FOUND.get_var().unwrap().len() > 0); } + #[cfg(target_os = "macos")] #[test] fn version() { assert!(api::macos_available("13.0")); @@ -253,4 +254,14 @@ mod tests { assert!(!api::version!(visionos = 1.0)); assert!(!api::version!(macos = 20.0)); } + + #[cfg(target_os = "ios")] + #[test] + fn version() { + assert!(api::ios_available("18.0")); + assert!(api::ios_available("18.2")); + assert!(api::version!(macos = 13.0, ios = 14.0)); + assert!(!api::version!(visionos = 1.0)); + assert!(!api::version!(macos = 20.0)); + } } diff --git a/cidre/src/at/audio/unit/component.rs b/cidre/src/at/audio/unit/component.rs index 92df699b..e46443ef 100644 --- a/cidre/src/at/audio/unit/component.rs +++ b/cidre/src/at/audio/unit/component.rs @@ -1450,6 +1450,7 @@ impl audio::Component { #[cfg(test)] mod tests { + #[cfg(target_os = "macos")] use std::{f32::consts::PI, ffi::c_void}; use au::Element; diff --git a/cidre/src/at/audio/unit/multi_channel_mixer.rs b/cidre/src/at/audio/unit/multi_channel_mixer.rs index 7aa3d92c..3996083f 100644 --- a/cidre/src/at/audio/unit/multi_channel_mixer.rs +++ b/cidre/src/at/audio/unit/multi_channel_mixer.rs @@ -362,8 +362,14 @@ mod tests { assert!(!mixer.is_input_enabled(0).unwrap()); // default volumes are zero + #[cfg(target_os = "macos")] assert_eq!(0.0f32, mixer.input_volume(0).unwrap()); + #[cfg(target_os = "ios")] + assert_eq!(1.0f32, mixer.input_volume(0).unwrap()); + #[cfg(target_os = "macos")] assert_eq!(0.0f32, mixer.output_volume().unwrap()); + #[cfg(target_os = "ios")] + assert_eq!(1.0f32, mixer.output_volume().unwrap()); // metering is not enabled by default, so we can't access params assert_eq!(false, mixer.is_input_metering_enabled(0).unwrap()); diff --git a/cidre/src/sec/item.rs b/cidre/src/sec/item.rs index d7fb231b..802b1279 100644 --- a/cidre/src/sec/item.rs +++ b/cidre/src/sec/item.rs @@ -299,6 +299,7 @@ pub mod return_data { } } +#[cfg(target_os = "macos")] #[cfg(test)] mod tests { use crate::{arc, cf, sec};