nimbus_fml/backends/kotlin/
mod.rs
1use crate::command_line::commands::GenerateStructCmd;
6use crate::error::{FMLError, Result};
7use crate::frontend::AboutBlock;
8use crate::intermediate_representation::FeatureManifest;
9use askama::Template;
10
11mod gen_structs;
12
13impl AboutBlock {
14 fn nimbus_fully_qualified_name(&self) -> String {
15 let kt_about = self.kotlin_about.as_ref().unwrap();
16
17 let class = &kt_about.class;
18 if class.starts_with('.') {
19 format!("{}{}", kt_about.package, class)
20 } else {
21 class.clone()
22 }
23 }
24
25 fn nimbus_object_name_kt(&self) -> String {
26 let fqe = self.nimbus_fully_qualified_name();
27 let last = fqe.split('.').next_back().unwrap_or(&fqe);
28 last.to_string()
29 }
30
31 fn nimbus_package_name(&self) -> Option<String> {
32 let fqe = self.nimbus_fully_qualified_name();
33 if !fqe.contains('.') {
34 return None;
35 }
36 let mut it = fqe.split('.');
37 it.next_back()?;
38 Some(it.collect::<Vec<&str>>().join("."))
39 }
40
41 fn resource_package_name(&self) -> String {
42 let kt_about = self.kotlin_about.as_ref().unwrap();
43 kt_about.package.clone()
44 }
45}
46
47pub(crate) fn generate_struct(manifest: &FeatureManifest, cmd: &GenerateStructCmd) -> Result<()> {
48 if manifest.about.kotlin_about.is_none() {
49 return Err(FMLError::ValidationError(
50 "about".to_string(),
51 format!(
52 "The `about` block is missing a valid `android` or `kotlin` entry: {}",
53 &cmd.manifest
54 ),
55 ));
56 }
57
58 let path = &cmd.output;
59 let path = if path.is_dir() {
60 path.join(format!("{}.kt", manifest.about.nimbus_object_name_kt()))
61 } else {
62 path.clone()
63 };
64
65 let kt = gen_structs::FeatureManifestDeclaration::new(manifest);
66
67 let contents = kt.render()?;
68
69 std::fs::write(path, contents)?;
70
71 Ok(())
72}
73
74#[cfg(test)]
75pub mod test {
76 use crate::util::{join, pkg_dir, sdk_dir};
77 use anyhow::{bail, Result};
78 use std::path::Path;
79 use std::process::Command;
80
81 fn sdk_android_dir() -> String {
83 join(sdk_dir(), "android/src/main/java")
84 }
85
86 fn runtime_dir() -> String {
89 join(pkg_dir(), "fixtures/android/runtime")
90 }
91
92 fn tests_dir() -> String {
94 join(pkg_dir(), "fixtures/android/tests")
95 }
96
97 fn json_jar() -> String {
100 join(runtime_dir(), "json.jar")
101 }
102
103 fn variables_kt() -> String {
105 join(
106 sdk_android_dir(),
107 "org/mozilla/experiments/nimbus/FeatureVariables.kt",
108 )
109 }
110
111 fn nimbus_internals_kt() -> String {
112 join(sdk_android_dir(), "org/mozilla/experiments/nimbus/internal")
113 }
114
115 fn features_kt() -> String {
117 join(
118 sdk_android_dir(),
119 "org/mozilla/experiments/nimbus/FeaturesInterface.kt",
120 )
121 }
122
123 fn hardcoded_features_kt() -> String {
124 join(
125 sdk_android_dir(),
126 "org/mozilla/experiments/nimbus/HardcodedNimbusFeatures.kt",
127 )
128 }
129
130 fn classpath(classes: &Path) -> Result<String> {
131 Ok(format!("{}:{}", json_jar(), classes.to_str().unwrap()))
132 }
133
134 fn detect_kotlinc() -> Result<bool> {
135 let output = Command::new("which").arg("kotlinc").output()?;
136
137 Ok(output.status.success())
138 }
139
140 pub fn compile_manifest_kt(manifest_paths: &[String]) -> Result<tempfile::TempDir> {
142 let temp = tempfile::tempdir()?;
143 let build_dir = temp.path();
144
145 let status = Command::new("kotlinc")
146 .arg("-Werror")
148 .arg("-J-ea")
149 .arg("-classpath")
151 .arg(json_jar())
152 .arg("-d")
153 .arg(build_dir)
154 .arg(variables_kt())
155 .arg(features_kt())
156 .arg(hardcoded_features_kt())
157 .arg(runtime_dir())
158 .arg(nimbus_internals_kt())
159 .args(manifest_paths)
160 .spawn()?
161 .wait()?;
162 if status.success() {
163 Ok(temp)
164 } else {
165 bail!("running `kotlinc` failed compiling a generated manifest")
166 }
167 }
168
169 pub fn run_script_with_generated_code(manifests_kt: &[String], script: &str) -> Result<()> {
171 if !detect_kotlinc()? {
172 println!("SDK-446 Install kotlinc or add it the PATH to run tests");
173 return Ok(());
174 }
175 let temp_dir = compile_manifest_kt(manifests_kt)?;
176 let build_dir = temp_dir.path();
177
178 let status = Command::new("kotlinc")
179 .arg("-Werror")
181 .arg("-J-ea")
182 .arg("-classpath")
184 .arg(&classpath(build_dir)?)
185 .arg("-script")
186 .arg(script)
187 .spawn()?
188 .wait()?;
189
190 drop(temp_dir);
191 if status.success() {
192 Ok(())
193 } else {
194 bail!("running `kotlinc` failed running a script")
195 }
196 }
197
198 #[test]
199 fn smoke_test_runtime_dir() -> Result<()> {
200 run_script_with_generated_code(
201 &[join(tests_dir(), "SmokeTestFeature.kt")],
202 "fixtures/android/tests/smoke_test.kts",
203 )?;
204 Ok(())
205 }
206}