feat: add crate swift-rs
This commit is contained in:
26
swift-rs/src-rs/autorelease.rs
Normal file
26
swift-rs/src-rs/autorelease.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
/// Run code with its own autorelease pool. Semantically, this is identical
|
||||
/// to [`@autoreleasepool`](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html)
|
||||
/// in Objective-C
|
||||
///
|
||||
///
|
||||
/// ```no_run
|
||||
/// use swift_rs::autoreleasepool;
|
||||
///
|
||||
/// autoreleasepool!({
|
||||
/// // do something memory intensive stuff
|
||||
/// })
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! autoreleasepool {
|
||||
( $expr:expr ) => {{
|
||||
extern "C" {
|
||||
fn objc_autoreleasePoolPush() -> *mut std::ffi::c_void;
|
||||
fn objc_autoreleasePoolPop(context: *mut std::ffi::c_void);
|
||||
}
|
||||
|
||||
let pool = unsafe { objc_autoreleasePoolPush() };
|
||||
let r = { $expr };
|
||||
unsafe { objc_autoreleasePoolPop(pool) };
|
||||
r
|
||||
}};
|
||||
}
|
||||
326
swift-rs/src-rs/build.rs
Normal file
326
swift-rs/src-rs/build.rs
Normal file
@@ -0,0 +1,326 @@
|
||||
#![allow(dead_code)]
|
||||
use std::{env, fmt::Display, path::Path, path::PathBuf, process::Command};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct SwiftTarget {
|
||||
triple: String,
|
||||
unversioned_triple: String,
|
||||
module_triple: String,
|
||||
//pub swift_runtime_compatibility_version: String,
|
||||
#[serde(rename = "librariesRequireRPath")]
|
||||
libraries_require_rpath: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct SwiftPaths {
|
||||
runtime_library_paths: Vec<String>,
|
||||
runtime_library_import_paths: Vec<String>,
|
||||
runtime_resource_path: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SwiftEnv {
|
||||
target: SwiftTarget,
|
||||
paths: SwiftPaths,
|
||||
}
|
||||
|
||||
impl SwiftEnv {
|
||||
fn new(minimum_macos_version: &str, minimum_ios_version: Option<&str>) -> Self {
|
||||
let rust_target = RustTarget::from_env();
|
||||
let target = rust_target.swift_target_triple(minimum_macos_version, minimum_ios_version);
|
||||
|
||||
let swift_target_info_str = Command::new("swift")
|
||||
.args(["-target", &target, "-print-target-info"])
|
||||
.output()
|
||||
.unwrap()
|
||||
.stdout;
|
||||
|
||||
serde_json::from_slice(&swift_target_info_str).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
enum RustTargetOS {
|
||||
MacOS,
|
||||
IOS,
|
||||
}
|
||||
|
||||
impl RustTargetOS {
|
||||
fn from_env() -> Self {
|
||||
match env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() {
|
||||
"macos" => RustTargetOS::MacOS,
|
||||
"ios" => RustTargetOS::IOS,
|
||||
_ => panic!("unexpected target operating system"),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_swift(&self) -> &'static str {
|
||||
match self {
|
||||
Self::MacOS => "macosx",
|
||||
Self::IOS => "ios",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RustTargetOS {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MacOS => write!(f, "macos"),
|
||||
Self::IOS => write!(f, "ios"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
enum SwiftSDK {
|
||||
MacOS,
|
||||
IOS,
|
||||
IOSSimulator,
|
||||
}
|
||||
|
||||
impl SwiftSDK {
|
||||
fn from_os(os: &RustTargetOS) -> Self {
|
||||
let target = env::var("TARGET").unwrap();
|
||||
let simulator = target.ends_with("ios-sim")
|
||||
|| (target.starts_with("x86_64") && target.ends_with("ios"));
|
||||
|
||||
match os {
|
||||
RustTargetOS::MacOS => Self::MacOS,
|
||||
RustTargetOS::IOS if simulator => Self::IOSSimulator,
|
||||
RustTargetOS::IOS => Self::IOS,
|
||||
}
|
||||
}
|
||||
|
||||
fn clang_lib_extension(&self) -> &'static str {
|
||||
match self {
|
||||
Self::MacOS => "osx",
|
||||
Self::IOS => "ios",
|
||||
Self::IOSSimulator => "iossim",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SwiftSDK {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MacOS => write!(f, "macosx"),
|
||||
Self::IOSSimulator => write!(f, "iphonesimulator"),
|
||||
Self::IOS => write!(f, "iphoneos"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RustTarget {
|
||||
arch: String,
|
||||
os: RustTargetOS,
|
||||
sdk: SwiftSDK,
|
||||
}
|
||||
|
||||
impl RustTarget {
|
||||
fn from_env() -> Self {
|
||||
let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
let os = RustTargetOS::from_env();
|
||||
let sdk = SwiftSDK::from_os(&os);
|
||||
|
||||
Self { arch, os, sdk }
|
||||
}
|
||||
|
||||
fn swift_target_triple(
|
||||
&self,
|
||||
minimum_macos_version: &str,
|
||||
minimum_ios_version: Option<&str>,
|
||||
) -> String {
|
||||
let unversioned = self.unversioned_swift_target_triple();
|
||||
format!(
|
||||
"{unversioned}{}{}",
|
||||
match (&self.os, minimum_ios_version) {
|
||||
(RustTargetOS::MacOS, _) => minimum_macos_version,
|
||||
(RustTargetOS::IOS, Some(version)) => version,
|
||||
_ => "",
|
||||
},
|
||||
// simulator suffix
|
||||
matches!(self.sdk, SwiftSDK::IOSSimulator)
|
||||
.then(|| "-simulator".to_string())
|
||||
.unwrap_or_default()
|
||||
)
|
||||
}
|
||||
|
||||
fn unversioned_swift_target_triple(&self) -> String {
|
||||
format!(
|
||||
"{}-apple-{}",
|
||||
match self.arch.as_str() {
|
||||
"aarch64" => "arm64",
|
||||
a => a,
|
||||
},
|
||||
self.os.to_swift(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct SwiftPackage {
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
/// Builder for linking the Swift runtime and custom packages.
|
||||
#[cfg(feature = "build")]
|
||||
pub struct SwiftLinker {
|
||||
packages: Vec<SwiftPackage>,
|
||||
macos_min_version: String,
|
||||
ios_min_version: Option<String>,
|
||||
}
|
||||
|
||||
impl SwiftLinker {
|
||||
/// Creates a new [`SwiftLinker`] with a minimum macOS verison.
|
||||
///
|
||||
/// Minimum macOS version must be at least 10.13.
|
||||
pub fn new(macos_min_version: &str) -> Self {
|
||||
Self {
|
||||
packages: vec![],
|
||||
macos_min_version: macos_min_version.to_string(),
|
||||
ios_min_version: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Instructs the [`SwiftLinker`] to also compile for iOS
|
||||
/// using the specified minimum iOS version.
|
||||
///
|
||||
/// Minimum iOS version must be at least 11.
|
||||
pub fn with_ios(mut self, min_version: &str) -> Self {
|
||||
self.ios_min_version = Some(min_version.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a package to be linked against.
|
||||
/// `name` should match the `name` field in your `Package.swift`,
|
||||
/// and `path` should point to the root of your Swift package relative
|
||||
/// to your crate's root.
|
||||
pub fn with_package(mut self, name: &str, path: impl AsRef<Path>) -> Self {
|
||||
self.packages.extend([SwiftPackage {
|
||||
name: name.to_string(),
|
||||
path: path.as_ref().into(),
|
||||
}]);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Links the Swift runtime, then builds and links the provided packages.
|
||||
/// This does not (yet) automatically rebuild your Swift files when they are modified,
|
||||
/// you'll need to modify/save your `build.rs` file for that.
|
||||
pub fn link(self) {
|
||||
let swift_env = SwiftEnv::new(&self.macos_min_version, self.ios_min_version.as_deref());
|
||||
|
||||
#[allow(clippy::uninlined_format_args)]
|
||||
for path in swift_env.paths.runtime_library_paths {
|
||||
println!("cargo:rustc-link-search=native={path}");
|
||||
}
|
||||
|
||||
let debug = env::var("DEBUG").unwrap() == "true";
|
||||
let configuration = if debug { "debug" } else { "release" };
|
||||
let rust_target = RustTarget::from_env();
|
||||
|
||||
link_clang_rt(&rust_target);
|
||||
|
||||
for package in self.packages {
|
||||
let package_path =
|
||||
Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join(&package.path);
|
||||
let out_path = Path::new(&env::var("OUT_DIR").unwrap())
|
||||
.join("swift-rs")
|
||||
.join(&package.name);
|
||||
|
||||
let sdk_path_output = Command::new("xcrun")
|
||||
.args(["--sdk", &rust_target.sdk.to_string(), "--show-sdk-path"])
|
||||
.output()
|
||||
.unwrap();
|
||||
if !sdk_path_output.status.success() {
|
||||
panic!(
|
||||
"Failed to get SDK path with `xcrun --sdk {} --show-sdk-path`",
|
||||
rust_target.sdk
|
||||
);
|
||||
}
|
||||
|
||||
let sdk_path = String::from_utf8_lossy(&sdk_path_output.stdout);
|
||||
|
||||
let mut command = Command::new("swift");
|
||||
command.current_dir(&package.path);
|
||||
|
||||
let arch = match std::env::consts::ARCH {
|
||||
"aarch64" => "arm64",
|
||||
arch => arch,
|
||||
};
|
||||
|
||||
command
|
||||
// Build the package (duh)
|
||||
.args(["build"])
|
||||
// SDK path for regular compilation (idk)
|
||||
.args(["--sdk", sdk_path.trim()])
|
||||
// Release/Debug configuration
|
||||
.args(["-c", configuration])
|
||||
.args(["--arch", arch])
|
||||
// Where the artifacts will be generated to
|
||||
.args(["--build-path", &out_path.display().to_string()])
|
||||
// Override SDK path for each swiftc instance.
|
||||
// Necessary for iOS compilation.
|
||||
.args(["-Xswiftc", "-sdk"])
|
||||
.args(["-Xswiftc", sdk_path.trim()])
|
||||
// Override target triple for each swiftc instance.
|
||||
// Necessary for iOS compilation.
|
||||
.args(["-Xswiftc", "-target"])
|
||||
.args([
|
||||
"-Xswiftc",
|
||||
&rust_target.swift_target_triple(
|
||||
&self.macos_min_version,
|
||||
self.ios_min_version.as_deref(),
|
||||
),
|
||||
]);
|
||||
|
||||
if !command.status().unwrap().success() {
|
||||
panic!("Failed to compile swift package {}", package.name);
|
||||
}
|
||||
|
||||
let search_path = out_path
|
||||
// swift build uses this output folder no matter what is the target
|
||||
.join(format!(
|
||||
"{}-apple-macosx",
|
||||
arch
|
||||
))
|
||||
.join(configuration);
|
||||
|
||||
println!("cargo:rerun-if-changed={}", package_path.display());
|
||||
println!("cargo:rustc-link-search=native={}", search_path.display());
|
||||
println!("cargo:rustc-link-lib=static={}", package.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn link_clang_rt(rust_target: &RustTarget) {
|
||||
println!(
|
||||
"cargo:rustc-link-lib=clang_rt.{}",
|
||||
rust_target.sdk.clang_lib_extension()
|
||||
);
|
||||
println!("cargo:rustc-link-search={}", clang_link_search_path());
|
||||
}
|
||||
|
||||
fn clang_link_search_path() -> String {
|
||||
let output = std::process::Command::new(
|
||||
std::env::var("SWIFT_RS_CLANG").unwrap_or_else(|_| "/usr/bin/clang".to_string()),
|
||||
)
|
||||
.arg("--print-search-dirs")
|
||||
.output()
|
||||
.unwrap();
|
||||
if !output.status.success() {
|
||||
panic!("Can't get search paths from clang");
|
||||
}
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
for line in stdout.lines() {
|
||||
if line.contains("libraries: =") {
|
||||
let path = line.split('=').nth(1).unwrap();
|
||||
return format!("{}/lib/darwin", path);
|
||||
}
|
||||
}
|
||||
panic!("clang is missing search paths");
|
||||
}
|
||||
90
swift-rs/src-rs/dark_magic.rs
Normal file
90
swift-rs/src-rs/dark_magic.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
/// This retain-balancing algorithm is cool but likely isn't required.
|
||||
/// I'm keeping it around in case it's necessary one day.
|
||||
|
||||
// #[derive(Clone, Copy, Debug)]
|
||||
// enum ValueArity {
|
||||
// Reference,
|
||||
// Value,
|
||||
// }
|
||||
|
||||
// pub unsafe fn balance_ptrs(args: Vec<(*const c_void, bool)>, ret: Vec<(*const c_void, bool)>) {
|
||||
// fn collect_references(
|
||||
// v: Vec<(*const c_void, bool)>,
|
||||
// ) -> BTreeMap<*const c_void, Vec<ValueArity>> {
|
||||
// v.into_iter().fold(
|
||||
// BTreeMap::<_, Vec<ValueArity>>::new(),
|
||||
// |mut map, (ptr, is_ref)| {
|
||||
// map.entry(ptr).or_default().push(if is_ref {
|
||||
// ValueArity::Reference
|
||||
// } else {
|
||||
// ValueArity::Value
|
||||
// });
|
||||
// map
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
|
||||
// let mut args = collect_references(args);
|
||||
// let mut ret = collect_references(ret);
|
||||
|
||||
// let both_counts = args
|
||||
// .clone()
|
||||
// .into_iter()
|
||||
// .flat_map(|(arg, values)| {
|
||||
// ret.remove(&arg).map(|ret| {
|
||||
// args.remove(&arg);
|
||||
|
||||
// let ret_values = ret
|
||||
// .iter()
|
||||
// .filter(|v| matches!(v, ValueArity::Value))
|
||||
// .count() as isize;
|
||||
|
||||
// let arg_references = values
|
||||
// .iter()
|
||||
// .filter(|v| matches!(v, ValueArity::Reference))
|
||||
// .count() as isize;
|
||||
|
||||
// let ref_in_value_out_retains = min(ret_values, arg_references);
|
||||
|
||||
// (arg, ref_in_value_out_retains)
|
||||
// })
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
|
||||
// let arg_counts = args.into_iter().map(|(ptr, values)| {
|
||||
// let count = values
|
||||
// .into_iter()
|
||||
// .filter(|v| matches!(v, ValueArity::Value))
|
||||
// .count() as isize;
|
||||
// (ptr, count)
|
||||
// });
|
||||
|
||||
// let ret_counts = ret
|
||||
// .into_iter()
|
||||
// .map(|(ptr, values)| {
|
||||
// let count = values
|
||||
// .into_iter()
|
||||
// .filter(|v| matches!(v, ValueArity::Value))
|
||||
// .count() as isize;
|
||||
// (ptr, count)
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
|
||||
// both_counts
|
||||
// .into_iter()
|
||||
// .chain(arg_counts)
|
||||
// .chain(ret_counts)
|
||||
// .for_each(|(ptr, count)| match count {
|
||||
// 0 => {}
|
||||
// n if n > 0 => {
|
||||
// for _ in 0..n {
|
||||
// retain_object(ptr)
|
||||
// }
|
||||
// }
|
||||
// n => {
|
||||
// for _ in n..0 {
|
||||
// release_object(ptr)
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
20
swift-rs/src-rs/lib.rs
Normal file
20
swift-rs/src-rs/lib.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
//! Call Swift functions from Rust with ease!
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
mod autorelease;
|
||||
mod swift;
|
||||
mod swift_arg;
|
||||
mod swift_ret;
|
||||
mod types;
|
||||
|
||||
pub use autorelease::*;
|
||||
pub use swift::*;
|
||||
pub use swift_arg::*;
|
||||
pub use swift_ret::*;
|
||||
pub use types::*;
|
||||
|
||||
#[cfg(feature = "build")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "build")))]
|
||||
mod build;
|
||||
#[cfg(feature = "build")]
|
||||
pub use build::*;
|
||||
101
swift-rs/src-rs/swift.rs
Normal file
101
swift-rs/src-rs/swift.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use std::ffi::c_void;
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// Reference to an `NSObject` for internal use by [`swift!`].
|
||||
#[must_use = "A Ref MUST be sent over to the Swift side"]
|
||||
#[repr(transparent)]
|
||||
pub struct SwiftRef<'a, T: SwiftObject>(&'a SRObjectImpl<T::Shape>);
|
||||
|
||||
impl<'a, T: SwiftObject> SwiftRef<'a, T> {
|
||||
pub(crate) unsafe fn retain(&self) {
|
||||
retain_object(self.0 as *const _ as *const c_void)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that is represented as an `NSObject` in Swift.
|
||||
pub trait SwiftObject {
|
||||
type Shape;
|
||||
|
||||
/// Gets a reference to the `SRObject` at the root of a `SwiftObject`
|
||||
fn get_object(&self) -> &SRObject<Self::Shape>;
|
||||
|
||||
/// Creates a [`SwiftRef`] for an object which can be used when calling a Swift function.
|
||||
/// This function should never be called manually,
|
||||
/// instead you should rely on the [`swift!`] macro to call it for you.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function converts the [`NonNull`](std::ptr::NonNull)
|
||||
/// inside an [`SRObject`] into a reference,
|
||||
/// implicitly assuming that the pointer is still valid.
|
||||
/// The inner pointer is private,
|
||||
/// and the returned [`SwiftRef`] is bound to the lifetime of the original [`SRObject`],
|
||||
/// so if you use `swift-rs` as normal this function should be safe.
|
||||
unsafe fn swift_ref(&self) -> SwiftRef<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
SwiftRef(self.get_object().0.as_ref())
|
||||
}
|
||||
|
||||
/// Adds a retain to an object.
|
||||
///
|
||||
/// # Safety
|
||||
/// Just don't call this, let [`swift!`] handle it for you.
|
||||
unsafe fn retain(&self)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.swift_ref().retain()
|
||||
}
|
||||
}
|
||||
|
||||
swift!(pub(crate) fn retain_object(obj: *const c_void));
|
||||
swift!(pub(crate) fn release_object(obj: *const c_void));
|
||||
swift!(pub(crate) fn data_from_bytes(data: *const u8, size: Int) -> SRData);
|
||||
swift!(pub(crate) fn string_from_bytes(data: *const u8, size: Int) -> SRString);
|
||||
|
||||
/// Declares a function defined in a swift library.
|
||||
/// As long as this macro is used, retain counts of arguments
|
||||
/// and return values will be correct.
|
||||
///
|
||||
/// Use this macro as if the contents were going directly
|
||||
/// into an `extern "C"` block.
|
||||
///
|
||||
/// ```
|
||||
/// use swift_rs::*;
|
||||
///
|
||||
/// swift!(fn echo(string: &SRString) -> SRString);
|
||||
///
|
||||
/// let string: SRString = "test".into();
|
||||
/// let result = unsafe { echo(&string) };
|
||||
///
|
||||
/// assert_eq!(result.as_str(), string.as_str())
|
||||
/// ```
|
||||
///
|
||||
/// # Details
|
||||
///
|
||||
/// Internally this macro creates a wrapping function around an `extern "C"` block
|
||||
/// that represents the actual Swift function. This is done in order to restrict the types
|
||||
/// that can be used as arguments and return types, and to ensure that retain counts of returned
|
||||
/// values are appropriately balanced.
|
||||
#[macro_export]
|
||||
macro_rules! swift {
|
||||
($vis:vis fn $name:ident $(<$($lt:lifetime),+>)? ($($arg:ident: $arg_ty:ty),*) $(-> $ret:ty)?) => {
|
||||
$vis unsafe fn $name $(<$($lt),*>)? ($($arg: $arg_ty),*) $(-> $ret)? {
|
||||
extern "C" {
|
||||
fn $name $(<$($lt),*>)? ($($arg: <$arg_ty as $crate::SwiftArg>::ArgType),*) $(-> $ret)?;
|
||||
}
|
||||
|
||||
let res = {
|
||||
$(let $arg = $crate::SwiftArg::as_arg(&$arg);)*
|
||||
|
||||
$name($($arg),*)
|
||||
};
|
||||
|
||||
$crate::SwiftRet::retain(&res);
|
||||
|
||||
res
|
||||
}
|
||||
};
|
||||
}
|
||||
75
swift-rs/src-rs/swift_arg.rs
Normal file
75
swift-rs/src-rs/swift_arg.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use std::ffi::c_void;
|
||||
|
||||
use crate::{swift::SwiftObject, *};
|
||||
|
||||
/// Identifies a type as being a valid argument in a Swift function.
|
||||
pub trait SwiftArg<'a> {
|
||||
type ArgType;
|
||||
|
||||
/// Creates a swift-compatible version of the argument.
|
||||
/// For primitives this just returns `self`,
|
||||
/// but for [`SwiftObject`] types it wraps them in [`SwiftRef`].
|
||||
///
|
||||
/// This function is called within the [`swift!`] macro.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Creating a [`SwiftRef`] is inherently unsafe,
|
||||
/// but is reliable if using the [`swift!`] macro,
|
||||
/// so it is not advised to call this function manually.
|
||||
unsafe fn as_arg(&'a self) -> Self::ArgType;
|
||||
}
|
||||
|
||||
macro_rules! primitive_impl {
|
||||
($($t:ty),+) => {
|
||||
$(impl<'a> SwiftArg<'a> for $t {
|
||||
type ArgType = $t;
|
||||
|
||||
unsafe fn as_arg(&'a self) -> Self::ArgType {
|
||||
*self
|
||||
}
|
||||
})+
|
||||
};
|
||||
}
|
||||
|
||||
primitive_impl!(
|
||||
Bool,
|
||||
Int,
|
||||
Int8,
|
||||
Int16,
|
||||
Int32,
|
||||
Int64,
|
||||
UInt,
|
||||
UInt8,
|
||||
UInt16,
|
||||
UInt32,
|
||||
UInt64,
|
||||
Float32,
|
||||
Float64,
|
||||
*const c_void,
|
||||
*mut c_void,
|
||||
*const u8,
|
||||
()
|
||||
);
|
||||
|
||||
macro_rules! ref_impl {
|
||||
($($t:ident $(<$($gen:ident),+>)?),+) => {
|
||||
$(impl<'a $($(, $gen: 'a),+)?> SwiftArg<'a> for $t$(<$($gen),+>)? {
|
||||
type ArgType = SwiftRef<'a, $t$(<$($gen),+>)?>;
|
||||
|
||||
unsafe fn as_arg(&'a self) -> Self::ArgType {
|
||||
self.swift_ref()
|
||||
}
|
||||
})+
|
||||
};
|
||||
}
|
||||
|
||||
ref_impl!(SRObject<T>, SRArray<T>, SRData, SRString);
|
||||
|
||||
impl<'a, T: SwiftArg<'a>> SwiftArg<'a> for &T {
|
||||
type ArgType = T::ArgType;
|
||||
|
||||
unsafe fn as_arg(&'a self) -> Self::ArgType {
|
||||
(*self).as_arg()
|
||||
}
|
||||
}
|
||||
55
swift-rs/src-rs/swift_ret.rs
Normal file
55
swift-rs/src-rs/swift_ret.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use crate::{swift::SwiftObject, *};
|
||||
use std::ffi::c_void;
|
||||
|
||||
/// Identifies a type as being a valid return type from a Swift function.
|
||||
/// For types that are objects which need extra retains,
|
||||
/// the [`retain`](SwiftRet::retain) function will be re-implemented.
|
||||
pub trait SwiftRet {
|
||||
/// Adds a retain to the value if possible
|
||||
///
|
||||
/// # Safety
|
||||
/// Just don't use this.
|
||||
/// Let [`swift!`] handle it.
|
||||
unsafe fn retain(&self) {}
|
||||
}
|
||||
|
||||
macro_rules! primitive_impl {
|
||||
($($t:ty),+) => {
|
||||
$(impl SwiftRet for $t {
|
||||
})+
|
||||
};
|
||||
}
|
||||
|
||||
primitive_impl!(
|
||||
Bool,
|
||||
Int,
|
||||
Int8,
|
||||
Int16,
|
||||
Int32,
|
||||
Int64,
|
||||
UInt,
|
||||
UInt8,
|
||||
UInt16,
|
||||
UInt32,
|
||||
UInt64,
|
||||
Float32,
|
||||
Float64,
|
||||
*const c_void,
|
||||
*mut c_void,
|
||||
*const u8,
|
||||
()
|
||||
);
|
||||
|
||||
impl<T: SwiftObject> SwiftRet for Option<T> {
|
||||
unsafe fn retain(&self) {
|
||||
if let Some(v) = self {
|
||||
v.retain()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SwiftObject> SwiftRet for T {
|
||||
unsafe fn retain(&self) {
|
||||
(*self).retain()
|
||||
}
|
||||
}
|
||||
20
swift-rs/src-rs/test-build.rs
Normal file
20
swift-rs/src-rs/test-build.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
//! Build script for swift-rs that is a no-op for normal builds, but can be enabled
|
||||
//! to include test swift library based on env var `TEST_SWIFT_RS=true` with the
|
||||
//! `build` feature being enabled.
|
||||
|
||||
#[cfg(feature = "build")]
|
||||
mod build;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-env-changed=TEST_SWIFT_RS");
|
||||
|
||||
#[cfg(feature = "build")]
|
||||
if std::env::var("TEST_SWIFT_RS").unwrap_or_else(|_| "false".into()) == "true" {
|
||||
use build::SwiftLinker;
|
||||
|
||||
SwiftLinker::new("10.15")
|
||||
.with_ios("11")
|
||||
.with_package("test-swift", "tests/swift-pkg")
|
||||
.link();
|
||||
}
|
||||
}
|
||||
110
swift-rs/src-rs/types/array.rs
Normal file
110
swift-rs/src-rs/types/array.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use std::{ops::Deref, ptr::NonNull};
|
||||
|
||||
use crate::swift::SwiftObject;
|
||||
|
||||
use super::SRObject;
|
||||
|
||||
/// Wrapper of [`SRArray`] exclusively for arrays of objects.
|
||||
/// Equivalent to `SRObjectArray` in Swift.
|
||||
// SRArray is wrapped in SRObject since the Swift implementation extends NSObject
|
||||
pub type SRObjectArray<T> = SRObject<SRArray<SRObject<T>>>;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[repr(C)]
|
||||
pub struct SRArrayImpl<T> {
|
||||
data: NonNull<T>,
|
||||
length: usize,
|
||||
}
|
||||
|
||||
/// General array type for objects and scalars.
|
||||
///
|
||||
/// ## Returning Directly
|
||||
///
|
||||
/// When returning an `SRArray` from a Swift function,
|
||||
/// you will need to wrap it in an `NSObject` class since
|
||||
/// Swift doesn't permit returning generic types from `@_cdecl` functions.
|
||||
/// To account for the wrapping `NSObject`, the array must be wrapped
|
||||
/// in `SRObject` on the Rust side.
|
||||
///
|
||||
/// ```rust
|
||||
/// use swift_rs::{swift, SRArray, SRObject, Int};
|
||||
///
|
||||
/// swift!(fn get_int_array() -> SRObject<SRArray<Int>>);
|
||||
///
|
||||
/// let array = unsafe { get_int_array() };
|
||||
///
|
||||
/// assert_eq!(array.as_slice(), &[1, 2, 3])
|
||||
/// ```
|
||||
/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L19)
|
||||
///
|
||||
/// ## Returning in a Struct fIeld
|
||||
///
|
||||
/// When returning an `SRArray` from a custom struct that is itself an `NSObject`,
|
||||
/// the above work is already done for you.
|
||||
/// Assuming your custom struct is already wrapped in `SRObject` in Rust,
|
||||
/// `SRArray` will work normally.
|
||||
///
|
||||
/// ```rust
|
||||
/// use swift_rs::{swift, SRArray, SRObject, Int};
|
||||
///
|
||||
/// #[repr(C)]
|
||||
/// struct ArrayStruct {
|
||||
/// array: SRArray<Int>
|
||||
/// }
|
||||
///
|
||||
/// swift!(fn get_array_struct() -> SRObject<ArrayStruct>);
|
||||
///
|
||||
/// let data = unsafe { get_array_struct() };
|
||||
///
|
||||
/// assert_eq!(data.array.as_slice(), &[4, 5, 6]);
|
||||
/// ```
|
||||
/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L32)
|
||||
#[repr(transparent)]
|
||||
pub struct SRArray<T>(SRObject<SRArrayImpl<T>>);
|
||||
|
||||
impl<T> SRArray<T> {
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
self.0.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SwiftObject for SRArray<T> {
|
||||
type Shape = SRArrayImpl<T>;
|
||||
|
||||
fn get_object(&self) -> &SRObject<Self::Shape> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for SRArray<T> {
|
||||
type Target = [T];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SRArrayImpl<T> {
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
unsafe { std::slice::from_raw_parts(self.data.as_ref(), self.length) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<T> serde::Serialize for SRArray<T>
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
use serde::ser::SerializeSeq;
|
||||
|
||||
let mut seq = serializer.serialize_seq(Some(self.len()))?;
|
||||
for item in self.iter() {
|
||||
seq.serialize_element(item)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
75
swift-rs/src-rs/types/data.rs
Normal file
75
swift-rs/src-rs/types/data.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use crate::{
|
||||
swift::{self, SwiftObject},
|
||||
Int,
|
||||
};
|
||||
|
||||
use super::{array::SRArray, SRObject};
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
type Data = SRArray<u8>;
|
||||
|
||||
/// Convenience type for working with byte buffers,
|
||||
/// analagous to `SRData` in Swift.
|
||||
///
|
||||
/// ```rust
|
||||
/// use swift_rs::{swift, SRData};
|
||||
///
|
||||
/// swift!(fn get_data() -> SRData);
|
||||
///
|
||||
/// let data = unsafe { get_data() };
|
||||
///
|
||||
/// assert_eq!(data.as_ref(), &[1, 2, 3])
|
||||
/// ```
|
||||
/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L68)
|
||||
#[repr(transparent)]
|
||||
pub struct SRData(SRObject<Data>);
|
||||
|
||||
impl SRData {
|
||||
///
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
self.as_slice().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl SwiftObject for SRData {
|
||||
type Shape = Data;
|
||||
|
||||
fn get_object(&self) -> &SRObject<Self::Shape> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SRData {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for SRData {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for SRData {
|
||||
fn from(value: &[u8]) -> Self {
|
||||
unsafe { swift::data_from_bytes(value.as_ptr(), value.len() as Int) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for SRData {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_bytes(self)
|
||||
}
|
||||
}
|
||||
11
swift-rs/src-rs/types/mod.rs
Normal file
11
swift-rs/src-rs/types/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
mod array;
|
||||
mod data;
|
||||
mod object;
|
||||
mod scalars;
|
||||
mod string;
|
||||
|
||||
pub use array::*;
|
||||
pub use data::*;
|
||||
pub use object::*;
|
||||
pub use scalars::*;
|
||||
pub use string::*;
|
||||
75
swift-rs/src-rs/types/object.rs
Normal file
75
swift-rs/src-rs/types/object.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use crate::swift::{self, SwiftObject};
|
||||
use std::{ffi::c_void, ops::Deref, ptr::NonNull};
|
||||
|
||||
#[doc(hidden)]
|
||||
#[repr(C)]
|
||||
pub struct SRObjectImpl<T> {
|
||||
_nsobject_offset: u8,
|
||||
data: T,
|
||||
}
|
||||
|
||||
/// Wrapper for arbitrary `NSObject` types.
|
||||
///
|
||||
/// When returning an `NSObject`, its Rust type must be wrapped in `SRObject`.
|
||||
/// The type must also be annotated with `#[repr(C)]` to ensure its memory layout
|
||||
/// is identical to its Swift counterpart's.
|
||||
///
|
||||
/// ```rust
|
||||
/// use swift_rs::{swift, SRObject, Int, Bool};
|
||||
///
|
||||
/// #[repr(C)]
|
||||
/// struct CustomObject {
|
||||
/// a: Int,
|
||||
/// b: Bool
|
||||
/// }
|
||||
///
|
||||
/// swift!(fn get_custom_object() -> SRObject<CustomObject>);
|
||||
///
|
||||
/// let value = unsafe { get_custom_object() };
|
||||
///
|
||||
/// let reference: &CustomObject = value.as_ref();
|
||||
/// ```
|
||||
/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L49)
|
||||
#[repr(transparent)]
|
||||
pub struct SRObject<T>(pub(crate) NonNull<SRObjectImpl<T>>);
|
||||
|
||||
impl<T> SwiftObject for SRObject<T> {
|
||||
type Shape = T;
|
||||
|
||||
fn get_object(&self) -> &SRObject<Self::Shape> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for SRObject<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &self.0.as_ref().data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for SRObject<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for SRObject<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe { swift::release_object(self.0.as_ref() as *const _ as *const c_void) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<T> serde::Serialize for SRObject<T>
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.deref().serialize(serializer)
|
||||
}
|
||||
}
|
||||
34
swift-rs/src-rs/types/scalars.rs
Normal file
34
swift-rs/src-rs/types/scalars.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
/// Swift's [`Bool`](https://developer.apple.com/documentation/swift/bool) type
|
||||
pub type Bool = bool;
|
||||
|
||||
/// Swift's [`Int`](https://developer.apple.com/documentation/swift/int) type
|
||||
pub type Int = isize;
|
||||
/// Swift's [`Int8`](https://developer.apple.com/documentation/swift/int8) type
|
||||
pub type Int8 = i8;
|
||||
/// Swift's [`Int16`](https://developer.apple.com/documentation/swift/int16) type
|
||||
pub type Int16 = i16;
|
||||
/// Swift's [`Int32`](https://developer.apple.com/documentation/swift/int32) type
|
||||
pub type Int32 = i32;
|
||||
/// Swift's [`Int64`](https://developer.apple.com/documentation/swift/int64) type
|
||||
pub type Int64 = i64;
|
||||
|
||||
/// Swift's [`UInt`](https://developer.apple.com/documentation/swift/uint) type
|
||||
pub type UInt = usize;
|
||||
/// Swift's [`UInt8`](https://developer.apple.com/documentation/swift/uint8) type
|
||||
pub type UInt8 = u8;
|
||||
/// Swift's [`UInt16`](https://developer.apple.com/documentation/swift/uint16) type
|
||||
pub type UInt16 = u16;
|
||||
/// Swift's [`UInt32`](https://developer.apple.com/documentation/swift/uint32) type
|
||||
pub type UInt32 = u32;
|
||||
/// Swift's [`UInt64`](https://developer.apple.com/documentation/swift/uint64) type
|
||||
pub type UInt64 = u64;
|
||||
|
||||
/// Swift's [`Float`](https://developer.apple.com/documentation/swift/float) type
|
||||
pub type Float = f32;
|
||||
/// Swift's [`Double`](https://developer.apple.com/documentation/swift/double) type
|
||||
pub type Double = f64;
|
||||
|
||||
/// Swift's [`Float32`](https://developer.apple.com/documentation/swift/float32) type
|
||||
pub type Float32 = f32;
|
||||
/// Swift's [`Float64`](https://developer.apple.com/documentation/swift/float64) type
|
||||
pub type Float64 = f64;
|
||||
84
swift-rs/src-rs/types/string.rs
Normal file
84
swift-rs/src-rs/types/string.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use std::{
|
||||
fmt::{Display, Error, Formatter},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
swift::{self, SwiftObject},
|
||||
Int, SRData, SRObject,
|
||||
};
|
||||
|
||||
/// String type that can be shared between Swift and Rust.
|
||||
///
|
||||
/// ```rust
|
||||
/// use swift_rs::{swift, SRString};
|
||||
///
|
||||
/// swift!(fn get_greeting(name: &SRString) -> SRString);
|
||||
///
|
||||
/// let greeting = unsafe { get_greeting(&"Brendan".into()) };
|
||||
///
|
||||
/// assert_eq!(greeting.as_str(), "Hello Brendan!");
|
||||
/// ```
|
||||
/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L56)
|
||||
#[repr(transparent)]
|
||||
pub struct SRString(SRData);
|
||||
|
||||
impl SRString {
|
||||
pub fn as_str(&self) -> &str {
|
||||
unsafe { std::str::from_utf8_unchecked(&self.0) }
|
||||
}
|
||||
}
|
||||
|
||||
impl SwiftObject for SRString {
|
||||
type Shape = <SRData as SwiftObject>::Shape;
|
||||
|
||||
fn get_object(&self) -> &SRObject<Self::Shape> {
|
||||
self.0.get_object()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SRString {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for SRString {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for SRString {
|
||||
fn from(string: &str) -> Self {
|
||||
unsafe { swift::string_from_bytes(string.as_ptr(), string.len() as Int) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SRString {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
self.as_str().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for SRString {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'a> serde::Deserialize<'a> for SRString {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'a>,
|
||||
{
|
||||
let string = String::deserialize(deserializer)?;
|
||||
Ok(SRString::from(string.as_str()))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user