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/. */
45use crate::error::{FMLError, Result};
6use crate::frontend::AboutBlock;
7use askama::Template;
89use crate::command_line::commands::GenerateStructCmd;
10use crate::intermediate_representation::FeatureManifest;
1112mod gen_structs;
1314impl AboutBlock {
15fn nimbus_object_name_swift(&self) -> String {
16let swift_about = self.swift_about.as_ref().unwrap();
17 swift_about.class.clone()
18 }
1920fn nimbus_module_name(&self) -> String {
21let swift_about = self.swift_about.as_ref().unwrap();
22 swift_about.module.clone()
23 }
24}
2526pub(crate) fn generate_struct(manifest: &FeatureManifest, cmd: &GenerateStructCmd) -> Result<()> {
27if manifest.about.swift_about.is_none() {
28return Err(FMLError::ValidationError(
29"about".to_string(),
30format!(
31"The `about` block is missing a valid `ios` or `swift` entry: {}",
32&cmd.manifest
33 ),
34 ));
35 }
3637let path = &cmd.output;
38let 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 };
4647let fm = gen_structs::FeatureManifestDeclaration::new(manifest);
4849let contents = fm.render()?;
5051 std::fs::write(path, contents)?;
5253Ok(())
54}
5556#[cfg(test)]
57pub mod test {
58use crate::util::{join, pkg_dir, sdk_dir};
59use anyhow::{bail, Context, Result};
60use std::{
61 ffi::OsString,
62 path::{Path, PathBuf},
63 process::Command,
64 };
6566// The root of the Android kotlin package structure
67fn sdk_ios_dir() -> String {
68 join(sdk_dir(), "ios/Nimbus")
69 }
7071fn mock_nimbus_error_swift() -> String {
72 join(pkg_dir(), "fixtures/ios/runtime/NimbusError.swift")
73 }
7475fn mock_uiimage_swift() -> String {
76 join(pkg_dir(), "fixtures/ios/runtime/UIImage.swift")
77 }
7879// The file with the swift implementation of FeatureVariables
80fn variables_swift() -> String {
81 join(sdk_ios_dir(), "FeatureVariables.swift")
82 }
8384// The file with the swift implementation of FeatureVariables
85fn features_swift() -> String {
86 join(sdk_ios_dir(), "FeatureInterface.swift")
87 }
8889// The file with the swift implementation of FeatureVariables
90fn collections_swift() -> String {
91 join(sdk_ios_dir(), "Collections+.swift")
92 }
9394// The file with the swift implementation of FeatureVariables
95fn dictionaries_swift() -> String {
96 join(sdk_ios_dir(), "Dictionary+.swift")
97 }
9899// The file with the swift implementation of Bundle extensions
100fn bundle_swift() -> String {
101 join(sdk_ios_dir(), "Bundle+.swift")
102 }
103104// The file with the swift implementation of FeatureHolder
105fn feature_holder() -> String {
106 join(sdk_ios_dir(), "FeatureHolder.swift")
107 }
108109fn hardcoded_nimbus_features() -> String {
110 join(sdk_ios_dir(), "HardcodedNimbusFeatures.swift")
111 }
112113// The file with the swift implementation of Feature Manifest protocol file
114fn generated_feature_manifest() -> String {
115 join(sdk_ios_dir(), "FeatureManifestInterface.swift")
116 }
117118fn detect_swiftc() -> Result<bool> {
119let output = Command::new("which").arg("swiftc").output()?;
120121Ok(output.status.success())
122 }
123124pub fn compile_manifest_swift(manifest_files: &[String], out_dir: &Path) -> Result<()> {
125let out_path = PathBuf::from(out_dir);
126let manifest_files = manifest_files.iter().map(PathBuf::from);
127let mut dylib_file = out_path.clone();
128 dylib_file.push(format!("lib{}.dylib", "FeatureManifest"));
129130// `-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.
134135let 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")?;
162if !status.success() {
163bail!("running `swiftc` failed")
164 }
165Ok(())
166 }
167168pub fn run_script(out_dir: &Path, script_file: &Path) -> Result<()> {
169let mut cmd = Command::new("swift");
170171// 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.
175176cmd.arg("-I").arg(out_dir).arg("-L").arg(out_dir);
177for entry in PathBuf::from(out_dir)
178 .read_dir()
179 .context("Failed to list target directory when running script")?
180{
181let entry = entry.context("Failed to list target directory when running script")?;
182if let Some(ext) = entry.path().extension() {
183if ext == "dylib" || ext == "so" {
184let mut option = OsString::from("-l");
185 option.push(entry.path());
186 cmd.arg(option);
187 }
188 }
189 }
190 cmd.arg(script_file);
191192let 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")?;
197if !status.success() {
198bail!("running `swift` failed")
199 }
200Ok(())
201 }
202203pub fn run_script_with_generated_code(manifest_files: &[String], script: &Path) -> Result<()> {
204if !detect_swiftc()? {
205eprintln!("SDK-446 Install swift or add it the PATH to run tests");
206return Ok(());
207 }
208let temp = tempfile::tempdir()?;
209let build_dir = temp.path();
210 compile_manifest_swift(manifest_files, build_dir)?;
211 run_script(build_dir, script)
212 }
213}