diff options
Diffstat (limited to 'testing/mozbase/rust/mozrunner')
-rw-r--r-- | testing/mozbase/rust/mozrunner/Cargo.toml | 22 | ||||
-rw-r--r-- | testing/mozbase/rust/mozrunner/src/bin/firefox-default-path.rs | 21 | ||||
-rw-r--r-- | testing/mozbase/rust/mozrunner/src/firefox_args.rs | 191 | ||||
-rw-r--r-- | testing/mozbase/rust/mozrunner/src/lib.rs | 20 | ||||
-rw-r--r-- | testing/mozbase/rust/mozrunner/src/path.rs | 48 | ||||
-rw-r--r-- | testing/mozbase/rust/mozrunner/src/runner.rs | 486 |
6 files changed, 788 insertions, 0 deletions
diff --git a/testing/mozbase/rust/mozrunner/Cargo.toml b/testing/mozbase/rust/mozrunner/Cargo.toml new file mode 100644 index 0000000000..00beaee040 --- /dev/null +++ b/testing/mozbase/rust/mozrunner/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "mozrunner" +version = "0.12.1" +authors = ["Mozilla"] +description = "Reliable Firefox process management." +repository = "https://hg.mozilla.org/mozilla-central/file/tip/testing/mozbase/rust/mozrunner" +license = "MPL-2.0" +edition = "2018" + +[dependencies] +log = "0.4" +mozprofile = { path = "../mozprofile", version = "0.7" } +plist = "0.5" + +[target.'cfg(target_os = "windows")'.dependencies] +winreg = "0.5" + +[target.'cfg(target_os = "macos")'.dependencies] +dirs = "2" + +[[bin]] +name = "firefox-default-path" diff --git a/testing/mozbase/rust/mozrunner/src/bin/firefox-default-path.rs b/testing/mozbase/rust/mozrunner/src/bin/firefox-default-path.rs new file mode 100644 index 0000000000..94958aac90 --- /dev/null +++ b/testing/mozbase/rust/mozrunner/src/bin/firefox-default-path.rs @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate mozrunner; + +use mozrunner::runner::platform; +use std::io::Write; + +fn main() { + let (path, code) = platform::firefox_default_path() + .map(|x| (x.to_string_lossy().into_owned(), 0)) + .unwrap_or(("Firefox binary not found".to_owned(), 1)); + + let mut writer: Box<dyn Write> = match code { + 0 => Box::new(std::io::stdout()), + _ => Box::new(std::io::stderr()), + }; + writeln!(&mut writer, "{}", &*path).unwrap(); + std::process::exit(code); +} diff --git a/testing/mozbase/rust/mozrunner/src/firefox_args.rs b/testing/mozbase/rust/mozrunner/src/firefox_args.rs new file mode 100644 index 0000000000..610af2c044 --- /dev/null +++ b/testing/mozbase/rust/mozrunner/src/firefox_args.rs @@ -0,0 +1,191 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Argument string parsing and matching functions for Firefox. +//! +//! Which arguments Firefox accepts and in what style depends on the platform. +//! On Windows only, arguments can be prefixed with `/` (slash), such as +//! `/foreground`. Elsewhere, including Windows, arguments may be prefixed +//! with both single (`-foreground`) and double (`--foreground`) dashes. +//! +//! An argument's name is determined by a space or an assignment operator (`=`) +//! so that for the string `-foo=bar`, `foo` is considered the argument's +//! basename. + +use std::ffi::{OsStr, OsString}; + +use crate::runner::platform; + +fn parse_arg_name<T>(arg: T) -> Option<String> +where + T: AsRef<OsStr>, +{ + let arg_os_str: &OsStr = arg.as_ref(); + let arg_str = arg_os_str.to_string_lossy(); + + let mut start = 0; + let mut end = 0; + + for (i, c) in arg_str.chars().enumerate() { + if i == 0 { + if !platform::arg_prefix_char(c) { + break; + } + } else if i == 1 { + if name_end_char(c) { + break; + } else if c != '-' { + start = i; + end = start + 1; + } else { + start = i + 1; + end = start; + } + } else { + end += 1; + if name_end_char(c) { + end -= 1; + break; + } + } + } + + if start > 0 && end > start { + Some(arg_str[start..end].into()) + } else { + None + } +} + +fn name_end_char(c: char) -> bool { + c == ' ' || c == '=' +} + +/// Represents a Firefox command-line argument. +#[derive(Debug, PartialEq)] +pub enum Arg { + /// `-foreground` ensures application window gets focus, which is not the + /// default on macOS. + Foreground, + + /// `-no-remote` prevents remote commands to this instance of Firefox, and + /// ensure we always start a new instance. + NoRemote, + + /// `-P NAME` starts Firefox with a profile with a given name. + NamedProfile, + + /// `-profile PATH` starts Firefox with the profile at the specified path. + Profile, + + /// `-ProfileManager` starts Firefox with the profile chooser dialogue. + ProfileManager, + + /// All other arguments. + Other(String), + + /// Not an argument. + None, +} + +impl<'a> From<&'a OsString> for Arg { + fn from(arg_str: &OsString) -> Arg { + if let Some(basename) = parse_arg_name(arg_str) { + match &*basename { + "profile" => Arg::Profile, + "P" => Arg::NamedProfile, + "ProfileManager" => Arg::ProfileManager, + "foreground" => Arg::Foreground, + "no-remote" => Arg::NoRemote, + _ => Arg::Other(basename), + } + } else { + Arg::None + } + } +} + +#[cfg(test)] +mod tests { + use super::{parse_arg_name, Arg}; + use std::ffi::OsString; + + fn parse(arg: &str, name: Option<&str>) { + let result = parse_arg_name(arg); + assert_eq!(result, name.map(|x| x.to_string())); + } + + #[test] + fn test_parse_arg_name() { + parse("-p", Some("p")); + parse("--p", Some("p")); + parse("--profile foo", Some("profile")); + parse("--profile", Some("profile")); + parse("--", None); + parse("", None); + parse("-=", None); + parse("--=", None); + parse("-- foo", None); + parse("foo", None); + parse("/ foo", None); + parse("/- foo", None); + parse("/=foo", None); + parse("foo", None); + parse("-profile", Some("profile")); + parse("-profile=foo", Some("profile")); + parse("-profile = foo", Some("profile")); + parse("-profile abc", Some("profile")); + parse("-profile /foo", Some("profile")); + } + + #[cfg(target_os = "windows")] + #[test] + fn test_parse_arg_name_windows() { + parse("/profile", Some("profile")); + } + + #[cfg(not(target_os = "windows"))] + #[test] + fn test_parse_arg_name_non_windows() { + parse("/profile", None); + } + + #[test] + fn test_arg_from_osstring() { + assert_eq!(Arg::from(&OsString::from("-- profile")), Arg::None); + assert_eq!(Arg::from(&OsString::from("profile")), Arg::None); + assert_eq!(Arg::from(&OsString::from("profile -P")), Arg::None); + assert_eq!( + Arg::from(&OsString::from("-profiled")), + Arg::Other("profiled".into()) + ); + assert_eq!( + Arg::from(&OsString::from("-PROFILEMANAGER")), + Arg::Other("PROFILEMANAGER".into()) + ); + + assert_eq!(Arg::from(&OsString::from("--profile")), Arg::Profile); + assert_eq!(Arg::from(&OsString::from("-profile foo")), Arg::Profile); + + assert_eq!( + Arg::from(&OsString::from("--ProfileManager")), + Arg::ProfileManager + ); + assert_eq!( + Arg::from(&OsString::from("-ProfileManager")), + Arg::ProfileManager + ); + + // TODO: -Ptest is valid + //assert_eq!(Arg::from(&OsString::from("-Ptest")), Arg::NamedProfile); + assert_eq!(Arg::from(&OsString::from("-P")), Arg::NamedProfile); + assert_eq!(Arg::from(&OsString::from("-P test")), Arg::NamedProfile); + + assert_eq!(Arg::from(&OsString::from("--foreground")), Arg::Foreground); + assert_eq!(Arg::from(&OsString::from("-foreground")), Arg::Foreground); + + assert_eq!(Arg::from(&OsString::from("--no-remote")), Arg::NoRemote); + assert_eq!(Arg::from(&OsString::from("-no-remote")), Arg::NoRemote); + } +} diff --git a/testing/mozbase/rust/mozrunner/src/lib.rs b/testing/mozbase/rust/mozrunner/src/lib.rs new file mode 100644 index 0000000000..5634de11bf --- /dev/null +++ b/testing/mozbase/rust/mozrunner/src/lib.rs @@ -0,0 +1,20 @@ +#![forbid(unsafe_code)] +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#[macro_use] +extern crate log; +#[cfg(target_os = "macos")] +extern crate dirs; +extern crate mozprofile; +#[cfg(target_os = "macos")] +extern crate plist; +#[cfg(target_os = "windows")] +extern crate winreg; + +pub mod firefox_args; +pub mod path; +pub mod runner; + +pub use crate::runner::platform::firefox_default_path; diff --git a/testing/mozbase/rust/mozrunner/src/path.rs b/testing/mozbase/rust/mozrunner/src/path.rs new file mode 100644 index 0000000000..215e72696f --- /dev/null +++ b/testing/mozbase/rust/mozrunner/src/path.rs @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Provides utilities for searching the system path. + +use std::env; +use std::path::PathBuf; + +#[cfg(unix)] +fn is_executable(path: &PathBuf) -> bool { + use std::fs; + use std::os::unix::fs::PermissionsExt; + + // Permissions are a set of four 4-bit bitflags, represented by a single octal + // digit. The lowest bit of each of the last three values represents the + // executable permission for all, group and user, repsectively. We assume the + // file is executable if any of these are set. + match fs::metadata(path).ok() { + Some(meta) => meta.permissions().mode() & 0o111 != 0, + None => false, + } +} + +#[cfg(not(unix))] +fn is_executable(_: &PathBuf) -> bool { + true +} + +/// Determines if the path is an executable binary. That is, if it exists, is +/// a file, and is executable where applicable. +pub fn is_binary(path: &PathBuf) -> bool { + path.exists() && path.is_file() && is_executable(&path) +} + +/// Searches the system path (`PATH`) for an executable binary and returns the +/// first match, or `None` if not found. +pub fn find_binary(binary_name: &str) -> Option<PathBuf> { + env::var_os("PATH").and_then(|path_env| { + for mut path in env::split_paths(&path_env) { + path.push(binary_name); + if is_binary(&path) { + return Some(path); + } + } + None + }) +} diff --git a/testing/mozbase/rust/mozrunner/src/runner.rs b/testing/mozbase/rust/mozrunner/src/runner.rs new file mode 100644 index 0000000000..485d94090c --- /dev/null +++ b/testing/mozbase/rust/mozrunner/src/runner.rs @@ -0,0 +1,486 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use mozprofile::prefreader::PrefReaderError; +use mozprofile::profile::Profile; +use std::collections::HashMap; +use std::convert::From; +use std::error::Error; +use std::ffi::{OsStr, OsString}; +use std::fmt; +use std::io; +use std::io::ErrorKind; +use std::path::{Path, PathBuf}; +use std::process; +use std::process::{Child, Command, Stdio}; +use std::thread; +use std::time; + +use crate::firefox_args::Arg; + +pub trait Runner { + type Process; + + fn arg<S>(&mut self, arg: S) -> &mut Self + where + S: AsRef<OsStr>; + + fn args<I, S>(&mut self, args: I) -> &mut Self + where + I: IntoIterator<Item = S>, + S: AsRef<OsStr>; + + fn env<K, V>(&mut self, key: K, value: V) -> &mut Self + where + K: AsRef<OsStr>, + V: AsRef<OsStr>; + + fn envs<I, K, V>(&mut self, envs: I) -> &mut Self + where + I: IntoIterator<Item = (K, V)>, + K: AsRef<OsStr>, + V: AsRef<OsStr>; + + fn stdout<T>(&mut self, stdout: T) -> &mut Self + where + T: Into<Stdio>; + + fn stderr<T>(&mut self, stderr: T) -> &mut Self + where + T: Into<Stdio>; + + fn start(self) -> Result<Self::Process, RunnerError>; +} + +pub trait RunnerProcess { + /// Attempts to collect the exit status of the process if it has already exited. + /// + /// This function will not block the calling thread and will only advisorily check to see if + /// the child process has exited or not. If the process has exited then on Unix the process ID + /// is reaped. This function is guaranteed to repeatedly return a successful exit status so + /// long as the child has already exited. + /// + /// If the process has exited, then `Ok(Some(status))` is returned. If the exit status is not + /// available at this time then `Ok(None)` is returned. If an error occurs, then that error is + /// returned. + fn try_wait(&mut self) -> io::Result<Option<process::ExitStatus>>; + + /// Waits for the process to exit completely, killing it if it does not stop within `timeout`, + /// and returns the status that it exited with. + /// + /// Firefox' integrated background monitor observes long running threads during shutdown and + /// kills these after 63 seconds. If the process fails to exit within the duration of + /// `timeout`, it is forcefully killed. + /// + /// This function will continue to have the same return value after it has been called at least + /// once. + fn wait(&mut self, timeout: time::Duration) -> io::Result<process::ExitStatus>; + + /// Determine if the process is still running. + fn running(&mut self) -> bool; + + /// Forces the process to exit and returns the exit status. This is + /// equivalent to sending a SIGKILL on Unix platforms. + fn kill(&mut self) -> io::Result<process::ExitStatus>; +} + +#[derive(Debug)] +pub enum RunnerError { + Io(io::Error), + PrefReader(PrefReaderError), +} + +impl fmt::Display for RunnerError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + RunnerError::Io(ref err) => match err.kind() { + ErrorKind::NotFound => "no such file or directory".fmt(f), + _ => err.fmt(f), + }, + RunnerError::PrefReader(ref err) => err.fmt(f), + } + } +} + +impl Error for RunnerError { + fn cause(&self) -> Option<&dyn Error> { + Some(match *self { + RunnerError::Io(ref err) => err as &dyn Error, + RunnerError::PrefReader(ref err) => err as &dyn Error, + }) + } +} + +impl From<io::Error> for RunnerError { + fn from(value: io::Error) -> RunnerError { + RunnerError::Io(value) + } +} + +impl From<PrefReaderError> for RunnerError { + fn from(value: PrefReaderError) -> RunnerError { + RunnerError::PrefReader(value) + } +} + +#[derive(Debug)] +pub struct FirefoxProcess { + process: Child, + profile: Profile, +} + +impl RunnerProcess for FirefoxProcess { + fn try_wait(&mut self) -> io::Result<Option<process::ExitStatus>> { + self.process.try_wait() + } + + fn wait(&mut self, timeout: time::Duration) -> io::Result<process::ExitStatus> { + let start = time::Instant::now(); + loop { + match self.try_wait() { + // child has already exited, reap its exit code + Ok(Some(status)) => return Ok(status), + + // child still running and timeout elapsed, kill it + Ok(None) if start.elapsed() >= timeout => return self.kill(), + + // child still running, let's give it more time + Ok(None) => thread::sleep(time::Duration::from_millis(100)), + + Err(e) => return Err(e), + } + } + } + + fn running(&mut self) -> bool { + self.try_wait().unwrap().is_none() + } + + fn kill(&mut self) -> io::Result<process::ExitStatus> { + match self.try_wait() { + // child has already exited, reap its exit code + Ok(Some(status)) => Ok(status), + + // child still running, kill it + Ok(None) => { + debug!("Killing process {}", self.process.id()); + self.process.kill()?; + self.process.wait() + } + + Err(e) => Err(e), + } + } +} + +#[derive(Debug)] +pub struct FirefoxRunner { + path: PathBuf, + profile: Profile, + args: Vec<OsString>, + envs: HashMap<OsString, OsString>, + stdout: Option<Stdio>, + stderr: Option<Stdio>, +} + +impl FirefoxRunner { + /// Initialise Firefox process runner. + /// + /// On macOS, `path` can optionally point to an application bundle, + /// i.e. _/Applications/Firefox.app_, as well as to an executable program + /// such as _/Applications/Firefox.app/Content/MacOS/firefox-bin_. + pub fn new(path: &Path, profile: Profile) -> FirefoxRunner { + let mut envs: HashMap<OsString, OsString> = HashMap::new(); + envs.insert("MOZ_NO_REMOTE".into(), "1".into()); + + FirefoxRunner { + path: path.to_path_buf(), + envs, + profile, + args: vec![], + stdout: None, + stderr: None, + } + } +} + +impl Runner for FirefoxRunner { + type Process = FirefoxProcess; + + fn arg<S>(&mut self, arg: S) -> &mut FirefoxRunner + where + S: AsRef<OsStr>, + { + self.args.push((&arg).into()); + self + } + + fn args<I, S>(&mut self, args: I) -> &mut FirefoxRunner + where + I: IntoIterator<Item = S>, + S: AsRef<OsStr>, + { + for arg in args { + self.args.push((&arg).into()); + } + self + } + + fn env<K, V>(&mut self, key: K, value: V) -> &mut FirefoxRunner + where + K: AsRef<OsStr>, + V: AsRef<OsStr>, + { + self.envs.insert((&key).into(), (&value).into()); + self + } + + fn envs<I, K, V>(&mut self, envs: I) -> &mut FirefoxRunner + where + I: IntoIterator<Item = (K, V)>, + K: AsRef<OsStr>, + V: AsRef<OsStr>, + { + for (key, value) in envs { + self.envs.insert((&key).into(), (&value).into()); + } + self + } + + fn stdout<T>(&mut self, stdout: T) -> &mut Self + where + T: Into<Stdio>, + { + self.stdout = Some(stdout.into()); + self + } + + fn stderr<T>(&mut self, stderr: T) -> &mut Self + where + T: Into<Stdio>, + { + self.stderr = Some(stderr.into()); + self + } + + fn start(mut self) -> Result<FirefoxProcess, RunnerError> { + self.profile.user_prefs()?.write()?; + + let stdout = self.stdout.unwrap_or_else(Stdio::inherit); + let stderr = self.stderr.unwrap_or_else(Stdio::inherit); + + let binary_path = platform::resolve_binary_path(&mut self.path); + let mut cmd = Command::new(binary_path); + cmd.args(&self.args[..]) + .envs(&self.envs) + .stdout(stdout) + .stderr(stderr); + + let mut seen_foreground = false; + let mut seen_no_remote = false; + let mut seen_profile = false; + for arg in self.args.iter() { + match arg.into() { + Arg::Foreground => seen_foreground = true, + Arg::NoRemote => seen_no_remote = true, + Arg::Profile | Arg::NamedProfile | Arg::ProfileManager => seen_profile = true, + Arg::Other(_) | Arg::None => {} + } + } + if !seen_foreground { + cmd.arg("-foreground"); + } + if !seen_no_remote { + cmd.arg("-no-remote"); + } + if !seen_profile { + cmd.arg("-profile").arg(&self.profile.path); + } + + info!("Running command: {:?}", cmd); + let process = cmd.spawn()?; + Ok(FirefoxProcess { + process, + profile: self.profile, + }) + } +} + +#[cfg(all(not(target_os = "macos"), unix))] +pub mod platform { + use crate::path::find_binary; + use std::path::PathBuf; + + pub fn resolve_binary_path(path: &mut PathBuf) -> &PathBuf { + path + } + + /// Searches the system path for `firefox`. + pub fn firefox_default_path() -> Option<PathBuf> { + find_binary("firefox") + } + + pub fn arg_prefix_char(c: char) -> bool { + c == '-' + } +} + +#[cfg(target_os = "macos")] +pub mod platform { + use crate::path::{find_binary, is_binary}; + use dirs; + use plist::Value; + use std::path::PathBuf; + + /// Searches for the binary file inside the path passed as parameter. + /// If the binary is not found, the path remains unaltered. + /// Else, it gets updated by the new binary path. + pub fn resolve_binary_path(path: &mut PathBuf) -> &PathBuf { + if path.as_path().is_dir() { + let mut info_plist = path.clone(); + info_plist.push("Contents"); + info_plist.push("Info.plist"); + if let Ok(plist) = Value::from_file(&info_plist) { + if let Some(dict) = plist.as_dictionary() { + if let Some(binary_file) = dict.get("CFBundleExecutable") { + if let Value::String(s) = binary_file { + path.push("Contents"); + path.push("MacOS"); + path.push(s); + } + } + } + } + } + path + } + + /// Searches the system path for `firefox-bin`, then looks for + /// `Applications/Firefox.app/Contents/MacOS/firefox-bin` as well + /// as `Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin` + /// under both `/` (system root) and the user home directory. + pub fn firefox_default_path() -> Option<PathBuf> { + if let Some(path) = find_binary("firefox-bin") { + return Some(path); + } + + let home = dirs::home_dir(); + for &(prefix_home, trial_path) in [ + ( + false, + "/Applications/Firefox.app/Contents/MacOS/firefox-bin", + ), + (true, "Applications/Firefox.app/Contents/MacOS/firefox-bin"), + ( + false, + "/Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin", + ), + ( + true, + "Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin", + ), + ] + .iter() + { + let path = match (home.as_ref(), prefix_home) { + (Some(ref home_dir), true) => home_dir.join(trial_path), + (None, true) => continue, + (_, false) => PathBuf::from(trial_path), + }; + if is_binary(&path) { + return Some(path); + } + } + + None + } + + pub fn arg_prefix_char(c: char) -> bool { + c == '-' + } +} + +#[cfg(target_os = "windows")] +pub mod platform { + use crate::path::{find_binary, is_binary}; + use std::io::Error; + use std::path::PathBuf; + use winreg::enums::*; + use winreg::RegKey; + + pub fn resolve_binary_path(path: &mut PathBuf) -> &PathBuf { + path + } + + /// Searches the Windows registry, then the system path for `firefox.exe`. + /// + /// It _does not_ currently check the `HKEY_CURRENT_USER` tree. + pub fn firefox_default_path() -> Option<PathBuf> { + if let Ok(Some(path)) = firefox_registry_path() { + if is_binary(&path) { + return Some(path); + } + }; + find_binary("firefox.exe") + } + + fn firefox_registry_path() -> Result<Option<PathBuf>, Error> { + let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); + for subtree_key in ["SOFTWARE", "SOFTWARE\\WOW6432Node"].iter() { + let subtree = hklm.open_subkey_with_flags(subtree_key, KEY_READ)?; + let mozilla_org = match subtree.open_subkey_with_flags("mozilla.org\\Mozilla", KEY_READ) + { + Ok(val) => val, + Err(_) => continue, + }; + let current_version: String = mozilla_org.get_value("CurrentVersion")?; + let mozilla = subtree.open_subkey_with_flags("Mozilla", KEY_READ)?; + for key_res in mozilla.enum_keys() { + let key = key_res?; + let section_data = mozilla.open_subkey_with_flags(&key, KEY_READ)?; + let version: Result<String, _> = section_data.get_value("GeckoVer"); + if let Ok(ver) = version { + if ver == current_version { + let mut bin_key = key.to_owned(); + bin_key.push_str("\\bin"); + if let Ok(bin_subtree) = mozilla.open_subkey_with_flags(bin_key, KEY_READ) { + let path_to_exe: Result<String, _> = bin_subtree.get_value("PathToExe"); + if let Ok(path_to_exe) = path_to_exe { + let path = PathBuf::from(path_to_exe); + if is_binary(&path) { + return Ok(Some(path)); + } + } + } + } + } + } + } + Ok(None) + } + + pub fn arg_prefix_char(c: char) -> bool { + c == '/' || c == '-' + } +} + +#[cfg(not(any(unix, target_os = "windows")))] +pub mod platform { + use std::path::PathBuf; + + /// Returns an unaltered path for all operating systems other than macOS. + pub fn resolve_binary_path(path: &mut PathBuf) -> &PathBuf { + path + } + + /// Returns `None` for all other operating systems than Linux, macOS, and + /// Windows. + pub fn firefox_default_path() -> Option<PathBuf> { + None + } + + pub fn arg_prefix_char(c: char) -> bool { + c == '-' + } +} |