#![feature(try_trait)]
#[cfg(feature = "use-bindgen")]
mod inner {
use {
anyhow::{Context, Result},
bindgen,
lazy_static::lazy_static,
std::env,
std::fs::File,
std::io::Write,
std::path::Path,
std::process,
};
lazy_static! {
static ref BINDGEN_SOURCE_MODULES: Vec<&'static str> = vec![
"ucal",
"uclean",
"ucol",
"udat",
"udata",
"uenum",
"ufieldpositer",
"uformattable",
"ulistformatter",
"umisc",
"umsg",
"unum",
"unumberformatter",
"upluralrules",
"uset",
"ustring",
"utext",
"utrans",
];
static ref BINDGEN_ALLOWLIST_FUNCTIONS: Vec<&'static str> = vec![
"u_.*",
"ucal_.*",
"ucol_.*",
"udat_.*",
"udata_.*",
"uenum_.*",
"ufieldpositer_.*",
"ufmt_.*",
"ulistfmt_.*",
"uloc_.*",
"umsg_.*",
"unum_.*",
"unumf_.*",
"uplrules_.*",
"utext_.*",
"utrans_.*",
];
static ref BINDGEN_ALLOWLIST_TYPES: Vec<&'static str> = vec![
"UAcceptResult",
"UBool",
"UCalendar.*",
"UChar.*",
"UCol.*",
"UCollation.*",
"UCollator",
"UData.*",
"UDate.*",
"UDateFormat.*",
"UDisplayContext.*",
"UEnumeration.*",
"UErrorCode",
"UField.*",
"UFormat.*",
"UFormattedList.*",
"UListFormatter.*",
"ULoc.*",
"ULOC.*",
"UMessageFormat",
"UNUM.*",
"UNumber.*",
"UParseError",
"UPlural.*",
"USet",
"UText",
"UTransDirection",
"UTransPosition",
"UTransliterator",
];
}
struct Command {
name: String,
rep: process::Command,
}
impl Command {
pub fn new(name: &'static str) -> Self {
let rep = process::Command::new(&name);
let name = String::from(name);
Command { name, rep }
}
pub fn run(&mut self, args: &[&str]) -> Result<String> {
self.rep.args(args);
let stdout = self.stdout()?;
Ok(String::from(&stdout).trim().to_string())
}
fn stdout(&mut self) -> Result<String> {
let output = self
.rep
.output()
.with_context(|| format!("could not execute command: {}", self.name))?;
let result = String::from_utf8(output.stdout)
.with_context(|| format!("could not convert output to UTF8"))?;
Ok(result.trim().to_string())
}
}
struct ICUConfig {
rep: Command,
}
impl ICUConfig {
fn new() -> Self {
ICUConfig {
rep: Command::new("pkg-config"),
}
}
fn prefix(&mut self) -> Result<String> {
self.rep
.run(&["--variable=prefix", "icu-i18n"])
.with_context(|| format!("could not get config prefix"))
}
fn libdir(&mut self) -> Result<String> {
self.rep
.run(&["--variable=libdir", "icu-i18n"])
.with_context(|| format!("could not get library directory"))
}
fn ldflags(&mut self) -> Result<String> {
let result = self
.rep
.run(&["--libs", "icu-i18n"])
.with_context(|| format!("could not get the ld flags"))?;
Ok(result.replace("-L", "-L ").replace("-l", "-l "))
}
fn cppflags(&mut self) -> Result<String> {
self.rep
.run(&["--cflags", "icu-i18n"])
.with_context(|| format!("while getting the cpp flags"))
}
fn version(&mut self) -> Result<String> {
self.rep
.run(&["--modversion", "icu-i18n"])
.with_context(|| format!("while getting ICU version; is icu-config in $PATH?"))
}
fn install_dir(&mut self) -> Result<String> {
self.prefix()
}
fn version_major() -> Result<String> {
let version = ICUConfig::new().version()?;
let components = version.split(".");
let last = components
.take(1)
.last()
.with_context(|| format!("could not parse version number: {}", version))?;
Ok(last.to_string())
}
fn version_major_int() -> Result<i32> {
let version_str = ICUConfig::version_major()?;
Ok(version_str.parse().unwrap())
}
}
fn has_renaming() -> Result<bool> {
let cpp_flags = ICUConfig::new().cppflags()?;
let found = cpp_flags.find("-DU_DISABLE_RENAMING=1");
Ok(found.is_none())
}
fn generate_wrapper_header(
out_dir_path: &Path,
bindgen_source_modules: &Vec<&str>,
include_path: &Path,
) -> String {
let wrapper_path = out_dir_path.join("wrapper.h");
let mut wrapper_file = File::create(&wrapper_path).unwrap();
wrapper_file
.write_all(b"/* Generated file, do not edit. */ \n")
.unwrap();
let includes = bindgen_source_modules
.iter()
.map(|f| {
let file_path = include_path.join(format!("{}.h", f));
let file_path_str = format!("#include \"{}\"\n", file_path.to_str().unwrap());
println!("include-file: '{}'", file_path.to_str().unwrap());
file_path_str
})
.collect::<String>();
wrapper_file.write_all(&includes.into_bytes()).unwrap();
String::from(wrapper_path.to_str().unwrap())
}
fn run_bindgen(header_file: &str, out_dir_path: &Path) -> Result<()> {
let mut builder = bindgen::Builder::default()
.header(header_file)
.default_enum_style(bindgen::EnumVariation::Rust {
non_exhaustive: false,
})
.rustfmt_bindings(true)
.generate_comments(false)
.derive_default(true)
.derive_hash(true)
.derive_partialord(true)
.derive_partialeq(true);
for bindgen_type in BINDGEN_ALLOWLIST_TYPES.iter() {
builder = builder.whitelist_type(bindgen_type);
}
for bindgen_function in BINDGEN_ALLOWLIST_FUNCTIONS.iter() {
builder = builder.whitelist_function(bindgen_function);
}
let renaming_arg =
match has_renaming().with_context(|| "could not prepare bindgen builder")? {
true => "",
false => "-DU_DISABLE_RENAMING=1",
};
let builder = builder.clang_arg(renaming_arg);
let ld_flags = ICUConfig::new()
.ldflags()
.with_context(|| "could not prepare bindgen builder")?;
let builder = builder.clang_arg(&ld_flags);
let cpp_flags = ICUConfig::new()
.cppflags()
.with_context(|| "could not prepare bindgen builder")?;
let builder = builder.clang_arg(cpp_flags);
let bindings = builder
.generate()
.map_err(|_| anyhow::anyhow!("could not generate bindings"))?;
let output_file_path = out_dir_path.join("lib.rs");
let output_file = output_file_path.to_str().unwrap();
bindings
.write_to_file(output_file)
.with_context(|| format!("while writing output"))?;
Ok(())
}
fn run_renamegen(out_dir_path: &Path) -> Result<()> {
let output_file_path = out_dir_path.join("macros.rs");
let mut macro_file = File::create(&output_file_path)
.with_context(|| format!("while opening {:?}", output_file_path))?;
if has_renaming()? {
println!("renaming: true");
let icu_major_version = ICUConfig::version_major()?;
let to_write = format!(
r#"
// Macros for changing function names.
// Automatically generated by build.rs.
extern crate paste;
// This library was build with version renaming, so rewrite every function name
// with its name with version number appended.
// The macro below will rename a symbol `foo::bar` to `foo::bar_{0}` (where "{0}")
// may be some other number depending on the ICU library in use.
#[cfg(feature="renaming")]
#[macro_export]
macro_rules! versioned_function {{
($i:ident) => {{
paste::expr! {{
[< $i _{0} >]
}}
}}
}}
// This allows the user to override the renaming configuration detected from
// icu-config.
#[cfg(not(feature="renaming"))]
#[macro_export]
macro_rules! versioned_function {{
($func_name:path) => {{
$func_name
}}
}}
"#,
icu_major_version
);
macro_file
.write_all(&to_write.into_bytes())
.with_context(|| format!("while writing macros.rs with renaming"))
} else {
println!("renaming: false");
macro_file
.write_all(
&r#"
// Macros for changing function names.
// Automatically generated by build.rs.
// There was no renaming in this one, so just short-circuit this macro.
#[macro_export]
macro_rules! versioned_function {{
($func_name:path) => {{
$func_name
}}
}}
"#
.to_string()
.into_bytes(),
)
.with_context(|| format!("while writing macros.rs without renaming"))
}
}
pub fn copy_features() -> Result<()> {
if let Some(_) = env::var_os("CARGO_FEATURE_RENAMING") {
println!("cargo:rustc-cfg=feature=\"renaming\"");
}
if let Some(_) = env::var_os("CARGO_FEATURE_USE_BINDGEN") {
println!("cargo:rustc-cfg=feature=\"use-bindgen\"");
}
if let Some(_) = env::var_os("CARGO_FEATURE_ICU_CONFIG") {
println!("cargo:rustc-cfg=feature=\"icu_config\"");
}
if let Some(_) = env::var_os("CARGO_FEATURE_ICU_VERSION_IN_ENV") {
println!("cargo:rustc-cfg=feature=\"icu_version_in_env\"");
}
if ICUConfig::version_major_int()? >= 67 {
println!("cargo:rustc-cfg=feature=\"icu_version_67_plus\"");
}
if ICUConfig::version_major_int()? >= 67 {
println!("cargo:rustc-cfg=feature=\"icu_version_67_plus\"");
}
Ok(())
}
pub fn icu_config_autodetect() -> Result<()> {
println!("icu-version: {}", ICUConfig::new().version()?);
println!("icu-cppflags: {}", ICUConfig::new().cppflags()?);
println!("icu-has-renaming: {}", has_renaming()?);
let out_dir = env::var("OUT_DIR").unwrap();
let out_dir_path = Path::new(&out_dir);
let include_dir_path = Path::new(&ICUConfig::new().prefix()?)
.join("include")
.join("unicode");
let header_file =
generate_wrapper_header(&out_dir_path, &BINDGEN_SOURCE_MODULES, &include_dir_path);
run_bindgen(&header_file, out_dir_path)
.with_context(|| format!("while running bindgen"))?;
run_renamegen(out_dir_path).with_context(|| format!("while running renamegen"))?;
println!("cargo:install-dir={}", ICUConfig::new().install_dir()?);
let lib_dir = ICUConfig::new().libdir()?;
println!("cargo:rustc-link-search=native={}", lib_dir);
println!("cargo:rustc-flags={}", ICUConfig::new().ldflags()?);
Ok(())
}
}
#[cfg(feature = "use-bindgen")]
fn main() -> Result<(), anyhow::Error> {
std::env::set_var("RUST_BACKTRACE", "full");
inner::copy_features()?;
if let None = std::env::var_os("CARGO_FEATURE_ICU_CONFIG") {
return Ok(());
}
inner::icu_config_autodetect()?;
println!("done:true");
Ok(())
}
#[cfg(not(feature = "use-bindgen"))]
fn main() {}