use crate::xpath::path::path_to_string;
use anyhow::Result;
use std::{
fs::{File, create_dir_all},
io::Write,
path::{Path, PathBuf},
};
static CARGO_SCRIPT_SKIP_RUSTC_LINK_LIB: &str = "CARGO_SCRIPT_SKIP_RUSTC_LINK_LIB";
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LibKind {
Shared(String),
Static(String),
Auto(String),
}
fn libname_strip(lib_name_str: impl AsRef<Path>) -> String {
let lib_name = lib_name_str.as_ref().file_name().unwrap().to_str().unwrap().to_string();
if cfg!(target_os = "linux") {
let temp = lib_name.strip_prefix("lib").unwrap_or(&lib_name);
path_to_string(std::path::Path::new(&temp).file_stem().unwrap_or(temp.as_ref()))
} else {
lib_name.to_string()
}
}
pub fn format_target_link_libraries(kind: LibKind) -> Option<String> {
match std::env::var(CARGO_SCRIPT_SKIP_RUSTC_LINK_LIB) {
Ok(value) if value == "1" => None,
_ => {
let line = format!(
"cargo:rustc-link-lib={}",
match kind {
LibKind::Shared(name) => "dylib=".to_string() + &libname_strip(&name),
LibKind::Static(name) => "static=".to_string() + &libname_strip(&name),
LibKind::Auto(name) => libname_strip(&name),
}
);
Some(line)
},
}
}
pub fn target_link_libraries<Libs>(kinds: Libs)
where
Libs: IntoIterator<Item = LibKind>,
{
for kind in kinds {
if let Some(line) = format_target_link_libraries(kind) {
println!("{}", line);
}
}
}
pub fn target_link_directories<Paths>(p: Paths)
where
Paths: IntoIterator,
Paths::Item: AsRef<std::path::Path>,
{
for path in p {
let path_str = path.as_ref().display().to_string();
if path_str.is_empty() {
continue;
}
println!("cargo:rerun-if-changed={}", path_str);
println!("cargo:rustc-link-search={}", path_str);
}
}
#[derive(Default)]
pub struct Target {
pub name: String,
pub files: String,
pub include_dir: Option<String>,
pub lib_dir: Option<String>,
pub dep: Vec<String>,
pub type_: String,
}
pub struct Module {
module_name: String,
targets: Vec<Target>,
out_path: PathBuf,
version: String,
}
impl Module {
pub fn builder() -> ModuleBuilder {
ModuleBuilder::default()
}
pub fn write(&self) -> Result<()> {
create_dir_all(&self.out_path)?;
self.write_version_file()?;
self.write_target_file()?;
self.write_config_file()?;
Ok(())
}
fn get_target_file_name(&self) -> String {
format!("{}Targets.cmake", self.module_name)
}
fn write_config_file(&self) -> Result<()> {
let file_name = format!("{}Config.cmake", self.module_name);
let mut file = File::create(self.out_path.join(file_name))?;
let text = self.get_config_str()?;
file.write_all(text.as_bytes())?;
Ok(())
}
fn get_config_str(&self) -> Result<String> {
let s = format!(
r#"include(${{CMAKE_CURRENT_LIST_DIR}}/{})"#,
self.get_target_file_name()
);
Ok(s)
}
fn write_version_file(&self) -> Result<()> {
let file_name = format!("{}ConfigVersion.cmake", self.module_name);
let mut file = File::create(self.out_path.join(file_name))?;
let text = self.get_verion_str()?;
file.write_all(text.as_bytes())?;
Ok(())
}
fn get_verion_str(&self) -> Result<String> {
let s = format!(r#"set(PACKAGE_VERSION "{}")"#, self.version);
Ok(s)
}
fn get_target_str(&self) -> Result<String> {
let mut s = String::new();
s += r#"
get_filename_component(var_import_prefix "${CMAKE_CURRENT_LIST_FILE}" PATH)
get_filename_component(var_import_prefix "${var_import_prefix}" PATH)"#
.trim();
s += "\n";
for target in &self.targets {
let sub_target_name = format!("{}::{}", self.module_name, target.name);
s += &format!(r#"add_library({} {} IMPORTED)"#, sub_target_name, target.type_);
s += "\n";
s += &format!(r#"set_target_properties({} PROPERTIES"#, sub_target_name);
s += "\n";
{
let mut prop_line = "".to_string();
if let Some(include_dir) = &target.include_dir {
prop_line = format!(
r#"INTERFACE_INCLUDE_DIRECTORIES "${{var_import_prefix}}/{}""#,
include_dir
);
}
s += &prop_line;
s += "\n";
}
{
let mut prop_line = "".to_string();
if let Some(lib_dir) = &target.lib_dir {
prop_line = format!(
r#"IMPORTED_LOCATION "${{var_import_prefix}}/{}/{}""#,
lib_dir, target.files
);
}
s += &prop_line;
s += "\n";
}
{
s += "IMPORTED_NO_SONAME TRUE";
s += "\n";
}
{
if !target.dep.is_empty() {
let dep_part = target.dep.join(";");
s += &format!(r#"INTERFACE_LINK_LIBRARIES "{}""#, dep_part);
s += "\n";
}
}
s += ")";
s += "\n";
s += r#"set(${CMAKE_FIND_PACKAGE_NAME}_FOUND TRUE)"#;
s += "\n";
s += &format!(
r#"message(STATUS "Using {} ${{{}_VERSION}}")"#,
self.module_name, self.module_name
);
}
Ok(s)
}
fn write_target_file(&self) -> Result<()> {
let file_name = self.get_target_file_name();
let mut file = File::create(self.out_path.join(file_name))?;
let text = self.get_target_str()?;
file.write_all(text.as_bytes())?;
Ok(())
}
}
#[derive(Default)]
pub struct ModuleBuilder {
target_name: Vec<Target>,
out_path: Option<PathBuf>,
module_name: Option<String>,
version: Option<String>,
}
impl ModuleBuilder {
pub fn add_target(mut self, target: Target) -> Self {
self.target_name.push(target);
self
}
pub fn module_name(mut self, module_name: impl Into<String>) -> Self {
self.module_name = Some(module_name.into());
self
}
pub fn version(mut self, version: impl Into<String>) -> Self {
self.version = Some(version.into());
self
}
pub fn out_path(mut self, out_path: impl AsRef<Path>) -> Self {
self.out_path = Some(out_path.as_ref().to_path_buf());
self
}
pub fn build(self) -> Module {
Module {
version: self.version.expect("Version must be set"),
module_name: self.module_name.expect("Module name must be set"),
targets: self.target_name,
out_path: self.out_path.expect("Output path must be set"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_target_link_libraries() {
std::env::set_var("CARGO_SCRIPT_SKIP_RUSTC_LINK_LIB", "0");
assert_eq!(
format_target_link_libraries(LibKind::Shared("libm".into())),
Some("cargo:rustc-link-lib=dylib=m".to_string())
);
assert_eq!(
format_target_link_libraries(LibKind::Shared("/path/to/libm".into())),
Some("cargo:rustc-link-lib=dylib=m".to_string())
);
assert_eq!(
format_target_link_libraries(LibKind::Static("libm".into())),
Some("cargo:rustc-link-lib=static=m".to_string())
);
assert_eq!(
format_target_link_libraries(LibKind::Auto("libm".into())),
Some("cargo:rustc-link-lib=m".to_string())
);
assert_eq!(
format_target_link_libraries(LibKind::Auto("m.a".into())),
Some("cargo:rustc-link-lib=m".to_string())
);
assert_eq!(
format_target_link_libraries(LibKind::Auto("libm.a".into())),
Some("cargo:rustc-link-lib=m".to_string())
);
assert_eq!(
format_target_link_libraries(LibKind::Auto("libm.lib".into())),
Some("cargo:rustc-link-lib=m".to_string())
);
assert_eq!(
format_target_link_libraries(LibKind::Auto("libm.so".into())),
Some("cargo:rustc-link-lib=m".to_string())
);
assert_eq!(
format_target_link_libraries(LibKind::Shared("m".into())),
Some("cargo:rustc-link-lib=dylib=m".to_string())
);
assert_eq!(
format_target_link_libraries(LibKind::Static("m".into())),
Some("cargo:rustc-link-lib=static=m".to_string())
);
assert_eq!(
format_target_link_libraries(LibKind::Auto("m".into())),
Some("cargo:rustc-link-lib=m".to_string())
);
}
#[test]
fn test_format_target_link_libraries_skip() {
std::env::set_var("CARGO_SCRIPT_SKIP_RUSTC_LINK_LIB", "1");
assert_eq!(format_target_link_libraries(LibKind::Auto("m".into())), None);
}
#[test]
fn test_target_link_libraries() {
target_link_libraries([LibKind::Shared("z".to_string())]);
target_link_libraries(vec![LibKind::Shared("z".to_string())]);
}
#[test]
fn test_target_link_directories() {
target_link_directories([""]);
target_link_directories(["path1"]);
target_link_directories(vec!["path1"]);
}
#[test]
fn test_module_writer() {
let m = Module::builder()
.module_name("FUCK")
.version("2.0")
.out_path("/workspace/cyberex/target")
.add_target(Target {
name: "YOU".to_string(),
files: "fuck.so".to_string(),
include_dir: Some("include".to_string()),
lib_dir: Some("lib".to_string()),
dep: vec!["dl".to_string(), "ssl".to_string()],
type_: "SHARED".to_string(),
})
.build();
assert_eq!(
m.get_target_str().unwrap().trim(),
r#"
get_filename_component(var_import_prefix "${CMAKE_CURRENT_LIST_FILE}" PATH)
get_filename_component(var_import_prefix "${var_import_prefix}" PATH)
add_library(FUCK::YOU SHARED IMPORTED)
set_target_properties(FUCK::YOU PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${var_import_prefix}/include"
IMPORTED_LOCATION "${var_import_prefix}/lib/fuck.so"
IMPORTED_NO_SONAME TRUE
INTERFACE_LINK_LIBRARIES "dl;ssl"
)
set(${CMAKE_FIND_PACKAGE_NAME}_FOUND TRUE)
message(STATUS "Using FUCK ${FUCK_VERSION}")
"#
.trim()
);
assert_eq!(
m.get_verion_str().unwrap().trim(),
r#"set(PACKAGE_VERSION "2.0")"#.trim()
);
assert_eq!(
m.get_config_str().unwrap().trim(),
r#"include(${CMAKE_CURRENT_LIST_DIR}/FUCKTargets.cmake)"#.trim()
);
m.write().unwrap();
}
}