1use std::path::PathBuf;
6
7use crate::{
8 cli::Cli,
9 version_utils::{is_before, pad_major, pad_major_minor, pad_major_minor_patch},
10 LaunchableApp, NimbusApp,
11};
12use anyhow::{bail, Result};
13
14impl TryFrom<&Cli> for LaunchableApp {
15 type Error = anyhow::Error;
16 fn try_from(value: &Cli) -> Result<Self> {
17 Self::try_from_app_channel_device(
18 value.app.as_deref(),
19 value.channel.as_deref(),
20 value.device_id.as_deref(),
21 )
22 }
23}
24
25impl LaunchableApp {
26 pub(crate) fn try_from_app_channel_device(
27 app: Option<&str>,
28 channel: Option<&str>,
29 device_id: Option<&str>,
30 ) -> Result<Self> {
31 match (&app, &channel) {
32 (None, None) => anyhow::bail!("A value for --app and --channel must be specified. Supported apps are: fenix, focus_android, firefox_ios and focus_ios"),
33 (None, _) => anyhow::bail!("A value for --app must be specified. One of: fenix, focus_android, firefox_ios and focus_ios are currently supported"),
34 (_, None) => anyhow::bail!("A value for --channel must be specified. Supported channels are: developer, nightly, beta and release"),
35 _ => (),
36 }
37
38 let app = app.unwrap();
39 let channel = channel.unwrap();
40
41 let prefix = match app {
42 "fenix" => Some("org.mozilla"),
43 "focus_android" => Some("org.mozilla"),
44 "firefox_ios" => Some("org.mozilla.ios"),
45 "focus_ios" => Some("org.mozilla.ios"),
46 _ => anyhow::bail!("Only --app values of fenix, focus_android, firefox_ios and focus_ios are currently supported"),
47 };
48
49 let suffix = match app {
50 "fenix" => Some(match channel {
51 "developer" => "fenix.debug",
52 "nightly" => "fenix",
53 "beta" => "firefox_beta",
54 "release" => "firefox",
55 _ => bail!(format!("Application {} has no channel '{}'. Try one of developer, nightly, beta or release", app, channel)),
56 }),
57 "focus_android" => Some(match channel {
58 "developer" => "focus.debug",
59 "nightly" => "focus.nightly",
60 "beta" => "focus.beta",
61 "release" => "focus",
62 _ => bail!(format!("Application {} has no channel '{}'. Try one of developer, nightly, beta or release", app, channel)),
63 }),
64 "firefox_ios" => Some(match channel {
65 "developer" => "Fennec",
66 "beta" => "FirefoxBeta",
67 "release" => "Firefox",
68 _ => bail!(format!("Application {} has no channel '{}'. Try one of developer, beta or release", app, channel)),
69 }),
70 "focus_ios" => Some(match channel {
71 "developer" => "Focus",
72 "beta" => "Focus",
73 "release" => "Focus",
74 _ => bail!(format!("Application {} has no channel '{}'. Try one of developer, beta or release", app, channel)),
75 }),
76 _ => None,
77 };
78
79 let scheme = match app {
81 "fenix" => Some(match channel {
82 "developer" => "fenix-dev",
85 "nightly" => "fenix-nightly",
86 "beta" => "fenix-beta",
87 "release" => "fenix",
88 _ => unreachable!(),
89 }),
90 "firefox_ios" => Some(match channel {
91 "developer" => "fennec",
99 "beta" => "firefox-beta",
100 "release" => "firefox-internal",
101 _ => unreachable!(),
102 }),
103 "focus_ios" => Some("firefox-focus"),
106
107 _ => None,
109 }
110 .map(str::to_string);
111
112 Ok(match (app, prefix, suffix) {
113 ("fenix", Some(prefix), Some(suffix)) => Self::Android {
114 package_name: format!("{}.{}", prefix, suffix),
115 activity_name: ".App".to_string(),
116 device_id: device_id.map(str::to_string),
117 scheme,
118 open_deeplink: Some("open".to_string()),
119 },
120 ("focus_android", Some(prefix), Some(suffix)) => Self::Android {
121 package_name: format!("{}.{}", prefix, suffix),
122 activity_name: "org.mozilla.focus.activity.MainActivity".to_string(),
123 device_id: device_id.map(str::to_string),
124 scheme,
125 open_deeplink: None,
126 },
127 ("firefox_ios", Some(prefix), Some(suffix)) => Self::Ios {
128 app_id: format!("{}.{}", prefix, suffix),
129 device_id: device_id.unwrap_or("booted").to_string(),
130 scheme,
131 },
132 ("focus_ios", Some(prefix), Some(suffix)) => Self::Ios {
133 app_id: format!("{}.{}", prefix, suffix),
134 device_id: device_id.unwrap_or("booted").to_string(),
135 scheme,
136 },
137 _ => unreachable!(),
138 })
139 }
140}
141
142impl NimbusApp {
143 pub(crate) fn ref_from_version(
144 &self,
145 version: &Option<String>,
146 ref_: &String,
147 ) -> Result<String> {
148 let app_name = self
149 .app_name()
150 .ok_or_else(|| anyhow::anyhow!("Either an --app or a --manifest must be specified"))?;
151
152 if version.is_none() {
153 if (app_name == "fenix" || app_name == "focus_android") && ref_ == "main" {
155 return Ok("master".into());
156 }
157
158 return Ok(ref_.to_string());
159 }
160
161 let v = version.as_ref().unwrap();
162 let v = match app_name.as_str() {
163 "fenix" => {
164 if is_before(version, 111) {
165 pad_major_minor_patch(v)
166 } else if is_before(version, 126) {
167 pad_major(v)
168 } else {
169 bail!("gecko-dev does not have tagged versions, use --ref instead")
170 }
171 }
172 "focus_android" => {
173 if is_before(version, 110) {
174 pad_major_minor(v)
175 } else if is_before(version, 126) {
176 pad_major(v)
177 } else {
178 bail!("gecko-dev does not have tagged versions, use --ref instead")
179 }
180 }
181 "firefox_ios" => {
182 if is_before(version, 112) {
183 pad_major_minor(v)
184 } else {
185 pad_major(v)
186 }
187 }
188 "focus_ios" => pad_major(v),
189 _ => v.to_string(),
190 };
191
192 Ok(match app_name.as_str() {
193 "fenix" => format!("releases_v{v}"),
194 "focus_android" => format!("releases_v{v}"),
195 "firefox_ios" => {
196 if is_before(version, 106) {
197 format!("v{v}")
198 } else {
199 format!("release/v{v}")
200 }
201 }
202 "focus_ios" => format!("releases_v{v}"),
203
204 _ => anyhow::bail!("{} is not defined", app_name),
205 })
206 }
207
208 pub(crate) fn github_repo<'a>(&self, version: &Option<String>) -> Result<&'a str> {
209 let app_name = self
210 .app_name()
211 .ok_or_else(|| anyhow::anyhow!("Either an --app or a --manifest must be specified"))?;
212 Ok(match app_name.as_str() {
213 "fenix" => {
215 if is_before(version, 111) {
216 "mozilla-mobile/fenix"
217 } else if is_before(version, 126) {
218 "mozilla-mobile/firefox-android"
219 } else {
220 "mozilla/gecko-dev"
221 }
222 }
223 "focus_android" => {
224 if is_before(version, 110) {
225 "mozilla-mobile/focus-android"
226 } else if is_before(version, 126) {
227 "mozilla-mobile/firefox-android"
228 } else {
229 "mozilla/gecko-dev"
230 }
231 }
232 "firefox_ios" => "mozilla-mobile/firefox-ios",
233 "focus_ios" => "mozilla-mobile/focus-ios",
234 _ => unreachable!("{} is not defined", app_name),
235 })
236 }
237
238 pub(crate) fn manifest_location<'a>(&self, version: &Option<String>) -> Result<&'a str> {
239 let app_name = self
240 .app_name()
241 .ok_or_else(|| anyhow::anyhow!("Either an --app or a --manifest must be specified"))?;
242 Ok(match app_name.as_str() {
243 "fenix" => {
244 if is_before(version, 98) {
245 bail!("Fenix wasn't Nimbus enabled before v98")
246 } else if is_before(version, 111) {
247 "nimbus.fml.yaml"
248 } else if is_before(version, 112) {
249 "fenix/nimbus.fml.yaml"
250 } else if is_before(version, 126) {
251 "fenix/app/nimbus.fml.yaml"
252 } else {
253 "mobile/android/fenix/app/nimbus.fml.yaml"
254 }
255 }
256 "focus_android" => {
257 if is_before(version, 102) {
258 bail!("Focus for Android wasn't Nimbus enabled before v102")
259 } else if is_before(version, 110) {
260 "nimbus.fml.yaml"
261 } else if is_before(version, 112) {
262 "focus-android/nimbus.fml.yaml"
263 } else if is_before(version, 126) {
264 "focus-android/app/nimbus.fml.yaml"
265 } else {
266 "mobile/android/focus-android/app/nimbus.fml.yaml"
267 }
268 }
269 "firefox_ios" => {
270 if is_before(version, 98) {
271 bail!("Firefox for iOS wasn't Nimbus enabled before v98")
272 } else if is_before(version, 122) {
273 "nimbus.fml.yaml"
274 } else {
275 "firefox-ios/nimbus.fml.yaml"
276 }
277 }
278 "focus_ios" => {
279 if is_before(version, 108) {
280 bail!("Focus wasn't Nimbus enabled before v108")
281 } else if is_before(version, 122) {
282 "nimbus.fml.yaml"
283 } else {
284 "focus-ios/nimbus.fml.yaml"
285 }
286 }
287 _ => anyhow::bail!("{} is not defined", app_name),
288 })
289 }
290}
291
292pub(crate) fn rs_production_server() -> String {
293 std::env::var("NIMBUS_URL")
294 .unwrap_or_else(|_| "https://firefox.settings.services.mozilla.com".to_string())
295}
296
297pub(crate) fn rs_stage_server() -> String {
298 std::env::var("NIMBUS_URL_STAGE")
299 .unwrap_or_else(|_| "https://firefox.settings.services.allizom.org".to_string())
300}
301
302pub(crate) fn api_v6_production_server() -> String {
303 std::env::var("NIMBUS_API_URL")
304 .unwrap_or_else(|_| "https://experimenter.services.mozilla.com".to_string())
305}
306
307pub(crate) fn api_v6_stage_server() -> String {
308 std::env::var("NIMBUS_API_URL_STAGE").unwrap_or_else(|_| {
309 "https://stage.experimenter.nonprod.webservices.mozgcp.net/api/v6".to_string()
310 })
311}
312
313pub(crate) fn manifest_cache_dir() -> Option<PathBuf> {
314 match std::env::var("NIMBUS_MANIFEST_CACHE") {
315 Ok(s) => {
316 let cwd = std::env::current_dir().expect("Current Working Directory is not set");
317 Some(cwd.join(s))
318 }
319 _ => None,
321 }
322}
323
324#[cfg(feature = "server")]
325pub(crate) fn server_port() -> String {
326 match std::env::var("NIMBUS_CLI_SERVER_PORT") {
327 Ok(s) => s,
328 _ => "8080".to_string(),
329 }
330}
331
332#[cfg(feature = "server")]
333pub(crate) fn server_host() -> String {
334 match std::env::var("NIMBUS_CLI_SERVER_HOST") {
335 Ok(s) => s,
336 _ => {
337 use local_ip_address::local_ip;
338 let ip = local_ip().unwrap();
339 ip.to_string()
340 }
341 }
342}