nimbus_fml/backends/swift/
mod.rs1use crate::error::{FMLError, Result};
6use crate::frontend::AboutBlock;
7use askama::Template;
8
9use crate::command_line::commands::GenerateStructCmd;
10use crate::intermediate_representation::FeatureManifest;
11
12mod gen_structs;
13
14impl AboutBlock {
15 fn nimbus_object_name_swift(&self) -> String {
16 let swift_about = self.swift_about.as_ref().unwrap();
17 swift_about.class.clone()
18 }
19
20 fn nimbus_module_name(&self) -> String {
21 let swift_about = self.swift_about.as_ref().unwrap();
22 swift_about.module.clone()
23 }
24}
25
26pub(crate) fn generate_struct(manifest: &FeatureManifest, cmd: &GenerateStructCmd) -> Result<()> {
27 if manifest.about.swift_about.is_none() {
28 return Err(FMLError::ValidationError(
29 "about".to_string(),
30 format!(
31 "The `about` block is missing a valid `ios` or `swift` entry: {}",
32 &cmd.manifest
33 ),
34 ));
35 }
36
37 let path = &cmd.output;
38 let path = if path.is_dir() {
39 path.join(format!(
40 "{}.swift",
41 manifest.about.nimbus_object_name_swift()
42 ))
43 } else {
44 path.clone()
45 };
46
47 let fm = gen_structs::FeatureManifestDeclaration::new(manifest);
48
49 let contents = fm.render()?;
50
51 std::fs::write(path, contents)?;
52
53 Ok(())
54}
55
56#[cfg(all(
57 test,
58 feature = "swift-tests",
59 not(feature = "all-features-workaround")
60))]
61pub mod test {
62 use crate::util::{join, pkg_dir, sdk_dir};
63 use anyhow::{bail, Context, Result};
64 use std::{
65 ffi::OsString,
66 path::{Path, PathBuf},
67 process::Command,
68 };
69
70 fn sdk_ios_dir() -> String {
72 join(sdk_dir(), "ios/Nimbus")
73 }
74
75 fn mock_nimbus_error_swift() -> String {
76 join(pkg_dir(), "fixtures/ios/runtime/NimbusError.swift")
77 }
78
79 fn mock_uiimage_swift() -> String {
80 join(pkg_dir(), "fixtures/ios/runtime/UIImage.swift")
81 }
82
83 fn variables_swift() -> String {
85 join(sdk_ios_dir(), "FeatureVariables.swift")
86 }
87
88 fn features_swift() -> String {
90 join(sdk_ios_dir(), "FeatureInterface.swift")
91 }
92
93 fn collections_swift() -> String {
95 join(sdk_ios_dir(), "Collections+.swift")
96 }
97
98 fn dictionaries_swift() -> String {
100 join(sdk_ios_dir(), "Dictionary+.swift")
101 }
102
103 fn bundle_swift() -> String {
105 join(sdk_ios_dir(), "Bundle+.swift")
106 }
107
108 fn feature_holder() -> String {
110 join(sdk_ios_dir(), "FeatureHolder.swift")
111 }
112
113 fn hardcoded_nimbus_features() -> String {
114 join(sdk_ios_dir(), "HardcodedNimbusFeatures.swift")
115 }
116
117 fn generated_feature_manifest() -> String {
119 join(sdk_ios_dir(), "FeatureManifestInterface.swift")
120 }
121
122 fn detect_swiftc() -> Result<bool> {
123 let output = Command::new("which").arg("swiftc").output()?;
124
125 Ok(output.status.success())
126 }
127
128 pub fn compile_manifest_swift(manifest_files: &[String], out_dir: &Path) -> Result<()> {
129 let out_path = PathBuf::from(out_dir);
130 let manifest_files = manifest_files.iter().map(PathBuf::from);
131 let mut dylib_file = out_path.clone();
132 dylib_file.push(format!("lib{}.dylib", "FeatureManifest"));
133
134 let status = Command::new("swiftc")
140 .arg("-module-name")
141 .arg("FeatureManifest")
142 .arg("-emit-library")
143 .arg("-o")
144 .arg(&dylib_file)
145 .arg("-emit-module")
146 .arg("-emit-module-path")
147 .arg(&out_path)
148 .arg("-parse-as-library")
149 .arg("-L")
150 .arg(&out_path)
151 .arg(collections_swift())
152 .arg(dictionaries_swift())
153 .arg(mock_uiimage_swift())
154 .arg(variables_swift())
155 .arg(features_swift())
156 .arg(feature_holder())
157 .arg(hardcoded_nimbus_features())
158 .arg(bundle_swift())
159 .arg(generated_feature_manifest())
160 .arg(mock_nimbus_error_swift())
161 .args(manifest_files)
162 .spawn()
163 .context("Failed to spawn `swiftc` when compiling bindings")?
164 .wait()
165 .context("Failed to wait for `swiftc` when compiling bindings")?;
166 if !status.success() {
167 bail!("running `swiftc` failed")
168 }
169 Ok(())
170 }
171
172 pub fn run_script(out_dir: &Path, script_file: &Path) -> Result<()> {
173 let mut cmd = Command::new("swift");
174
175 cmd.arg("-I").arg(out_dir).arg("-L").arg(out_dir);
181 for entry in PathBuf::from(out_dir)
182 .read_dir()
183 .context("Failed to list target directory when running script")?
184 {
185 let entry = entry.context("Failed to list target directory when running script")?;
186 if let Some(ext) = entry.path().extension() {
187 if ext == "dylib" || ext == "so" {
188 let mut option = OsString::from("-l");
189 option.push(entry.path());
190 cmd.arg(option);
191 }
192 }
193 }
194 cmd.arg(script_file);
195
196 let status = cmd
197 .spawn()
198 .context("Failed to spawn `swift` when running script")?
199 .wait()
200 .context("Failed to wait for `swift` when running script")?;
201 if !status.success() {
202 bail!("running `swift` failed")
203 }
204 Ok(())
205 }
206
207 pub fn run_script_with_generated_code(manifest_files: &[String], script: &Path) -> Result<()> {
208 if !detect_swiftc()? {
209 eprintln!("SDK-446 Install swift or add it the PATH to run tests");
210 return Ok(());
211 }
212 let temp = tempfile::tempdir()?;
213 let build_dir = temp.path();
214 compile_manifest_swift(manifest_files, build_dir)?;
215 run_script(build_dir, script)
216 }
217}