swift_bridge_build/
package.rs1use std::collections::HashMap;
4use std::fs;
5use std::fs::OpenOptions;
6use std::io::Write;
7use std::path::{Path, PathBuf};
8use std::process::{Command, Stdio};
9use tempfile::tempdir;
10
11pub struct CreatePackageConfig {
13 pub bridge_dir: PathBuf,
15 pub paths: HashMap<ApplePlatform, PathBuf>,
17 pub out_dir: PathBuf,
19 pub package_name: String,
21}
22
23impl CreatePackageConfig {
24 pub fn new(
26 bridge_dir: PathBuf,
27 paths: HashMap<ApplePlatform, PathBuf>,
28 out_dir: PathBuf,
29 package_name: String,
30 ) -> Self {
31 Self {
32 bridge_dir,
33 paths,
34 out_dir,
35 package_name,
36 }
37 }
38}
39
40#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
41pub enum ApplePlatform {
43 IOS,
45 Simulator,
50 MacOS,
52 MacCatalyst,
54 TvOS,
57 WatchOS,
59 WatchOSSimulator,
61 CarPlayOS,
63 CarPlayOSSimulator,
65}
66
67impl ApplePlatform {
68 pub fn dir_name(&self) -> &str {
70 match self {
71 ApplePlatform::IOS => "ios",
72 ApplePlatform::Simulator => "simulator",
73 ApplePlatform::MacOS => "macos",
74 ApplePlatform::MacCatalyst => "mac-catalyst",
75 ApplePlatform::TvOS => "tvos",
76 ApplePlatform::WatchOS => "watchos",
77 ApplePlatform::WatchOSSimulator => "watchos-simulator",
78 ApplePlatform::CarPlayOS => "carplay",
79 ApplePlatform::CarPlayOSSimulator => "carplay-simulator",
80 }
81 }
82
83 pub const ALL: &'static [Self] = &[
85 ApplePlatform::IOS,
86 ApplePlatform::Simulator,
87 ApplePlatform::MacOS,
88 ApplePlatform::MacCatalyst,
89 ApplePlatform::TvOS,
90 ApplePlatform::WatchOS,
91 ApplePlatform::WatchOSSimulator,
92 ApplePlatform::CarPlayOS,
93 ApplePlatform::CarPlayOSSimulator,
94 ];
95}
96
97pub fn create_package(config: CreatePackageConfig) {
101 let output_dir: &Path = config.out_dir.as_ref();
103 if !&output_dir.exists() {
104 fs::create_dir_all(&output_dir).expect("Couldn't create output directory");
105 }
106
107 gen_xcframework(&output_dir, &config);
109
110 gen_package(&output_dir, &config);
112}
113
114fn gen_xcframework(output_dir: &Path, config: &CreatePackageConfig) {
116 let temp_dir = tempdir().expect("Couldn't create temporary directory");
118 let tmp_framework_path = &temp_dir.path().join("swiftbridge._tmp_framework");
119 fs::create_dir(&tmp_framework_path).expect("Couldn't create framework directory");
120
121 let include_dir = tmp_framework_path.join("include");
122 if !include_dir.exists() {
123 fs::create_dir(&include_dir).expect("Couldn't create inlcude directory for xcframework");
124 }
125
126 let modulemap_path = include_dir.join("module.modulemap");
128 fs::write(
129 &modulemap_path,
130 "module RustXcframework {\n header \"SwiftBridgeCore.h\"\n",
131 )
132 .expect("Couldn't write modulemap file");
133 let mut modulemap_file = OpenOptions::new()
134 .write(true)
135 .append(true)
136 .open(&modulemap_path)
137 .expect("Couldn't open modulemap file for writing");
138
139 let bridge_dir: &Path = config.bridge_dir.as_ref();
141 fs::copy(
142 bridge_dir.join("SwiftBridgeCore.h"),
143 &include_dir.join("SwiftBridgeCore.h"),
144 )
145 .expect("Couldn't copy SwiftBirdgeCore header file");
146 let bridge_project_dir = fs::read_dir(&bridge_dir)
147 .expect("Couldn't read generated directory")
148 .find_map(|file| {
149 let file = file.unwrap().path();
150 if file.is_dir() {
151 Some(file)
152 } else {
153 None
154 }
155 })
156 .expect("Couldn't find project directory inside of generated directory");
157 let bridge_project_header_dir = fs::read_dir(&bridge_project_dir)
158 .expect("Couldn't read generated directory")
159 .find_map(|file| {
160 let file = file.unwrap().path();
161 if file.extension().unwrap() == "h" {
162 Some(file)
163 } else {
164 None
165 }
166 })
167 .expect("Couldn't find project's header file");
168 fs::copy(
169 &bridge_project_header_dir,
170 &include_dir.join(&bridge_project_header_dir.file_name().unwrap()),
171 )
172 .expect("Couldn't copy project's header file");
173 writeln!(
174 modulemap_file,
175 " header \"{}\"",
176 bridge_project_header_dir
177 .file_name()
178 .unwrap()
179 .to_str()
180 .unwrap()
181 )
182 .expect("Couldn't write to modulemap");
183 writeln!(modulemap_file, " export *\n}}").expect("Couldn't write to modulemap");
184
185 for platform in &config.paths {
187 let platform_path = &tmp_framework_path.join(platform.0.dir_name());
188 if !platform_path.exists() {
189 fs::create_dir(&platform_path).expect(&format!(
190 "Couldn't create directory for target {:?}",
191 platform.0
192 ));
193 }
194
195 let lib_path: &Path = platform.1.as_ref();
196 fs::copy(lib_path, platform_path.join(lib_path.file_name().unwrap())).expect(&format!(
197 "Couldn't copy library for platform {:?}",
198 platform.0
199 ));
200 }
201
202 let xcframework_dir = output_dir.join("RustXcframework.xcframework");
204 if xcframework_dir.exists() {
205 fs::remove_dir_all(&xcframework_dir).expect("Couldn't delete previous xcframework file");
206 }
207 fs::create_dir(&xcframework_dir).expect("Couldn't create directory for xcframework");
208
209 let mut args: Vec<String> = Vec::new();
210 args.push("-create-xcframework".to_string());
211 for platform in &config.paths {
212 let file_path = Path::new(platform.0.dir_name())
213 .join((platform.1.as_ref() as &Path).file_name().unwrap());
214
215 args.push("-library".to_string());
216 args.push(file_path.to_str().unwrap().trim().to_string());
217 args.push("-headers".to_string());
218 args.push("include".to_string());
219 }
220 args.push("-output".to_string());
221 args.push(
222 fs::canonicalize(xcframework_dir)
223 .expect("Couldn't convert output directory to absolute path")
224 .as_path()
225 .to_str()
226 .unwrap()
227 .to_string(),
228 );
229
230 let output = Command::new("xcodebuild")
231 .current_dir(&tmp_framework_path)
232 .args(args)
233 .stdout(Stdio::piped())
234 .spawn()
235 .expect("Failed to spawn xcodebuild")
236 .wait_with_output()
237 .expect("Failed to execute xcodebuild");
238 if !output.status.success() {
239 let stderr = std::str::from_utf8(&output.stderr).unwrap();
240 panic!("{}", stderr);
241 }
242
243 let temp_dir_string = temp_dir.path().to_str().unwrap().to_string();
245 if let Err(err) = temp_dir.close() {
246 eprintln!(
247 "Couldn't close temporary directory {} - {}",
248 temp_dir_string, err
249 );
250 }
251}
252
253fn gen_package(output_dir: &Path, config: &CreatePackageConfig) {
263 let sources_dir = output_dir.join("Sources").join(&config.package_name);
264 if !sources_dir.exists() {
265 fs::create_dir_all(&sources_dir).expect("Couldn't create directory for source files");
266 }
267
268 let bridge_dir: &Path = config.bridge_dir.as_ref();
270 fs::write(
271 sources_dir.join("SwiftBridgeCore.swift"),
272 format!(
273 "import RustXcframework\n{}",
274 fs::read_to_string(&bridge_dir.join("SwiftBridgeCore.swift"))
275 .expect("Couldn't read core bridging swift file")
276 ),
277 )
278 .expect("Couldn't write core bridging swift file");
279
280 let bridge_project_dir = fs::read_dir(&bridge_dir)
281 .expect("Couldn't read generated directory")
282 .find_map(|file| {
283 let file = file.unwrap().path();
284 if file.is_dir() {
285 Some(file)
286 } else {
287 None
288 }
289 })
290 .expect("Couldn't find project directory inside of generated directory");
291 let bridge_project_swift_dir = fs::read_dir(&bridge_project_dir)
292 .expect("Couldn't read generated directory")
293 .find_map(|file| {
294 let file = file.unwrap().path();
295 if file.extension().unwrap() == "swift" {
296 Some(file)
297 } else {
298 None
299 }
300 })
301 .expect("Couldn't find project's bridging swift file");
302 fs::write(
303 sources_dir.join(&bridge_project_swift_dir.file_name().unwrap()),
304 format!(
305 "import RustXcframework\n{}",
306 fs::read_to_string(&bridge_project_swift_dir)
307 .expect("Couldn't read project's bridging swift file")
308 ),
309 )
310 .expect("Couldn't copy project's bridging swift file to the package");
311
312 let package_name = &config.package_name;
314 let package_swift = format!(
315 r#"// swift-tools-version:5.5.0
316import PackageDescription
317let package = Package(
318 name: "{package_name}",
319 products: [
320 .library(
321 name: "{package_name}",
322 targets: ["{package_name}"]),
323 ],
324 dependencies: [],
325 targets: [
326 .binaryTarget(
327 name: "RustXcframework",
328 path: "RustXcframework.xcframework"
329 ),
330 .target(
331 name: "{package_name}",
332 dependencies: ["RustXcframework"])
333 ]
334)
335 "#
336 );
337
338 fs::write(output_dir.join("Package.swift"), package_swift)
339 .expect("Couldn't write Package.swift file");
340}