use std::fs;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use crate::backend::*;
use crate::model::*;
mod cpp;
mod ctype;
mod doc;
mod formatting;
mod header;
pub(crate) struct CBindgenConfig {
pub(crate) output_dir: PathBuf,
pub(crate) ffi_name: &'static str,
pub(crate) extra_files: Vec<PathBuf>,
pub(crate) platform_locations: PlatformLocations,
pub(crate) generate_doxygen: bool,
}
pub(crate) fn generate_c_package(lib: &Library, config: &CBindgenConfig) -> FormattingResult<()> {
let include_path = config.output_dir.join("include");
let source_path = config.output_dir.join("src");
header::generate_c_header(lib, &include_path)?;
cpp::header::generate_header(lib, &include_path)?;
cpp::implementation::generate_cpp_file(lib, &source_path)?;
generate_cmake_config(lib, config, &config.platform_locations)?;
for pl in config.platform_locations.iter() {
let span = tracing::info_span!("libs", platform = pl.platform.target_triple);
let _entered = span.enter();
let lib_path = config
.output_dir
.join("lib")
.join(pl.platform.target_triple);
logged::create_dir_all(&lib_path)?;
let lib_filename = pl.platform.dyn_lib_filename(config.ffi_name);
logged::copy(
pl.location.join(&lib_filename),
lib_path.join(&lib_filename),
)?;
let bin_filename = pl.platform.bin_filename(config.ffi_name);
logged::copy(
pl.location.join(&bin_filename),
lib_path.join(&bin_filename),
)?;
}
logged::copy(
&lib.info.license_path,
config
.output_dir
.join(lib.info.license_path.file_name().unwrap()),
)?;
for path in &config.extra_files {
logged::copy(path, config.output_dir.join(path.file_name().unwrap()))?;
}
if config.generate_doxygen {
generate_doxygen(lib, config)?;
}
Ok(())
}
fn generate_doxygen(lib: &Library, config: &CBindgenConfig) -> FormattingResult<()> {
let doxygen_awesome = include_str!("../../../static/doxygen-awesome.css");
fs::write(
config.output_dir.join("doxygen-awesome.css"),
doxygen_awesome,
)?;
fs::write(config.output_dir.join("logo.png"), lib.info.logo_png)?;
let include_path = "include";
fs::create_dir_all(config.output_dir.join("doc").join("c"))?;
run_doxygen(
&config.output_dir,
&[
&format!("PROJECT_NAME = {} (C API)", lib.settings.name),
&format!("PROJECT_NUMBER = {}", lib.version),
&format!("INPUT = include/{}.h", lib.settings.name),
"HTML_OUTPUT = doc/c",
"GENERATE_LATEX = NO", "EXTRACT_STATIC = YES", "TYPEDEF_HIDES_STRUCT = YES", "AUTOLINK_SUPPORT = NO", "OPTIMIZE_OUTPUT_FOR_C = YES", "ALWAYS_DETAILED_SEC = YES", &format!("STRIP_FROM_PATH = {include_path}"), "HTML_EXTRA_STYLESHEET = doxygen-awesome.css",
"GENERATE_TREEVIEW = YES",
"PROJECT_LOGO = logo.png",
"HTML_COLORSTYLE_HUE = 209", "HTML_COLORSTYLE_SAT = 255",
"HTML_COLORSTYLE_GAMMA = 113",
],
)?;
fs::create_dir_all(config.output_dir.join("doc").join("cpp"))?;
run_doxygen(
&config.output_dir,
&[
&format!("PROJECT_NAME = {} (C++ API)", lib.settings.name),
&format!("PROJECT_NUMBER = {}", lib.version),
&format!("INPUT = {}/{}.hpp", include_path, lib.settings.name),
"HTML_OUTPUT = doc/cpp",
"GENERATE_LATEX = NO", "EXTRACT_STATIC = YES", "ALWAYS_DETAILED_SEC = YES", &format!("STRIP_FROM_PATH = {include_path}"), "HTML_EXTRA_STYLESHEET = doxygen-awesome.css",
"GENERATE_TREEVIEW = YES",
"PROJECT_LOGO = logo.png",
"HTML_COLORSTYLE_HUE = 209", "HTML_COLORSTYLE_SAT = 255",
"HTML_COLORSTYLE_GAMMA = 113",
],
)?;
Ok(())
}
fn run_doxygen(cwd: &Path, config_lines: &[&str]) -> FormattingResult<()> {
let mut command = Command::new("doxygen")
.current_dir(cwd)
.arg("-")
.stdin(std::process::Stdio::piped())
.spawn()?;
{
let stdin = command.stdin.as_mut().unwrap();
for line in config_lines {
stdin.write_all(&format!("{line}\n").into_bytes())?;
}
}
command.wait()?;
Ok(())
}
fn generate_cmake_config(
lib: &Library,
config: &CBindgenConfig,
platform_locations: &PlatformLocations,
) -> FormattingResult<()> {
fn write_set_libs(
f: &mut dyn Printer,
lib: &Library,
config: &CBindgenConfig,
pl: &PlatformLocation,
) -> FormattingResult<()> {
indented(f, |f| {
f.writeln(&format!(
"set({}_IMPORTED_LOCATION {})",
lib.settings.name.capital_snake_case(),
pl.platform.bin_filename(config.ffi_name)
))?;
f.writeln(&format!(
"set({}_IMPORTED_IMPLIB {})",
lib.settings.name.capital_snake_case(),
pl.platform.dyn_lib_filename(config.ffi_name)
))
})
}
let cmake_path = config.output_dir.join("cmake");
logged::create_dir_all(&cmake_path)?;
let filename = cmake_path.join(format!("{}-config.cmake", lib.settings.name));
let mut f = FilePrinter::new(filename)?;
f.writeln("set(prefix \"${CMAKE_CURRENT_LIST_DIR}/..\")")?;
f.newline()?;
let rust_target_var = format!("{}_RUST_TARGET", lib.settings.name.capital_snake_case());
let imported_location_var = format!(
"{}_IMPORTED_LOCATION",
lib.settings.name.capital_snake_case()
);
let imported_implib_var = format!("{}_IMPORTED_IMPLIB", lib.settings.name.capital_snake_case());
let (first, others) = platform_locations
.locations
.split_first()
.expect("there must be at least one target");
f.writeln(&format!("if(NOT {rust_target_var})"))?;
indented(&mut f, |f| {
if others.is_empty() {
f.writeln("# since there is only 1 target in this package we can assume this is what is wanted")?;
f.writeln(&format!(
"message(\"{} not set, default to the only library in this package: {}\")",
rust_target_var, first.platform.target_triple
))?;
f.writeln(&format!(
"set({} \"{}\")",
rust_target_var, first.platform.target_triple
))
} else {
f.writeln(&format!(
"message(FATAL_ERROR \"{} not specified and there {} possible targets\")",
rust_target_var,
platform_locations.locations.len()
))
}
})?;
f.writeln("endif()")?;
f.newline()?;
f.writeln(&format!(
"message(\"{rust_target_var} is: ${{{rust_target_var}}}\")"
))?;
f.newline()?;
f.writeln(&format!(
"if(${{{}}} STREQUAL \"{}\")",
rust_target_var, first.platform.target_triple
))?;
write_set_libs(&mut f, lib, config, first)?;
for pl in others {
f.writeln(&format!(
"elseif(${{{}}} STREQUAL \"{}\")",
rust_target_var, pl.platform.target_triple
))?;
write_set_libs(&mut f, lib, config, pl)?;
}
f.writeln("else()")?;
indented(&mut f, |f| {
f.writeln(&format!(
"message(FATAL_ERROR \"unknown target triple: ${{{rust_target_var}}}\")"
))
})?;
f.writeln("endif()")?;
f.newline()?;
f.writeln(&format!(
"add_library({} SHARED IMPORTED GLOBAL)",
lib.settings.name
))?;
f.writeln(&format!(
"set_target_properties({} PROPERTIES",
lib.settings.name
))?;
indented(&mut f, |f| {
f.writeln(&format!(
"IMPORTED_LOCATION \"${{prefix}}/lib/${{{rust_target_var}}}/${{{imported_location_var}}}\""
))?;
f.writeln(&format!(
"IMPORTED_IMPLIB \"${{prefix}}/lib/${{{rust_target_var}}}/${{{imported_implib_var}}}\""
))?;
f.writeln("INTERFACE_INCLUDE_DIRECTORIES \"${prefix}/include\"")
})?;
f.writeln(")")?;
f.newline()?;
f.writeln("get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES)")?;
f.writeln("if(\"CXX\" IN_LIST languages)")?;
indented(&mut f, |f| {
f.writeln("set(CMAKE_CXX_STANDARD 11)")?;
f.writeln(&format!(
"add_library({}_cpp OBJECT EXCLUDE_FROM_ALL ${{prefix}}/src/{}.cpp)",
lib.settings.name, lib.settings.name
))?;
f.writeln(&format!(
"target_compile_features({} INTERFACE cxx_std_14)",
lib.settings.name
))?;
f.writeln(&format!(
"target_link_libraries({}_cpp {})",
lib.settings.name, lib.settings.name
))?;
Ok(())
})?;
f.writeln("endif()")?;
Ok(())
}