Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ plist = "1"
[target."cfg(not(target_os = \"macos\"))".dependencies]
ureq = { version = "3", default-features = false, features = ["gzip"] }

[target."cfg(target_os = \"linux\")".dependencies]
[target."cfg(any(target_os = \"linux\", target_os = \"freebsd\"))".dependencies]
freedesktop_entry_parser = "2.0"

[target."cfg(unix)".dependencies]
Expand Down
6 changes: 6 additions & 0 deletions src/android/ndk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ pub fn host_tag() -> &'static str {
"linux-x86_64"
}

// FreeBSD runs Android NDK tooling via Linux binary compatibility (Linuxulator)
#[cfg(target_os = "freebsd")]
pub fn host_tag() -> &'static str {
"linux-x86_64"
}

#[cfg(all(windows, target_pointer_width = "32"))]
pub fn host_tag() -> &'static str {
"windows"
Expand Down
43 changes: 43 additions & 0 deletions src/os/freebsd/info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use crate::os::Info;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
#[error("Failed to run `uname {flag}`: {source}")]
UnameFailed { flag: &'static str, source: std::io::Error },
#[error("`uname {flag}` returned empty output")]
UnameEmpty { flag: &'static str },
}

pub fn check() -> Result<Info, Error> {
let name = duct::cmd("uname", ["-s"])
.read()
.map_err(|source| Error::UnameFailed { flag: "-s", source })?;
let name = name.trim().to_owned();
if name.is_empty() {
return Err(Error::UnameEmpty { flag: "-s" });
}

let version = duct::cmd("uname", ["-r"])
.read()
.map_err(|source| Error::UnameFailed { flag: "-r", source })?;
let version = version.trim().to_owned();
if version.is_empty() {
return Err(Error::UnameEmpty { flag: "-r" });
}

Ok(Info { name, version })
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
#[cfg(target_os = "freebsd")]
fn check_returns_freebsd_info() {
let info = check().expect("uname should succeed on FreeBSD");
assert_eq!(info.name, "FreeBSD");
assert!(!info.version.is_empty());
}
}
175 changes: 175 additions & 0 deletions src/os/freebsd/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
pub(super) mod info;

#[path = "../linux/xdg.rs"]
mod xdg;

use std::{
ffi::{OsStr, OsString},
io,
path::{Path, PathBuf},
};
use thiserror::Error;

use crate::DuctExpressionExt;
pub use crate::{
env::{Env, ExplicitEnv},
util::ln,
};

#[derive(Debug, Error)]
pub enum DetectEditorError {
#[error("No default editor is set: xdg-mime queries for \"text/rust\" and \"text/plain\" both failed")]
NoDefaultEditorSet,
#[error("Entry Not Found: xdg-mime returned an entry name that could not be found")]
FreeDesktopEntryNotFound,
#[error(
"Entry Parse Error: xdg-mime returned an entry that could not be parsed. Caused by {0}"
)]
FreeDesktopEntryParseError(io::Error),
#[error("Entry Parse Error: file lookup failed. Caused by {0}")]
FreeDesktopEntryLookupFailed(io::Error),
#[error("Exec field on desktop entry was not found")]
ExecFieldMissing,
}

#[derive(Debug, Error)]
pub enum OpenFileError {
#[error("Failed to run {command}: {error}")]
CommandFailed {
command: String,
error: std::io::Error,
},
#[error("Command parsing failed")]
CommandParsingFailed,
}

#[derive(Debug)]
pub struct Application {
exec_command: OsString,
icon: Option<OsString>,
xdg_entry_path: PathBuf,
}

impl Application {
pub fn detect_editor() -> Result<Self, DetectEditorError> {
let entry = xdg::query_mime_entry("text/rust")
.or_else(|| xdg::query_mime_entry("text/plain"))
.ok_or(DetectEditorError::NoDefaultEditorSet)?;

xdg::get_xdg_data_dirs()
.iter()
.find_map(|dir| {
let dir = dir.join("applications");
xdg::find_entry_in_dir(&dir, &entry)
.ok()?
.map(|entry_filepath| {
xdg::parse(&entry_filepath)
.map_err(DetectEditorError::FreeDesktopEntryParseError)
.and_then(|parsed_entry| {
Ok(Self {
exec_command: parsed_entry
.section("Desktop Entry")
.and_then(|s| s.attr("Exec").first())
.ok_or(DetectEditorError::ExecFieldMissing)?
.into(),
icon: parsed_entry
.section("Desktop Entry")
.and_then(|s| s.attr("Icon").first())
.map(Into::into),
xdg_entry_path: entry_filepath,
})
})
})
})
.unwrap_or(Err(DetectEditorError::FreeDesktopEntryNotFound))
}

pub fn open_file(&self, path: impl AsRef<Path>) -> Result<(), OpenFileError> {
let path = path.as_ref();
let maybe_icon = self.icon.as_deref();

let command_parts = xdg::parse_command(
&self.exec_command,
path.as_os_str(),
maybe_icon,
Some(&self.xdg_entry_path),
);

if !command_parts.is_empty() {
let cmd = duct::cmd(&command_parts[0], &command_parts[1..]);
cmd.run_and_detach()
.map_err(|error| OpenFileError::CommandFailed {
command: format!("{cmd:?}"),
error,
})
} else {
Err(OpenFileError::CommandParsingFailed)
}
}
}

pub fn open_file_with(
application: impl AsRef<OsStr>,
path: impl AsRef<OsStr>,
env: &Env,
) -> Result<(), OpenFileError> {
let app_str = application.as_ref();
let path_str = path.as_ref();

let command_parts = xdg::get_xdg_data_dirs()
.iter()
.find_map(|dir| {
let dir = dir.join("applications");
let (entry, entry_path) = xdg::find_entry_by_app_name(&dir, app_str)?;

let command_parts = entry
.section("Desktop Entry")
.and_then(|s| s.attr("Exec").first())
.map(|str_entry| {
xdg::parse_command(
str_entry.as_ref(),
path_str,
entry
.section("Desktop Entry")
.and_then(|s| s.attr("Icon").first())
.map(|s| s.as_ref()),
Some(&entry_path),
)
})?;
if !command_parts.is_empty() {
Some(command_parts)
} else {
None
}
})
.unwrap_or_else(|| vec![app_str.to_os_string()]);

let cmd = duct::cmd(&command_parts[0], &command_parts[1..]).vars(env.explicit_env());
cmd.run_and_detach()
.map_err(|error| OpenFileError::CommandFailed {
command: format!("{cmd:?}"),
error,
})
}

#[cfg(target_os = "freebsd")]
pub fn command_path(name: &str) -> std::io::Result<std::process::Output> {
duct::cmd("sh", ["-c", format!("command -v {name}").as_str()]).run()
}

pub fn code_command() -> duct::Expression {
duct::cmd!("code")
}

pub fn replace_path_separator(path: OsString) -> OsString {
path
}

pub mod consts {
pub const CLANG: &str = "clang";
pub const CLANGXX: &str = "clang++";
pub const AR: &str = "ar";
pub const LD: &str = "ld";
pub const READELF: &str = "readelf";
pub const NDK_STACK: &str = "ndk-stack";
}
8 changes: 7 additions & 1 deletion src/os/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ mod linux;
#[cfg(target_os = "linux")]
pub use self::linux::*;

#[cfg(target_os = "freebsd")]
mod freebsd;

#[cfg(target_os = "freebsd")]
pub use self::freebsd::*;

#[cfg(windows)]
mod windows;

#[cfg(windows)]
pub use self::windows::*;

#[cfg(not(any(target_os = "macos", target_os = "linux", windows)))]
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "freebsd", windows)))]
compile_error!("Host platform not yet supported by cargo-mobile2! We'd love if you made a PR to add support for this platform ❤️");

// TODO: we should probably expose common functionality throughout `os` in a
Expand Down