use crate::{
cargo_toml::CargoToml,
config::{
ALMA_ARM64_IMAGE, ALMA_X64_IMAGE, ARM64_TARGET, CENTOS_ARM64_IMAGE, CENTOS_X64_IMAGE,
QEMU_IMAGE, UBUNTU_X64_IMAGE, X64_TARGET,
},
param::get_param,
};
use shlex;
#[cfg(any(target_os = "macos", target_os = "linux"))]
use std::os::unix::fs::PermissionsExt;
use std::{env, error::Error, fs, path::PathBuf, process::Command};
pub fn build(
active_channel: &String,
active_target: &String,
is_ubuntu: bool,
is_alma: bool,
args: &mut Vec<String>,
) -> Result<(), Box<dyn Error>> {
prepare()?;
let mut cargo_toml = CargoToml::new::<PathBuf>(None)?;
let package_name = cargo_toml.get_string(&vec!["package", "name"], Some(""))?;
let crate_type = cargo_toml
.get_string_array(&vec!["lib", "crate-type"], None)
.unwrap_or_default();
let is_executable = crate_type.contains(&String::from("bin")) || crate_type.is_empty();
let is_static_lib = crate_type.contains(&String::from("staticlib"));
let is_dynamic_lib =
crate_type.contains(&String::from("cdylib")) || crate_type.contains(&String::from("dylib"));
if package_name == "" {
panic!("Invalid Package Name");
}
let upx = get_param(args, "--upx", false, true);
cargo_toml.get_bool(&vec!["profile", "release", "strip"], Some(true))?;
cargo_toml.get_string(&vec!["profile", "release", "opt-level"], Some("z"))?;
cargo_toml.get_integer(&vec!["profile", "release", "codegen-units"], Some(1))?;
cargo_toml.get_bool(&vec!["profile", "release", "lto"], Some(true))?;
let default_panic = if active_channel == "nightly" {
"immediate-abort"
} else {
"abort"
};
let panic = cargo_toml.get_string(&vec!["profile", "release", "panic"], Some(default_panic))?;
if panic == "immediate-abort" {
let mut cargo_features = cargo_toml
.get_string_array(&vec!["cargo-features"], Some(vec!["panic-immediate-abort"]))?;
if !cargo_features.contains(&"panic-immediate-abort".to_string()) {
cargo_features.push("panic-immediate-abort".to_string());
cargo_toml.set_string_array(&vec!["cargo-features"], cargo_features)?;
}
}
cargo_toml.save(false)?;
let bin_name = if is_executable {
package_name.clone()
} else {
let lib = if is_static_lib {
format!("lib{}.a", package_name)
} else if is_dynamic_lib {
format!("lib{}.so", package_name)
} else {
panic!("Unsupport Crate Type");
};
lib.replace("-", "_")
};
let dist = cargo_toml.root.join("dist");
fs::create_dir_all(&dist)?;
let target = cargo_toml
.root
.join("target")
.join(&active_target)
.join("release");
let target_bin = target.join(&bin_name);
let target_bin_relative = target_bin
.strip_prefix(&cargo_toml.root)?
.to_string_lossy()
.replace("\\", "/");
let (base_name, ext_name) = {
let mut parts = bin_name.split(".").collect::<Vec<&str>>();
let ext_name = if parts.len() > 1 {
format!(".{}", parts.pop().unwrap())
} else {
String::new()
};
let base_name = parts.join(".");
(base_name, ext_name)
};
let dist_bin_name = if active_target == X64_TARGET {
format!("{base_name}_linux_x86_64{ext_name}")
} else if active_target == ARM64_TARGET {
format!("{base_name}_linux_arm64{ext_name}")
} else {
panic!("Unsupport Target");
};
let dist_bin = dist.join(&dist_bin_name);
let dist_bin_relative = dist_bin
.strip_prefix(&cargo_toml.root)?
.to_string_lossy()
.replace("\\", "/");
let mut args = args.clone();
args.push("--target".to_string());
args.push(active_target.clone());
if active_channel == "nightly" {
args.push("-Z".to_string());
args.push("build-std=std,panic_abort".to_string());
args.push("-Z".to_string());
args.push("build-std-features=".to_string());
}
let mut rust_flags = Vec::<String>::new();
if active_channel == "nightly" {
rust_flags.push("-Z".to_string());
rust_flags.push("location-detail=none".to_string());
}
if panic == "immediate-abort" {
rust_flags.push("-Z".to_string());
rust_flags.push("unstable-options".to_string());
rust_flags.push("-C".to_string());
rust_flags.push("panic=immediate-abort".to_string());
}
let cargo_config = CargoToml::from_cargo_config()?;
let (image, platform, devtoolset) =
get_image_and_devtoolset(&active_target, is_ubuntu, is_alma);
let build_script_name = format!("ibuild-build-{}.sh", active_target);
let build_script = cargo_toml.root.join(&build_script_name);
let pre_build_script = cargo_toml.root.join("pre-ibuild.sh");
let post_build_script = cargo_toml.root.join("post-ibuild.sh");
let upx_script = format!(
"upx --best --lzma {}",
shlex::try_quote(target_bin_relative.as_str())?
);
let scl = if devtoolset.is_empty() {
String::new()
} else {
format!("source scl_source enable {} > /dev/null", devtoolset)
};
fs::write(
&build_script,
[
"#!/bin/bash",
scl.as_str(),
"rm -rf /root/.cargo/config",
"rm -rf /root/.cargo/config.toml",
format!(
"echo {} > /root/.cargo/config.toml",
shlex::try_quote(cargo_config.to_string()?.as_str())?
)
.as_str(),
format!("rustup default {} > /dev/null 2>&1", active_channel).as_str(),
format!("RUSTFLAGS={}", rust_flags.join(" ")).as_str(),
if pre_build_script.exists() {
"./pre-ibuild.sh"
} else {
""
},
format!("cargo build --target {} \"$@\"", active_target).as_str(),
if post_build_script.exists() {
"./post-ibuild.sh"
} else {
""
},
format!(
"cp -f {} {}",
shlex::try_quote(target_bin_relative.as_str())?,
shlex::try_quote(dist_bin_relative.as_str())?
)
.as_str(),
if upx { upx_script.as_str() } else { "" },
]
.join("\n"),
)?;
#[cfg(any(target_os = "macos", target_os = "linux"))]
fs::set_permissions(&build_script, fs::Permissions::from_mode(0o777))?;
let cargo_dir = dirs::home_dir().unwrap().join(".cargo");
let cargo_registry_dir = cargo_dir.join("registry");
let cargo_git_dir = cargo_dir.join("git");
let mut command = Command::new("docker");
command.args([
"run",
"-it",
"--rm",
"--platform",
platform,
"-v",
format!(
"{}:{}",
cargo_registry_dir.to_str().unwrap(),
"/root/.cargo/registry"
)
.as_str(),
"-v",
format!("{}:{}", cargo_git_dir.to_str().unwrap(), "/root/.cargo/git").as_str(),
"-v",
format!("{}:{}", cargo_toml.root.to_str().unwrap(), "/opt/source").as_str(),
"-w",
"/opt/source",
"--entrypoint",
format!("/opt/source/{}", &build_script_name).as_str(),
]);
let mut rust_flags = Vec::<String>::new();
if active_channel == "nightly" {
rust_flags.push("-Z".to_string());
rust_flags.push("location-detail=none".to_string());
}
if !rust_flags.is_empty() {
command.args(["-e", format!("RUSTFLAGS={}", rust_flags.join(" ")).as_str()]);
}
command.args([image, "--release"]);
command.args(args);
let mut proc = command.spawn()?;
let code = proc.wait()?.code().unwrap_or(-1);
fs::remove_file(&build_script)?;
if code != 0 || !target_bin.is_file() {
panic!("Build Failed");
}
Ok(())
}
pub fn run(
active_channel: &String,
active_target: &String,
is_ubuntu: bool,
is_alma: bool,
) -> Result<(), Box<dyn Error>> {
exec(
active_channel,
active_target,
is_ubuntu,
is_alma,
&vec![String::from("bash")],
)
}
pub fn exec(
active_channel: &String,
active_target: &String,
is_ubuntu: bool,
is_alma: bool,
args: &Vec<String>,
) -> Result<(), Box<dyn Error>> {
prepare()?;
let root = match CargoToml::new::<PathBuf>(None) {
Ok(cargo_toml) => cargo_toml.root,
Err(_) => env::current_dir()?,
};
let cargo_config = CargoToml::from_cargo_config()?;
let (image, platform, devtoolset) =
get_image_and_devtoolset(&active_target, is_ubuntu, is_alma);
let exec_script_name = format!("ibuild-exec-{}.sh", active_target);
let exec_script = root.join(&exec_script_name);
let scl = if devtoolset.is_empty() {
String::new()
} else {
format!("source scl_source enable {} > /dev/null", devtoolset)
};
fs::write(
&exec_script,
[
"#!/bin/bash",
scl.as_str(),
"rm -rf /root/.cargo/config",
"rm -rf /root/.cargo/config.toml",
format!(
"echo {} > /root/.cargo/config.toml",
shlex::try_quote(cargo_config.to_string()?.as_str())?
)
.as_str(),
format!("rustup default {} > /dev/null 2>&1", active_channel).as_str(),
args.into_iter()
.map(|arg| shlex::try_quote(arg).unwrap_or_default().to_string())
.collect::<Vec<String>>()
.join(" ")
.as_str(),
]
.join("\n"),
)?;
#[cfg(any(target_os = "macos", target_os = "linux"))]
fs::set_permissions(&exec_script, fs::Permissions::from_mode(0o777))?;
let cargo_dir = dirs::home_dir().unwrap().join(".cargo");
let cargo_registry_dir = cargo_dir.join("registry");
let cargo_git_dir = cargo_dir.join("git");
let mut command = Command::new("docker");
command.args([
"run",
"-it",
"--rm",
"--platform",
platform,
"-v",
format!(
"{}:{}",
cargo_registry_dir.to_str().unwrap(),
"/root/.cargo/registry"
)
.as_str(),
"-v",
format!("{}:{}", cargo_git_dir.to_str().unwrap(), "/root/.cargo/git").as_str(),
"-v",
format!("{}:{}", root.to_str().unwrap(), "/opt/source").as_str(),
"-w",
"/opt/source",
"--entrypoint",
format!("/opt/source/{}", &exec_script_name).as_str(),
image,
]);
let mut proc = command.spawn()?;
proc.wait()?;
fs::remove_file(&exec_script)?;
Ok(())
}
fn get_image_and_devtoolset(
active_target: &String,
is_ubuntu: bool,
is_alma: bool,
) -> (&str, &str, &str) {
let image = if active_target == X64_TARGET {
if is_ubuntu {
(UBUNTU_X64_IMAGE.as_str(), "linux/amd64", "")
} else if is_alma {
(ALMA_X64_IMAGE.as_str(), "linux/amd64", "")
} else {
(CENTOS_X64_IMAGE.as_str(), "linux/amd64", "devtoolset-11")
}
} else if active_target == ARM64_TARGET {
if is_alma {
(ALMA_ARM64_IMAGE.as_str(), "linux/arm64", "")
} else {
(CENTOS_ARM64_IMAGE.as_str(), "linux/arm64", "devtoolset-10")
}
} else {
panic!("Unsupported Target: {}", active_target);
};
image
}
fn prepare() -> Result<(), Box<dyn Error>> {
let output = Command::new("docker").args(["version"]).output()?;
if !output.status.success() {
panic!("Docker Not Ready");
}
let output = Command::new("docker")
.args([
"ps",
"-aq",
"--filter",
format!("ancestor={}", QEMU_IMAGE).as_str(),
])
.output()?;
if !output.status.success() {
panic!("Docker Not Ready");
}
let stdout = String::from_utf8_lossy(&output.stdout);
for container in stdout.lines() {
if !container.trim().is_empty() {
let output = Command::new("docker")
.args(["rm", "-f", container])
.output()?;
if !output.status.success() {
panic!("Remove Exist Container {} Failed", container);
}
}
}
Command::new("docker")
.args([
"run",
"--rm",
"--privileged",
QEMU_IMAGE,
"--reset",
"-p",
"yes",
])
.output()?;
if !output.status.success() {
panic!("Run Qemu Failed");
}
Ok(())
}