#![recursion_limit = "128"]
#![allow(clippy::case_sensitive_file_extension_comparisons)]
#![allow(clippy::if_not_else)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::missing_panics_doc)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::redundant_closure_for_method_calls)]
#![allow(clippy::similar_names)]
#[macro_use] extern crate quick_error;
pub mod compress;
pub mod control;
pub mod data;
pub mod listener;
pub mod manifest;
pub use crate::debarchive::DebArchive;
pub use crate::error::*;
pub use crate::manifest::Config;
#[macro_use]
mod util;
mod config;
mod debarchive;
mod dependencies;
mod dh_installsystemd;
mod dh_lib;
mod error;
mod ok_or;
mod pathbytes;
mod tararchive;
mod wordsplit;
use crate::listener::Listener;
use crate::manifest::AssetSource;
use rayon::prelude::*;
use std::env;
use std::fs;
use std::io;
use std::path::Path;
use std::process::{Command, ExitStatus};
const TAR_REJECTS_CUR_DIR: bool = true;
const DEFAULT_TARGET: &str = env!("CARGO_DEB_DEFAULT_TARGET");
pub fn install_deb(path: &Path) -> CDResult<()> {
let status = Command::new("sudo").arg("dpkg").arg("-i").arg(path)
.status()?;
if !status.success() {
return Err(CargoDebError::InstallFailed);
}
Ok(())
}
pub fn reset_deb_temp_directory(options: &Config) -> io::Result<()> {
let deb_dir = options.default_deb_output_dir();
let deb_temp_dir = options.deb_temp_dir();
remove_deb_temp_directory(options);
let g = deb_dir.join(DebArchive::filename_glob(options));
if let Ok(old_files) = glob::glob(g.to_str().expect("utf8 path")) {
for old_file in old_files.flatten() {
let _ = fs::remove_file(old_file);
}
}
fs::create_dir_all(deb_temp_dir)
}
pub fn remove_deb_temp_directory(options: &Config) {
let deb_temp_dir = options.deb_temp_dir();
let _ = fs::remove_dir(deb_temp_dir);
}
pub fn cargo_build(options: &Config, target: Option<&str>, build_command: &str, build_flags: &[String], verbose: bool) -> CDResult<()> {
let mut cmd = Command::new("cargo");
cmd.current_dir(&options.package_manifest_dir);
cmd.arg(build_command);
cmd.args(build_flags);
if verbose {
cmd.arg("--verbose");
}
if let Some(target) = target {
cmd.args(["--target", target]);
if env::var_os("PKG_CONFIG_ALLOW_CROSS").is_none() && env::var_os("PKG_CONFIG_PATH").is_none() {
let pkg_config_path = format!("/usr/lib/{}/pkgconfig", debian_triple_from_rust_triple(target));
if Path::new(&pkg_config_path).exists() {
cmd.env("PKG_CONFIG_ALLOW_CROSS", "1");
cmd.env("PKG_CONFIG_PATH", pkg_config_path);
}
}
}
if !options.default_features {
cmd.arg("--no-default-features");
}
let features = &options.features;
if !features.is_empty() {
cmd.args(["--features", &features.join(",")]);
}
log::debug!("cargo build {:?}", cmd.get_args());
let status = cmd.status()
.map_err(|e| CargoDebError::CommandFailed(e, "cargo"))?;
if !status.success() {
return Err(CargoDebError::BuildFailed);
}
Ok(())
}
fn debian_triple_from_rust_triple(rust_target_triple: &str) -> String {
let mut p = rust_target_triple.split('-');
let arch = p.next().unwrap();
let abi = p.last().unwrap_or("gnu");
let (darch, dabi) = match (arch, abi) {
("i586" | "i686", _) => ("i386", "gnu"),
("x86_64", _) => ("x86_64", "gnu"),
("aarch64", _) => ("aarch64", "gnu"),
(arm, abi) if arm.starts_with("arm") || arm.starts_with("thumb") => {
("arm", if abi.ends_with("hf") {"gnueabihf"} else {"gnueabi"})
},
("mipsel", _) => ("mipsel", "gnu"),
("loongarch64", _) => ("loong64", "gnu"),
(risc, _) if risc.starts_with("riscv64") => ("riscv64", "gnu"),
(arch, abi) => (arch, abi),
};
format!("{darch}-linux-{dabi}")
}
pub(crate) fn debian_architecture_from_rust_triple(target: &str) -> &str {
let mut parts = target.split('-');
let arch = parts.next().unwrap();
let abi = parts.last().unwrap_or("");
match (arch, abi) {
("aarch64", _) => "arm64",
("mips64", "gnuabin32") => "mipsn32",
("mips64el", "gnuabin32") => "mipsn32el",
("mipsisa32r6", _) => "mipsr6",
("mipsisa32r6el", _) => "mipsr6el",
("mipsisa64r6", "gnuabi64") => "mips64r6",
("mipsisa64r6", "gnuabin32") => "mipsn32r6",
("mipsisa64r6el", "gnuabi64") => "mips64r6el",
("mipsisa64r6el", "gnuabin32") => "mipsn32r6el",
("powerpc", "gnuspe") => "powerpcspe",
("powerpc64", _) => "ppc64",
("powerpc64le", _) => "ppc64el",
("riscv64gc", _) => "riscv64",
("i586" | "i686" | "x86", _) => "i386",
("x86_64", "gnux32") => "x32",
("x86_64", _) => "amd64",
("loongarch64", _) => "loong64",
(arm, gnueabi) if arm.starts_with("arm") && gnueabi.ends_with("hf") => "armhf",
(arm, _) if arm.starts_with("arm") => "armel",
(other_arch, _) => other_arch,
}
}
fn ensure_success(status: ExitStatus) -> io::Result<()> {
if status.success() {
Ok(())
} else {
Err(io::Error::new(io::ErrorKind::Other, status.to_string()))
}
}
pub fn strip_binaries(options: &mut Config, target: Option<&str>, listener: &dyn Listener, separate_file: bool) -> CDResult<()> {
let mut cargo_config = None;
let objcopy_tmp;
let strip_tmp;
let mut objcopy_cmd = Path::new("objcopy");
let mut strip_cmd = Path::new("strip");
if let Some(target) = target {
cargo_config = options.cargo_config()?;
if let Some(ref conf) = cargo_config {
if let Some(cmd) = conf.objcopy_command(target) {
listener.info(format!("Using '{}' for '{target}'", cmd.display()));
objcopy_tmp = cmd;
objcopy_cmd = &objcopy_tmp;
}
if let Some(cmd) = conf.strip_command(target) {
listener.info(format!("Using '{}' for '{target}'", cmd.display()));
strip_tmp = cmd;
strip_cmd = &strip_tmp;
}
}
}
let stripped_binaries_output_dir = options.default_deb_output_dir();
let original_binaries = options.built_binaries_mut().into_iter().map(|asset| asset.clone()).collect();
options.built_binaries_mut().into_par_iter().enumerate()
.filter(|(_, asset)| !asset.source.archive_as_symlink_only()) .try_for_each(|(i, asset)| {
let new_source = match asset.source.path() {
Some(path) => {
if !path.exists() {
return Err(CargoDebError::StripFailed(path.to_owned(), "The file doesn't exist".into()));
}
let conf_path = cargo_config.as_ref().map(|c| c.path())
.unwrap_or_else(|| Path::new(".cargo/config"));
let file_name = path.file_name().ok_or(CargoDebError::Str("bad path"))?;
let file_name = format!("{}.tmp{}-stripped", file_name.to_string_lossy(), i);
let stripped_temp_path = stripped_binaries_output_dir.join(file_name);
let _ = std::fs::remove_file(&stripped_temp_path);
log::debug!("stripping with {} from {} into {}", strip_cmd.display(), path.display(), stripped_temp_path.display());
Command::new(strip_cmd)
.arg("--strip-unneeded")
.arg("-o")
.arg(&stripped_temp_path)
.arg(path)
.status()
.and_then(ensure_success)
.map_err(|err| {
if let Some(target) = target {
CargoDebError::StripFailed(path.to_owned(), format!("{}: {}.\nhint: Target-specific strip commands are configured in [target.{}] strip = {{ path = \"{}\" }} in {}", strip_cmd.display(), err, target, strip_cmd.display(), conf_path.display()))
} else {
CargoDebError::CommandFailed(err, "strip")
}
})?;
if !stripped_temp_path.exists() {
return Err(CargoDebError::StripFailed(path.to_owned(), format!("{} command failed to create output '{}'", strip_cmd.display(), stripped_temp_path.display())));
}
if separate_file {
let debug_path = asset.source.debug_source().expect("Failed to compute debug source path");
log::debug!("extracting debug info with {} from {} into {}", objcopy_cmd.display(), path.display(), debug_path.display());
let _ = std::fs::remove_file(&debug_path);
Command::new(objcopy_cmd)
.arg("--only-keep-debug")
.arg(path)
.arg(&debug_path)
.status()
.and_then(ensure_success)
.map_err(|err| {
if let Some(target) = target {
CargoDebError::StripFailed(path.to_owned(), format!("{}: {}.\nhint: Target-specific strip commands are configured in [target.{}] objcopy = {{ path =\"{}\" }} in {}", objcopy_cmd.display(), err, target, objcopy_cmd.display(), conf_path.display()))
} else {
CargoDebError::CommandFailed(err, "objcopy")
}
})?;
log::debug!("linking debug info with {} from {} into {}", objcopy_cmd.display(), stripped_temp_path.display(), debug_path.display());
let debug_filename = debug_path.file_name().expect("Built binary has no filename");
Command::new(objcopy_cmd)
.current_dir(debug_path.parent().expect("Debug source file had no parent path"))
.arg(format!(
"--add-gnu-debuglink={}",
debug_filename.to_str().expect("Debug source file had no filename")
))
.arg(&stripped_temp_path)
.status()
.and_then(ensure_success)
.map_err(|err| CargoDebError::CommandFailed(err, "objcopy"))?;
}
listener.info(format!("Stripped '{}'", path.display()));
AssetSource::Path(stripped_temp_path)
},
None => {
listener.warning(format!("Found built asset with non-path source '{asset:?}'"));
return Ok(());
},
};
log::debug!("Replacing asset {} with stripped asset {}", asset.source.path().unwrap().display(), new_source.path().unwrap().display());
asset.source = new_source;
Ok::<_, CargoDebError>(())
})?;
if separate_file {
options.add_debug_assets(original_binaries);
}
Ok(())
}