1use cargo_metadata::Package;
4use crc::{Crc, CRC_64_ECMA_182};
5use std::borrow::Cow;
6use std::io::Write;
7use std::path::{Path, PathBuf};
8use std::{fs, io};
9
10mod plist;
11
12struct XcodeTarget {
13 name_label: String,
14 cargo_file_name: String,
15 xcode_product_name: String,
16 xcode_file_name: String,
17 compiler_flags: String,
18 file_type: &'static str,
19 prod_type: &'static str,
20 supported_platforms: Cow<'static, str>,
21 skip_install: bool,
22}
23
24struct XcodeTargetIds<'a> {
25 target: &'a XcodeTarget,
26 id: String,
27 compile_cargo_id: String,
28 manifest_path_build_object_id: String,
29 prod_conf_debug_id: String,
30 prod_conf_list_id: String,
31 prod_conf_release_id: String,
32 prod_id: String,
33}
34
35pub struct Generator {
36 crc: Crc<u64>,
37 id_base: u64,
38 package: Package,
39 output_dir: PathBuf,
40 custom_project_name: Option<String>,
41 skip_install_everything: bool,
42 use_rustup_nightly: bool,
43 default_features: bool,
44 features: String,
45 allowed_platforms: Option<Vec<String>>,
46 xcconfigs: Vec<String>,
47}
48
49struct XcodeTargetKindProps {
50 name_label: String,
51 cargo_file_name: String,
52 xcode_file_name: String,
53 xcode_product_name: String,
54 file_type: &'static str,
55 prod_type: &'static str,
56 supported_platforms: Cow<'static, str>,
57 skip_install: bool,
58}
59
60const STATIC_LIB_APPLE_PRODUCT_TYPE: &str = "com.apple.product-type.library.static";
61const DY_LIB_APPLE_PRODUCT_TYPE: &str = "com.apple.product-type.library.dynamic";
62const EXECUTABLE_APPLE_PRODUCT_TYPE: &str = "com.apple.product-type.tool";
63
64impl Generator {
65 pub fn new(package: Package, output_dir: Option<&Path>, custom_project_name: Option<String>) -> io::Result<Self> {
66 let crc = Crc::<u64>::new(&CRC_64_ECMA_182);
67 let id_base = crc.checksum(package.name.as_bytes());
68
69 let output_dir = fs::canonicalize(match output_dir {
70 Some(o) => o,
71 None => package.manifest_path.as_std_path().parent().ok_or(io::ErrorKind::InvalidInput)?,
72 })?;
73
74 Ok(Self {
75 crc, id_base, package, output_dir, custom_project_name,
76 skip_install_everything: false,
77 use_rustup_nightly: false,
78 default_features: true,
79 features: String::new(),
80 allowed_platforms: None,
81 xcconfigs: Vec::new(),
82 })
83 }
84
85 pub fn skip_install(&mut self, in_binaries_too: bool) {
86 self.skip_install_everything = in_binaries_too;
87 }
88
89 pub fn nightly(&mut self, use_rustup_nightly: bool) {
90 self.use_rustup_nightly = use_rustup_nightly;
91 }
92
93 pub fn xcconfigs(&mut self, xcconfigs: Vec<String>) {
94 if xcconfigs.len() > 2 {
95 panic!("Specify maximum of 2 xcconfigs");
96 }
97 self.xcconfigs = xcconfigs;
98 }
99
100 pub fn platforms(&mut self, allowed_platforms: Option<Vec<String>>) {
101 self.allowed_platforms = allowed_platforms;
102 }
103
104 pub fn features(&mut self, features: &str, default_features: bool) {
105 self.features = features.into();
106 self.default_features = default_features;
107 }
108
109 fn make_id(&self, sort: u8, kind: &str, name: &str) -> String {
110 let mut crc = self.crc.digest();
111 crc.update(&self.id_base.to_ne_bytes());
112 crc.update(kind.as_bytes());
113 let kind = crc.finalize();
114
115 let name = self.crc.checksum(name.as_bytes());
116 let mut out = format!("CA{:02X}{:08X}{:012X}", sort, kind as u32, name);
117 out.truncate(24);
118 out
119 }
120
121 pub fn write_pbxproj(&self) -> Result<PathBuf, io::Error> {
122 let proj_path = self.prepare_project_path()?;
123 let proj_data = self.pbxproj()?;
124
125 let pbx_path = proj_path.join("project.pbxproj");
126 let mut f = fs::File::create(pbx_path)?;
127 f.write_all(proj_data.as_bytes())?;
128
129 Ok(proj_path)
130 }
131
132 fn project_targets(&self) -> Vec<XcodeTarget> {
133 self.package.targets.iter().flat_map(move |target| {
134 let base_name = self.custom_project_name.as_ref().unwrap_or(&target.name).clone();
135 let required_features = target.required_features.join(",");
136 let dylib_exists = target.kind.iter().any(|k| k == "cdylib");
137 let static_suffix = if dylib_exists { "_static" } else { "" };
138 target.kind.iter().filter_map(move |kind| {
139 let p = match kind.as_str() {
140 "bin" => XcodeTargetKindProps {
141 name_label: format!("{} ({})", target.name, if self.skip_install_everything { "bundled binary" } else { "standalone executable" }),
142 cargo_file_name: target.name.clone(),
143 xcode_file_name: base_name.clone(),
144 xcode_product_name: base_name.clone(),
145 file_type: "compiled.mach-o.executable",
146 prod_type: EXECUTABLE_APPLE_PRODUCT_TYPE,
147 skip_install: self.skip_install_everything,
148 supported_platforms: self.only_allowed_platforms("macosx"),
149 },
150 "cdylib" => XcodeTargetKindProps {
151 name_label: format!("{}.dylib (cdylib)", target.name),
152 cargo_file_name: format!("lib{}.dylib", target.name.replace('-', "_")),
153 xcode_file_name: format!("{base_name}.dylib"),
154 xcode_product_name: base_name.clone(),
155 file_type: "compiled.mach-o.dylib",
156 prod_type: DY_LIB_APPLE_PRODUCT_TYPE,
157 skip_install: self.skip_install_everything,
158 supported_platforms: self.only_allowed_platforms("macosx iphonesimulator iphoneos"),
159 },
160 "staticlib" => XcodeTargetKindProps {
161 name_label: format!("{}.a (static library)", target.name),
162 cargo_file_name: format!("lib{}.a", target.name.replace('-', "_")),
164 xcode_file_name: format!("lib{base_name}{static_suffix}.a"),
165 xcode_product_name: format!("{base_name}{static_suffix}"),
166 file_type: "archive.ar",
167 prod_type: STATIC_LIB_APPLE_PRODUCT_TYPE,
168 skip_install: true,
169 supported_platforms: self.only_allowed_platforms("xrsimulator xros watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos"),
170 },
171 _ => return None,
172 };
173
174 let mut compiler_flags = if p.prod_type == EXECUTABLE_APPLE_PRODUCT_TYPE { format!("--bin '{base_name}'") } else { "--lib".into() };
175 if p.prod_type == EXECUTABLE_APPLE_PRODUCT_TYPE && !required_features.is_empty() {
176 use std::fmt::Write;
177 write!(&mut compiler_flags, " --features '{required_features}'").unwrap(); }
179 if !self.default_features {
180 compiler_flags += " --no-default-features";
181 }
182
183 Some(XcodeTarget {
184 name_label: p.name_label,
185 compiler_flags,
186 supported_platforms: p.supported_platforms,
187 cargo_file_name: p.cargo_file_name,
188 xcode_file_name: p.xcode_file_name,
189 xcode_product_name: p.xcode_product_name,
190 file_type: p.file_type,
191 prod_type: p.prod_type,
192 skip_install: p.skip_install,
193 })
194 })
195 })
196 .collect()
197 }
198
199 fn target_ids<'a>(&self, cargo_targets: &'a [XcodeTarget]) -> Vec<XcodeTargetIds<'a>> {
200 cargo_targets.iter().enumerate().map(|(n, target)| {
201 let sort = n as u8;
202 let prod_id = self.make_id(sort, target.file_type, &target.cargo_file_name);
203 XcodeTargetIds {
204 target,
205 id: self.make_id(sort, target.file_type, &prod_id),
206 compile_cargo_id: self.make_id(sort, "<cargo>", &prod_id),
207 manifest_path_build_object_id: self.make_id(sort, "<cargo-toml>", &prod_id),
208 prod_conf_debug_id: self.make_id(sort, "<config-debug>", &prod_id),
209 prod_conf_list_id: self.make_id(sort, "<config-list>", &prod_id),
210 prod_conf_release_id: self.make_id(sort, "<config-release>", &prod_id),
211 prod_id,
212 }
213 }).collect()
214 }
215
216 pub fn pbxproj(&self) -> Result<String, io::Error> {
217 let main_group_id = self.make_id(0xF0, "", "<root>");
218 let prod_group_id = self.make_id(0xF1, "", "Products");
219 let frameworks_group_id = self.make_id(0xF2, "", "Frameworks"); let project_id = self.make_id(0xF3, "", "<project>");
221 let build_rule_id = self.make_id(0xF4, "", "BuildRule");
222 let conf_list_id = self.make_id(0xF6, "", "<configuration-list>");
223 let conf_release_id = self.make_id(0xF7, "configuration", "Release");
224 let conf_debug_id = self.make_id(0xF8, "configuration", "Debug");
225 let manifest_path_id = self.make_id(0xF9, "", "Cargo.toml");
226
227 let project_name = self.custom_project_name.as_deref().unwrap_or(&self.package.name);
228 let rust_targets = self.project_targets();
229 let targets = self.target_ids(&rust_targets);
230
231 let mut main_folder_refs = Vec::new();
232
233 main_folder_refs.push(format!("{manifest_path_id} /* Cargo.toml */"));
234
235 let cargo_toml_path = self.relative_path(self.package.manifest_path.as_std_path())?;
236
237 let xcconfigs = self.xcconfigs.iter().enumerate().map(|(n, xc)| {
238 let path = self.relative_path(xc.as_ref())?;
239 let id = self.make_id(0xFD + n as u8, "configuration", xc);
240 Ok((path, id))
241 }).collect::<io::Result<Vec<_>>>()?;
242
243 main_folder_refs.push(format!("{prod_group_id} /* Products */"));
244 main_folder_refs.push(format!("{frameworks_group_id} /* Frameworks */"));
245
246 for (path, id) in &xcconfigs {
247 main_folder_refs.push(format!("{id} /* {name} */", name = path.file_name().unwrap().to_str().unwrap()));
248 }
249
250 let main_folder_refs = main_folder_refs.iter().map(|id| format!("\t\t\t\t{id},\n")).collect::<String>();
251
252 let build_script = include_str!("xcodebuild.sh").replace(" ", " ");
253
254 let supported_platforms_prefixes = rust_targets.iter().flat_map(|t| t.supported_platforms.split(' '))
255 .filter_map(|p| p.as_bytes().first())
256 .fold(vec![], |mut v, prefix| { if !v.contains(prefix) { v.push(*prefix); } v });
257
258 let common_build_settings = |o: &mut plist::Object<'_>, mode: &str| {
259 if supported_platforms_prefixes.contains(&b'i') { o.kvq("\"ADDITIONAL_SDKS[sdk=i*]\"", "macosx"); }
261 if supported_platforms_prefixes.contains(&b'w') { o.kvq("\"ADDITIONAL_SDKS[sdk=w*]\"", "macosx"); }
262 if supported_platforms_prefixes.contains(&b'x') { o.kvq("\"ADDITIONAL_SDKS[sdk=x*]\"", "macosx"); }
263 if supported_platforms_prefixes.contains(&b'a') { o.kvq("\"ADDITIONAL_SDKS[sdk=a*]\"", "macosx"); }
264 o.kv("ALWAYS_SEARCH_USER_PATHS", "NO")
265 .kvq("CARGO_TARGET_DIR", "$(PROJECT_TEMP_DIR)/cargo_target")
266 .kv("CARGO_XCODE_BUILD_PROFILE", mode)
267 .kvq("CARGO_XCODE_FEATURES", &self.features)
268 .kvq("CURRENT_PROJECT_VERSION", format_args!("{major}.{minor}", major = self.package.version.major, minor = self.package.version.minor))
269 .kv("ENABLE_USER_SCRIPT_SANDBOXING", "NO") .kvq("MARKETING_VERSION", &self.package.version);
271 if mode == "debug" {
272 o.kv("ONLY_ACTIVE_ARCH", "YES");
273 }
274 o.kvq("PRODUCT_NAME", &self.package.name)
275 .kvq("RUSTUP_TOOLCHAIN", if self.use_rustup_nightly { "nightly" } else { "" })
276 .kv("SDKROOT", "macosx");
277 if self.skip_install_everything {
278 o.kv("SKIP_INSTALL", "YES");
279 }
280 o.kv("SUPPORTS_MACCATALYST", "YES");
281 };
282
283 let crate_version = env!("CARGO_PKG_VERSION");
284
285 let mut p = plist::Plist::new();
286 p.root()
287 .comment(format_args!("generated with cargo-xcode {crate_version}"))
288 .kv("archiveVersion", "1")
289 .obj("classes", None, |_| {})
290 .kv("objectVersion", "53")
291 .obj("objects", None, |o| {
292 o.nl()
293 .section_comment("Begin PBXBuildFile section");
294 for t in &targets {
295 o.kv(
296 format_args!("{} /* Cargo.toml in Sources */", t.manifest_path_build_object_id),
297 format_args!("{{isa = PBXBuildFile; fileRef = {manifest_path_id} /* Cargo.toml */; settings = {{COMPILER_FLAGS = \"{compiler_flags}\"; }}; }}", compiler_flags = t.target.compiler_flags)
298 );
299 }
300 o.section_comment("End PBXBuildFile section")
301 .nl()
302 .section_comment("Begin PBXBuildRule section")
303 .obj(&build_rule_id, Some("PBXBuildRule"), |p| {
304 p.kv("isa", "PBXBuildRule")
305 .kv("compilerSpec", "com.apple.compilers.proxy.script")
306 .kvq("dependencyFile", "$(DERIVED_FILE_DIR)/$(ARCHS)-$(EXECUTABLE_NAME).d")
307 .kvq("filePatterns", "*/Cargo.toml") .kv("fileType", "pattern.proxy")
309 .array("inputFiles", None, |_| {})
310 .kv("isEditable", "0")
311 .kvq("name", "Cargo project build")
312 .array("outputFiles", None, |a| {
313 a.q("$(TARGET_BUILD_DIR)/$(EXECUTABLE_NAME)");
314 })
315 .kv("runOncePerArchitecture", "0")
316 .kvq("script", format_args!("# generated with cargo-xcode {crate_version}\n{build_script}"));
317 })
318 .section_comment("End PBXBuildRule section")
319 .nl()
320 .section_comment("Begin PBXFileReference section");
321 for t in &targets {
322 o.kv(
324 format_args!("{prod_id} /* {xcode_file_name} */", prod_id = t.prod_id, xcode_file_name = t.target.xcode_file_name),
325 format_args!("{{isa = PBXFileReference; explicitFileType = {file_type}; includeInIndex = 0; path = {xcode_file_name_q}; sourceTree = BUILT_PRODUCTS_DIR; }}",
326 xcode_file_name_q = plist::quote(&t.target.xcode_file_name),
327 file_type = plist::quote(t.target.file_type),
328 )
329 );
330 }
331 for (xc_path, xc_id) in &xcconfigs {
332 let xc_filename = xc_path.file_name().unwrap().to_str().unwrap();
333 o.kv(
334 format_args!("{xc_id} /* {xc_filename} */"),
335 format_args!("{{isa = PBXFileReference; lastKnownFileType = text; name = {xc_filename_q}; path = {xc_path_q}; sourceTree = \"<group>\"; }}",
336 xc_filename_q = plist::quote(xc_filename), xc_path_q = plist::quote(xc_path.to_str().unwrap())),
337 );
338 }
339 o.kv(
340 format_args!("{manifest_path_id} /* {cargo_toml_path} */", cargo_toml_path = cargo_toml_path.to_str().unwrap()),
341 format_args!("{{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Cargo.toml; path = {cargo_toml_path_q}; sourceTree = \"<group>\"; }}",
342 cargo_toml_path_q = plist::quote(cargo_toml_path.to_str().unwrap()),
343 )
344 )
345 .section_comment("End PBXFileReference section")
346 .nl()
347 .section_comment("Begin PBXGroup section")
348 .obj(&main_group_id, None, |f| {
349 f.kv("isa", "PBXGroup")
350 .array("children", None, |c| {
351 c.raw(main_folder_refs);
352 })
353 .kvq("sourceTree", "<group>");
354 })
355 .obj(&prod_group_id, Some("Products"), |f| {
356 f.kv("isa", "PBXGroup")
357 .array("children", None, |c| {
358 for t in &targets {
359 c.v(format_args!("{} /* {} */", t.prod_id, t.target.xcode_file_name));
360 }
361 })
362 .kv("name", "Products")
363 .kvq("sourceTree", "<group>");
364 })
365 .obj(&frameworks_group_id, Some("Frameworks"), |f| {
366 f.kv("isa", "PBXGroup")
367 .array("children", None, |_| {})
368 .kv("name", "Frameworks")
369 .kvq("sourceTree", "<group>");
370 })
371 .section_comment("End PBXGroup section")
372 .nl()
373 .section_comment("Begin PBXNativeTarget section");
374 for t in &targets {
375 o.obj(&t.id, Some(&t.target.name_label), |d| {
376 d.kv("isa", "PBXNativeTarget")
377 .kv("buildConfigurationList", format_args!("{conf_list_id} /* Build configuration list for PBXNativeTarget \"{name}\" */",
378 conf_list_id = t.prod_conf_list_id, name = t.target.name_label))
379 .array("buildPhases", None, |a| {
380 a.v(format_args!("{} /* Sources */", t.compile_cargo_id));
381 })
382 .array("buildRules", None, |r| {
383 r.v(format_args!("{build_rule_id} /* PBXBuildRule */"));
384 })
385 .array("dependencies", None, |_| {
386 })
387 .kvq("name", &t.target.name_label)
388 .kvq("productName", &t.target.xcode_file_name)
389 .kv("productReference", format_args!("{} /* {} */", t.prod_id, t.target.xcode_file_name))
390 .kvq("productType", t.target.prod_type)
391 ;
392 });
393 }
394 o.section_comment("End PBXNativeTarget section")
395 .nl()
396 .section_comment("Begin PBXProject section")
397 .obj(&project_id, Some("Project object"), |p| {
398 p.kv("isa", "PBXProject")
399 .obj("attributes", None, |a| {
400 a.kv("BuildIndependentTargetsInParallel", "YES")
401 .kv("LastUpgradeCheck", "1510")
402 .obj("TargetAttributes", None, |t| {
403 for o in &targets {
404 t.obj(&o.id, None, |a| {
405 a.kv("CreatedOnToolsVersion", "9.2")
406 .kv("ProvisioningStyle", "Automatic");
407 });
408 }
409 });
410 })
411 .kv("buildConfigurationList", format_args!("{conf_list_id} /* Build configuration list for PBXProject \"{project_name}\" */"))
412 .kvq("compatibilityVersion", "Xcode 11.4")
413 .kv("developmentRegion", "en")
414 .kv("hasScannedForEncodings", "0")
415 .array("knownRegions", None, |r| {
416 r.v("en").v("Base");
417 })
418 .kv("mainGroup", main_group_id)
419 .kv("productRefGroup", format_args!("{prod_group_id} /* Products */"))
420 .kvq("projectDirPath", "")
421 .kvq("projectRoot", "")
422 .array("targets", None, |a| {
423 for t in &targets {
424 a.v(format_args!("{id} /* {name} */", id = t.id, name = t.target.name_label));
425 }
426 });
427 })
428 .section_comment("End PBXProject section")
429 .nl()
430 .section_comment("Begin PBXSourcesBuildPhase section");
431 for t in &targets {
432 o.obj(&t.compile_cargo_id, Some("Sources"), |b| {
433 b.kv("isa", "PBXSourcesBuildPhase")
434 .kv("buildActionMask", "2147483647")
435 .array("files", None, |f| {
436 f.v(format_args!("{} /* Cargo.toml in Sources */", t.manifest_path_build_object_id));
437 })
438 .kv("runOnlyForDeploymentPostprocessing", "0");
439 });
440 }
441 o.section_comment("End PBXSourcesBuildPhase section")
442 .nl()
443 .section_comment("Begin XCBuildConfiguration section");
444 for t in &targets {
445 let skip_install_config = t.target.skip_install && !self.skip_install_everything;
446 for [conf_name, conf_id] in [["Release", &t.prod_conf_release_id], ["Debug", &t.prod_conf_debug_id]] {
447 o.obj(conf_id, Some(conf_name), |c| {
448 c.kv("isa", "XCBuildConfiguration")
449 .obj("buildSettings", None, |b| {
450 b.kvq("CARGO_XCODE_CARGO_DEP_FILE_NAME", Path::new(&t.target.cargo_file_name).with_extension("d").file_name().unwrap().to_str().unwrap())
451 .kvq("CARGO_XCODE_CARGO_FILE_NAME", &t.target.cargo_file_name);
452 if skip_install_config {
453 b.kvq("INSTALL_GROUP", "")
455 .kvq("INSTALL_MODE_FLAG", "")
456 .kvq("INSTALL_OWNER", "");
457 }
458 if t.target.prod_type == DY_LIB_APPLE_PRODUCT_TYPE && self.package.version.major != 1 {
459 b.kv("DYLIB_COMPATIBILITY_VERSION", self.package.version.major);
460 }
461 b.kvq("PRODUCT_NAME", &t.target.xcode_product_name);
462 if skip_install_config {
463 b.kv("SKIP_INSTALL", "YES");
464 }
465 b.kvq("SUPPORTED_PLATFORMS", &t.target.supported_platforms);
466 })
467 .kv("name", conf_name);
468 });
469 }
470 }
471 o.obj(&conf_release_id, Some("Release"), |c| {
472 c.kv("isa", "XCBuildConfiguration");
473 if let Some((xc_path, xc_id)) = xcconfigs.last() {
474 let xc_filename = xc_path.file_name().unwrap().to_str().unwrap();
475 c.kv("baseConfigurationReference", format_args!("{xc_id} /* {xc_filename} */"));
476 }
477
478 c.obj("buildSettings", None, |s| {
479 common_build_settings(s, "release");
480 })
481 .kv("name", "Release");
482 })
483 .obj(&conf_debug_id, Some("Debug"), |c| {
484 c.kv("isa", "XCBuildConfiguration");
485 if let Some((xc_path, xc_id)) = xcconfigs.first() {
486 let xc_filename = xc_path.file_name().unwrap().to_str().unwrap();
487 c.kv("baseConfigurationReference", format_args!("{xc_id} /* {xc_filename} */"));
488 }
489
490 c.obj("buildSettings", None, |s| {
491 common_build_settings(s, "debug");
492 })
493 .kv("name", "Debug");
494 })
495 .section_comment("End XCBuildConfiguration section")
496 .nl()
497 .section_comment("Begin XCConfigurationList section");
498 for t in &targets {
499 o.obj(&t.prod_conf_list_id, Some(&format!("Build configuration list for PBXNativeTarget \"{}\"", t.target.name_label)), |l| {
500 l.kv("isa", "XCConfigurationList")
501 .array("buildConfigurations", None, |c| {
502 c.v(format_args!("{} /* Release */", t.prod_conf_release_id))
503 .v(format_args!("{} /* Debug */", t.prod_conf_debug_id));
504 })
505 .kv("defaultConfigurationIsVisible", "0")
506 .kv("defaultConfigurationName", "Release");
507 });
508 }
509 o.obj(&conf_list_id, Some(&format!("Build configuration list for PBXProject \"{project_name}\"")), |c| {
510 c.kv("isa", "XCConfigurationList")
511 .array("buildConfigurations", None, |c| {
512 c.v(format_args!("{conf_release_id} /* Release */"))
513 .v(format_args!("{conf_debug_id} /* Debug */"));
514 })
515 .kv("defaultConfigurationIsVisible", "0")
516 .kv("defaultConfigurationName", "Release");
517 })
518 .section_comment("End XCConfigurationList section");
519 })
520 .kv("rootObject", format_args!("{project_id} /* Project object */"));
521 let tpl = p.fin();
522 Ok(tpl)
523 }
524
525 fn relative_path(&self, path: &Path) -> Result<PathBuf, io::Error> {
526 let rel_path = pathdiff::diff_paths(fs::canonicalize(path)?, &self.output_dir)
527 .ok_or_else(|| {
528 io::Error::new(io::ErrorKind::Unsupported, format!("warning: Unable to make relative path from {} to {}", path.display(), self.output_dir.display()))
529 })?;
530
531 if rel_path.is_absolute() {
532 eprintln!("warning: Unable to make relative path from {} to {}", path.display(), self.output_dir.display());
533 }
534 Ok(rel_path)
535 }
536
537 fn prepare_project_path(&self) -> Result<PathBuf, io::Error> {
538 let proj_file_name = format!("{}.xcodeproj", self.custom_project_name.as_ref().unwrap_or(&self.package.name));
539 let proj_path = self.output_dir.join(proj_file_name);
540 fs::create_dir_all(&proj_path)?;
541 Ok(proj_path)
542 }
543
544 fn only_allowed_platforms<'a>(&self, supported_platforms: &'a str) -> Cow<'a, str> {
545 if let Some(a) = &self.allowed_platforms {
546 supported_platforms.split(' ').filter(|&p| a.iter().any(|a| a == p)).collect::<Vec<_>>().join(" ").into()
547 } else {
548 supported_platforms.into()
549 }
550 }
551}