nimbus_fml/backends/swift/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2* License, v. 2.0. If a copy of the MPL was not distributed with this
3* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use 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(test)]
57pub mod test {
58    use crate::util::{join, pkg_dir, sdk_dir};
59    use anyhow::{bail, Context, Result};
60    use std::{
61        ffi::OsString,
62        path::{Path, PathBuf},
63        process::Command,
64    };
65
66    // The root of the Android kotlin package structure
67    fn sdk_ios_dir() -> String {
68        join(sdk_dir(), "ios/Nimbus")
69    }
70
71    fn mock_nimbus_error_swift() -> String {
72        join(pkg_dir(), "fixtures/ios/runtime/NimbusError.swift")
73    }
74
75    fn mock_uiimage_swift() -> String {
76        join(pkg_dir(), "fixtures/ios/runtime/UIImage.swift")
77    }
78
79    // The file with the swift implementation of FeatureVariables
80    fn variables_swift() -> String {
81        join(sdk_ios_dir(), "FeatureVariables.swift")
82    }
83
84    // The file with the swift implementation of FeatureVariables
85    fn features_swift() -> String {
86        join(sdk_ios_dir(), "FeatureInterface.swift")
87    }
88
89    // The file with the swift implementation of FeatureVariables
90    fn collections_swift() -> String {
91        join(sdk_ios_dir(), "Collections+.swift")
92    }
93
94    // The file with the swift implementation of FeatureVariables
95    fn dictionaries_swift() -> String {
96        join(sdk_ios_dir(), "Dictionary+.swift")
97    }
98
99    // The file with the swift implementation of Bundle extensions
100    fn bundle_swift() -> String {
101        join(sdk_ios_dir(), "Bundle+.swift")
102    }
103
104    // The file with the swift implementation of FeatureHolder
105    fn feature_holder() -> String {
106        join(sdk_ios_dir(), "FeatureHolder.swift")
107    }
108
109    fn hardcoded_nimbus_features() -> String {
110        join(sdk_ios_dir(), "HardcodedNimbusFeatures.swift")
111    }
112
113    // The file with the swift implementation of Feature Manifest protocol file
114    fn generated_feature_manifest() -> String {
115        join(sdk_ios_dir(), "FeatureManifestInterface.swift")
116    }
117
118    fn detect_swiftc() -> Result<bool> {
119        let output = Command::new("which").arg("swiftc").output()?;
120
121        Ok(output.status.success())
122    }
123
124    pub fn compile_manifest_swift(manifest_files: &[String], out_dir: &Path) -> Result<()> {
125        let out_path = PathBuf::from(out_dir);
126        let manifest_files = manifest_files.iter().map(PathBuf::from);
127        let mut dylib_file = out_path.clone();
128        dylib_file.push(format!("lib{}.dylib", "FeatureManifest"));
129
130        // `-emit-library -o <path>` generates a `.dylib`, so that we can use the
131        // Swift module from the REPL. Otherwise, we'll get "Couldn't lookup
132        // symbols" when we try to import the module.
133        // See https://bugs.swift.org/browse/SR-1191.
134
135        let status = Command::new("swiftc")
136            .arg("-module-name")
137            .arg("FeatureManifest")
138            .arg("-emit-library")
139            .arg("-o")
140            .arg(&dylib_file)
141            .arg("-emit-module")
142            .arg("-emit-module-path")
143            .arg(&out_path)
144            .arg("-parse-as-library")
145            .arg("-L")
146            .arg(&out_path)
147            .arg(collections_swift())
148            .arg(dictionaries_swift())
149            .arg(mock_uiimage_swift())
150            .arg(variables_swift())
151            .arg(features_swift())
152            .arg(feature_holder())
153            .arg(hardcoded_nimbus_features())
154            .arg(bundle_swift())
155            .arg(generated_feature_manifest())
156            .arg(mock_nimbus_error_swift())
157            .args(manifest_files)
158            .spawn()
159            .context("Failed to spawn `swiftc` when compiling bindings")?
160            .wait()
161            .context("Failed to wait for `swiftc` when compiling bindings")?;
162        if !status.success() {
163            bail!("running `swiftc` failed")
164        }
165        Ok(())
166    }
167
168    pub fn run_script(out_dir: &Path, script_file: &Path) -> Result<()> {
169        let mut cmd = Command::new("swift");
170
171        // Find any module maps and/or dylibs in the target directory, and tell swift to use them.
172        // Listing the directory like this is a little bit hacky - it would be nicer if we could tell
173        // Swift to load only the module(s) for the component under test, but the way we're calling
174        // this test function doesn't allow us to pass that name in to the call.
175
176        cmd.arg("-I").arg(out_dir).arg("-L").arg(out_dir);
177        for entry in PathBuf::from(out_dir)
178            .read_dir()
179            .context("Failed to list target directory when running script")?
180        {
181            let entry = entry.context("Failed to list target directory when running script")?;
182            if let Some(ext) = entry.path().extension() {
183                if ext == "dylib" || ext == "so" {
184                    let mut option = OsString::from("-l");
185                    option.push(entry.path());
186                    cmd.arg(option);
187                }
188            }
189        }
190        cmd.arg(script_file);
191
192        let status = cmd
193            .spawn()
194            .context("Failed to spawn `swift` when running script")?
195            .wait()
196            .context("Failed to wait for `swift` when running script")?;
197        if !status.success() {
198            bail!("running `swift` failed")
199        }
200        Ok(())
201    }
202
203    pub fn run_script_with_generated_code(manifest_files: &[String], script: &Path) -> Result<()> {
204        if !detect_swiftc()? {
205            eprintln!("SDK-446 Install swift or add it the PATH to run tests");
206            return Ok(());
207        }
208        let temp = tempfile::tempdir()?;
209        let build_dir = temp.path();
210        compile_manifest_swift(manifest_files, build_dir)?;
211        run_script(build_dir, script)
212    }
213}