use std::path::{Path, PathBuf};
use cargo::core::Workspace;
use cargo::util::command_prelude::opt;
use cargo::util::command_prelude::{AppExt, ArgMatchesExt};
use cargo::CliResult;
use cargo::Config;
use cargo_c::build::{cbuild, config_configure};
use cargo_c::build_targets::BuildTargets;
use cargo_c::cli::base_cli;
use cargo_c::install_paths::InstallPaths;
use cargo_c::target::Target;
use anyhow::Context;
use structopt::clap::*;
pub fn cli() -> App<'static, 'static> {
let subcommand = base_cli()
.name("cinstall")
.arg_jobs()
.arg_release("Build artifacts in release mode, with optimizations")
.arg_profile("Build artifacts with the specified profile")
.arg_features()
.arg_target_triple("Build for the target triple")
.arg_target_dir()
.arg(
opt(
"out-dir",
"Copy final artifacts to this directory (unstable)",
)
.value_name("PATH"),
)
.arg_manifest_path()
.arg_message_format()
.arg_build_plan()
.after_help(
"\
Compilation can be configured via the use of profiles which are configured in
the manifest. The default profile for this command is `dev`, but passing
the --release flag will use the `release` profile instead.
",
);
app_from_crate!()
.settings(&[
AppSettings::UnifiedHelpMessage,
AppSettings::DeriveDisplayOrder,
AppSettings::VersionlessSubcommands,
])
.subcommand(subcommand)
}
fn append_to_destdir(destdir: &PathBuf, path: &PathBuf) -> PathBuf {
let path = if path.is_absolute() {
let mut components = path.components();
let _ = components.next();
components.as_path()
} else {
path.as_path()
};
destdir.join(path)
}
fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> anyhow::Result<u64> {
let from = from.as_ref();
let to = to.as_ref();
std::fs::copy(from, to)
.with_context(|| format!("Cannot copy {} to {}.", from.display(), to.display()))
}
fn cinstall(
ws: &Workspace,
target: &Target,
build_targets: BuildTargets,
paths: InstallPaths,
) -> anyhow::Result<()> {
use std::fs;
let pkg = ws.current()?;
let os = &target.os;
let env = &target.env;
let ver = pkg.version();
let name = &pkg
.manifest()
.targets()
.iter()
.find(|t| t.is_lib())
.unwrap()
.crate_name();
let destdir = &paths.destdir;
let install_path_lib = append_to_destdir(destdir, &paths.libdir);
let install_path_pc = append_to_destdir(destdir, &paths.pkgconfigdir);
let install_path_include = append_to_destdir(destdir, &paths.includedir).join(name);
let install_path_bin = append_to_destdir(destdir, &paths.bindir);
fs::create_dir_all(&install_path_lib)?;
fs::create_dir_all(&install_path_pc)?;
fs::create_dir_all(&install_path_include)?;
fs::create_dir_all(&install_path_bin)?;
ws.config()
.shell()
.status("Installing", "pkg-config file")?;
fs::copy(
&build_targets.pc,
install_path_pc.join(&format!("{}.pc", name)),
)?;
ws.config().shell().status("Installing", "header file")?;
fs::copy(
&build_targets.include,
install_path_include.join(&format!("{}.h", name)),
)?;
if let Some(ref static_lib) = build_targets.static_lib {
ws.config().shell().status("Installing", "static library")?;
let static_lib_path = if env == "msvc" {
format!("{}.lib", name)
} else {
format!("lib{}.a", name)
};
copy(static_lib, install_path_lib.join(&static_lib_path))?;
}
if let Some(ref shared_lib) = build_targets.shared_lib {
ws.config().shell().status("Installing", "shared library")?;
let link_libs = |lib: &str, lib_with_major_ver: &str, lib_with_full_ver: &str| {
let mut ln_sf = std::process::Command::new("ln");
ln_sf.arg("-sf");
ln_sf
.arg(lib_with_full_ver)
.arg(install_path_lib.join(lib_with_major_ver));
let _ = ln_sf.status().unwrap();
let mut ln_sf = std::process::Command::new("ln");
ln_sf.arg("-sf");
ln_sf.arg(lib_with_full_ver).arg(install_path_lib.join(lib));
let _ = ln_sf.status().unwrap();
};
match (os.as_str(), env.as_str()) {
("linux", _) | ("freebsd", _) | ("dragonfly", _) | ("netbsd", _) => {
let lib = &format!("lib{}.so", name);
let lib_with_major_ver = &format!("{}.{}", lib, ver.major);
let lib_with_full_ver =
&format!("{}.{}.{}", lib_with_major_ver, ver.minor, ver.patch);
copy(shared_lib, install_path_lib.join(lib_with_full_ver))?;
link_libs(lib, lib_with_major_ver, lib_with_full_ver);
}
("macos", _) => {
let lib = &format!("lib{}.dylib", name);
let lib_with_major_ver = &format!("lib{}.{}.dylib", name, ver.major);
let lib_with_full_ver = &format!(
"lib{}.{}.{}.{}.dylib",
name, ver.major, ver.minor, ver.patch
);
copy(shared_lib, install_path_lib.join(lib_with_full_ver))?;
link_libs(lib, lib_with_major_ver, lib_with_full_ver);
}
("windows", ref env) => {
let lib = format!("{}.dll", name);
let impl_lib = if *env == "msvc" {
format!("{}.dll.lib", name)
} else {
format!("lib{}.dll.a", name)
};
let def = format!("{}.def", name);
copy(shared_lib, install_path_bin.join(lib))?;
copy(
build_targets.impl_lib.as_ref().unwrap(),
install_path_lib.join(impl_lib),
)?;
copy(
build_targets.def.as_ref().unwrap(),
install_path_lib.join(def),
)?;
}
_ => unimplemented!("The target {}-{} is not supported yet", os, env),
}
}
Ok(())
}
fn main() -> CliResult {
let mut config = Config::default()?;
let args = cli().get_matches();
let subcommand_args = match args.subcommand() {
("cinstall", Some(args)) => args,
_ => {
cli().print_help()?;
return Ok(());
}
};
config_configure(&mut config, subcommand_args)?;
let mut ws = subcommand_args.workspace(&config)?;
let (build_targets, install_paths) = cbuild(&mut ws, &config, &subcommand_args)?;
cinstall(
&ws,
&Target::new(args.target())?,
build_targets,
install_paths,
)?;
Ok(())
}