diff --git a/src/codegen/sys/build.rs b/src/codegen/sys/build.rs index 3be6a94a5..8e7a895d8 100644 --- a/src/codegen/sys/build.rs +++ b/src/codegen/sys/build.rs @@ -1,9 +1,11 @@ use super::collect_versions; -use crate::{codegen::general, env::Env, file_saver::save_to_file}; +use crate::{ + codegen::general, env::Env, file_saver::save_to_file, library::MAIN_NAMESPACE, nameutil, +}; use log::info; use std::io::{Result, Write}; -pub fn generate(env: &Env) { +pub fn generate(env: &Env, has_abi_tests: bool) { info!( "Generating sys build script for {}", env.config.library_name @@ -15,7 +17,7 @@ pub fn generate(env: &Env) { if !split_build_rs || !path.exists() { info!("Generating file {:?}", path); save_to_file(&path, env.config.make_backup, |w| { - generate_build_script(w, env, split_build_rs) + generate_build_script(w, env, split_build_rs, has_abi_tests) }); } @@ -29,11 +31,17 @@ pub fn generate(env: &Env) { } #[allow(clippy::write_literal)] -fn generate_build_script(w: &mut dyn Write, env: &Env, split_build_rs: bool) -> Result<()> { +fn generate_build_script( + w: &mut dyn Write, + env: &Env, + split_build_rs: bool, + has_abi_tests: bool, +) -> Result<()> { if !split_build_rs { general::start_comments(w, &env.config)?; writeln!(w)?; } + writeln!( w, "{}", @@ -55,13 +63,61 @@ fn main() {} // prevent linking libraries to avoid documentation failure #[cfg(not(feature = "dox"))] fn main() { - if let Err(s) = system_deps::Config::new().probe() { + let deps = system_deps::Config::new().probe(); + if let Err(s) = deps { println!("cargo:warning={}", s); process::exit(1); + }"## + )?; + + if has_abi_tests { + let ns = env.library.namespace(MAIN_NAMESPACE); + let package_name = ns.package_name.as_ref().expect("Missing package name"); + let lib_name = nameutil::crate_name(package_name); + + write!( + w, + "{}", + r##" + + #[cfg(feature = "abi-tests")] + { + let deps = deps.unwrap(); +"## + )?; + + write!( + w, + " let includes = deps.get(\"{}\").unwrap().include_paths.clone();", + lib_name + )?; + + write!( + w, + "{}", + r##" + + let mut cc = cc::Build::new(); + + cc.flag_if_supported("-Wno-deprecated-declarations"); + cc.flag_if_supported("/std:c11"); // for _Generic + cc.flag_if_supported("-std=c11"); // for _Generic + + cc.file("tests/constant.c"); + cc.file("tests/layout.c"); + + for i in includes { + cc.include(i); + } + + cc.compile("cabitests"); + } } -} "## - ) + ) + } else { + writeln!(w, "}}") + } } fn generate_build_version(w: &mut dyn Write, env: &Env) -> Result<()> { diff --git a/src/codegen/sys/cargo_toml.rs b/src/codegen/sys/cargo_toml.rs index 1787fa00d..44f19fa22 100644 --- a/src/codegen/sys/cargo_toml.rs +++ b/src/codegen/sys/cargo_toml.rs @@ -83,14 +83,13 @@ fn fill_in(root: &mut Table, env: &Env) { { let build_deps = upsert_table(root, "build-dependencies"); + set_string(build_deps, "cc", "1.0.0"); set_string(build_deps, "system-deps", "2.0"); } { let dev_deps = upsert_table(root, "dev-dependencies"); set_string(dev_deps, "shell-words", "1.0.0"); - set_string(dev_deps, "tempfile", "3"); - unset(dev_deps, "tempdir"); } { @@ -115,6 +114,7 @@ fn fill_in(root: &mut Table, env: &Env) { .collect(), ), ); + features.insert("abi-tests".to_string(), Value::Array(Vec::new())); } { diff --git a/src/codegen/sys/mod.rs b/src/codegen/sys/mod.rs index fb7523e40..dd1dc7129 100644 --- a/src/codegen/sys/mod.rs +++ b/src/codegen/sys/mod.rs @@ -13,9 +13,9 @@ mod tests; pub fn generate(env: &Env) { generate_single_version_file(env); lib_::generate(env); - build::generate(env); let crate_name = cargo_toml::generate(env); - tests::generate(env, &crate_name); + let has_abi_tests = tests::generate(env, &crate_name); + build::generate(env, has_abi_tests); } pub fn collect_versions(env: &Env) -> BTreeMap { diff --git a/src/codegen/sys/tests.rs b/src/codegen/sys/tests.rs index 156d3a6c5..806fe2b30 100644 --- a/src/codegen/sys/tests.rs +++ b/src/codegen/sys/tests.rs @@ -27,12 +27,12 @@ struct CConstant { value: String, } -pub fn generate(env: &Env, crate_name: &str) { +pub fn generate(env: &Env, crate_name: &str) -> bool { let ctypes = prepare_ctypes(env); let cconsts = prepare_cconsts(env); if ctypes.is_empty() && cconsts.is_empty() { - return; + return false; } let tests = env.config.target_path.join("tests"); @@ -58,6 +58,8 @@ pub fn generate(env: &Env, crate_name: &str) { save_to_file(&abi_rs, env.config.make_backup, |w| { generate_abi_rs(env, &abi_rs, w, crate_name, &ctypes, &cconsts) }); + + true } fn prepare_ctypes(env: &Env) -> Vec { @@ -213,20 +215,45 @@ fn generate_layout_c( writeln!(w)?; writeln!(w, "#include \"manual.h\"")?; writeln!(w, "#include ")?; - writeln!(w, "#include ")?; - writeln!(w)?; - writeln!(w, "{}", r"int main() {")?; + writeln!( + w, + "{}", + r####" +typedef struct { + const char *name; + size_t size; + size_t alignent; +} Layout; - for ctype in ctypes { - writeln!( +const Layout LAYOUTS[] = {"#### + )?; + + let n = ctypes.len(); + for (i, ctype) in ctypes.iter().enumerate() { + write!(w, "{}", " { ")?; + write!( w, - " printf(\"%s;%zu;%zu\\n\", \"{ctype}\", sizeof({ctype}), alignof({ctype}));", + "\"{ctype}\", sizeof({ctype}), alignof({ctype})", ctype = ctype.name )?; + + if i == n - 1 { + writeln!(w, "{}", " }")?; + } else { + writeln!(w, "{}", " },")?; + } } - writeln!(w, " return 0;")?; - writeln!(w, "{}", r"}") + writeln!( + w, + "{}", + r####"}; + +const Layout *c_layouts(size_t *n) { + *n = sizeof(LAYOUTS) / sizeof(Layout); + return LAYOUTS; +}"#### + ) } #[allow(clippy::write_literal)] @@ -240,42 +267,72 @@ fn generate_constant_c( general::start_comments(w, &env.config)?; writeln!(w)?; writeln!(w, "#include \"manual.h\"")?; - writeln!(w, "#include ")?; + writeln!(w, "#include ")?; writeln!( w, "{}", r####" -#define PRINT_CONSTANT(CONSTANT_NAME) \ - printf("%s;", #CONSTANT_NAME); \ - printf(_Generic((CONSTANT_NAME), \ - char *: "%s", \ - const char *: "%s", \ - char: "%c", \ - signed char: "%hhd", \ - unsigned char: "%hhu", \ - short int: "%hd", \ - unsigned short int: "%hu", \ - int: "%d", \ - unsigned int: "%u", \ - long: "%ld", \ - unsigned long: "%lu", \ - long long: "%lld", \ - unsigned long long: "%llu", \ - double: "%f", \ - long double: "%ld"), \ - CONSTANT_NAME); \ - printf("\n"); -"#### +#define FORMAT_CONSTANT(CONSTANT_NAME) \ + _Generic((CONSTANT_NAME), \ + char *: "%s", \ + const char *: "%s", \ + char: "%c", \ + signed char: "%hhd", \ + unsigned char: "%hhu", \ + short int: "%hd", \ + unsigned short int: "%hu", \ + int: "%d", \ + unsigned int: "%u", \ + long: "%ld", \ + unsigned long: "%lu", \ + long long: "%lld", \ + unsigned long long: "%llu", \ + double: "%f", \ + long double: "%ld") + +typedef struct { + char *name; + char *value; +} Constant; + +Constant *c_constants(size_t *n) {"#### )?; + writeln!(w, " *n = {};", cconsts.len())?; - writeln!(w, "{}", r"int main() {")?; + // We are leaking this, but for a test it does not matter + writeln!(w, "{}", " Constant *res = g_new0(Constant, *n);")?; - for cconst in cconsts { - writeln!(w, " PRINT_CONSTANT({name});", name = cconst.name,)?; + for (i, cconst) in cconsts.iter().enumerate() { + writeln!( + w, + " res[{index}].name = g_strdup(\"{name}\");", + index = i, + name = cconst.name + )?; + writeln!( + w, + " res[{index}].value = g_strdup_printf(FORMAT_CONSTANT({name}), {name});", + index = i, + name = cconst.name, + )?; } - writeln!(w, " return 0;")?; - writeln!(w, "{}", r"}") + writeln!(w, "{}", " return res;")?; + writeln!(w, "{}", "}")?; + + writeln!( + w, + "{}", + r####" +void c_constants_free(Constant *constants, size_t n) { + size_t i; + for (i = 0; i < n; i++) { + g_free(constants[i].name); + g_free(constants[i].value); + } + g_free(constants); +}"#### + ) } #[allow(clippy::write_literal)] @@ -287,91 +344,59 @@ fn generate_abi_rs( ctypes: &[CType], cconsts: &[CConstant], ) -> io::Result<()> { - let ns = env.library.namespace(MAIN_NAMESPACE); - let package_name = ns.package_name.as_ref().expect("Missing package name"); - info!("Generating file {:?}", path); general::start_comments(w, &env.config)?; writeln!(w)?; - writeln!(w, "use std::env;")?; - writeln!(w, "use std::error::Error;")?; - writeln!(w, "use std::ffi::OsString;")?; - writeln!(w, "use std::path::Path;")?; writeln!(w, "use std::mem::{{align_of, size_of}};")?; - writeln!(w, "use std::process::Command;")?; writeln!(w, "use std::str;")?; - writeln!(w, "use tempfile::Builder;")?; - writeln!(w, "use {}::*;\n", crate_name)?; - writeln!(w, "static PACKAGES: &[&str] = &[\"{}\"];", package_name)?; + writeln!(w, "use {}::*;", crate_name)?; writeln!( w, "{}", r####" -#[derive(Clone, Debug)] -struct Compiler { - pub args: Vec, -} - -impl Compiler { - pub fn new() -> Result> { - let mut args = get_var("CC", "cc")?; - args.push("-Wno-deprecated-declarations".to_owned()); - // For _Generic - args.push("-std=c11".to_owned()); - // For %z support in printf when using MinGW. - args.push("-D__USE_MINGW_ANSI_STDIO".to_owned()); - args.extend(get_var("CFLAGS", "")?); - args.extend(get_var("CPPFLAGS", "")?); - args.extend(pkg_config_cflags(PACKAGES)?); - Ok(Compiler { args }) +mod c_abi { + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + #[repr(C)] + pub struct CConstant { + pub name: *const libc::c_char, + pub value: *const libc::c_char, } - pub fn compile(&self, src: &Path, out: &Path) -> Result<(), Box> { - let mut cmd = self.to_command(); - cmd.arg(src); - cmd.arg("-o"); - cmd.arg(out); - let status = cmd.spawn()?.wait()?; - if !status.success() { - return Err(format!("compilation command {:?} failed, {}", &cmd, status).into()); - } - Ok(()) + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + #[repr(C)] + pub struct CLayout { + pub name: *const libc::c_char, + pub size: usize, + pub alignment: usize, } - fn to_command(&self) -> Command { - let mut cmd = Command::new(&self.args[0]); - cmd.args(&self.args[1..]); - cmd + extern "C" { + pub fn c_constants(n: *mut usize) -> *mut CConstant; + pub fn c_constants_free(c: *mut CConstant, n: usize); + pub fn c_layouts(n: *mut usize) -> *const CLayout; } } -fn get_var(name: &str, default: &str) -> Result, Box> { - match env::var(name) { - Ok(value) => Ok(shell_words::split(&value)?), - Err(env::VarError::NotPresent) => Ok(shell_words::split(default)?), - Err(err) => Err(format!("{} {}", name, err).into()), - } -} +fn c_constants() -> Vec<(String, String)> { + let mut res: Vec<(String, String)> = Vec::new(); -fn pkg_config_cflags(packages: &[&str]) -> Result, Box> { - if packages.is_empty() { - return Ok(Vec::new()); - } - let pkg_config = env::var_os("PKG_CONFIG") - .unwrap_or_else(|| OsString::from("pkg-config")); - let mut cmd = Command::new(pkg_config); - cmd.arg("--cflags"); - cmd.args(packages); - let out = cmd.output()?; - if !out.status.success() { - return Err(format!("command {:?} returned {}", - &cmd, out.status).into()); + unsafe { + let mut n = 0; + let p = c_abi::c_constants(&mut n); + let constants = std::slice::from_raw_parts(p, n); + + for c in constants { + let c_name = std::ffi::CStr::from_ptr(c.name); + let c_value = std::ffi::CStr::from_ptr(c.value); + res.push((c_name.to_str().unwrap().to_owned(), c_value.to_str().unwrap().to_owned())); + } + + c_abi::c_constants_free(p, n); } - let stdout = str::from_utf8(&out.stdout)?; - Ok(shell_words::split(stdout.trim())?) -} + res +} #[derive(Copy, Clone, Debug, Eq, PartialEq)] struct Layout { @@ -379,6 +404,26 @@ struct Layout { alignment: usize, } +fn c_layouts() -> Vec<(String, Layout)> { + let mut res = Vec::new(); + + unsafe { + let mut n = 0; + let p = c_abi::c_layouts(&mut n); + let layouts = std::slice::from_raw_parts(p, n); + + for l in layouts { + let c_name = std::ffi::CStr::from_ptr(l.name); + let name = c_name.to_str().unwrap().to_owned(); + let size = l.size; + let alignment = l.alignment; + res.push((name, Layout { size, alignment })); + } + } + + res +} + #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] struct Results { /// Number of successfully completed tests. @@ -408,22 +453,10 @@ impl Results { #[test] fn cross_validate_constants_with_c() { - let mut c_constants: Vec<(String, String)> = Vec::new(); - - for l in get_c_output("constant").unwrap().lines() { - let mut words = l.trim().split(";"); - let name = words.next().expect("Failed to parse name").to_owned(); - let value = words - .next() - .and_then(|s| s.parse().ok()) - .expect("Failed to parse value"); - c_constants.push((name, value)); - } - let mut results = Results::default(); for ((rust_name, rust_value), (c_name, c_value)) in - RUST_CONSTANTS.iter().zip(c_constants.iter()) + RUST_CONSTANTS.iter().zip(c_constants().iter()) { if rust_name != c_name { results.record_failed(); @@ -448,26 +481,10 @@ fn cross_validate_constants_with_c() { #[test] fn cross_validate_layout_with_c() { - let mut c_layouts = Vec::new(); - - for l in get_c_output("layout").unwrap().lines() { - let mut words = l.trim().split(";"); - let name = words.next().expect("Failed to parse name").to_owned(); - let size = words - .next() - .and_then(|s| s.parse().ok()) - .expect("Failed to parse size"); - let alignment = words - .next() - .and_then(|s| s.parse().ok()) - .expect("Failed to parse alignment"); - c_layouts.push((name, Layout { size, alignment })); - } - let mut results = Results::default(); for ((rust_name, rust_layout), (c_name, c_layout)) in - RUST_LAYOUTS.iter().zip(c_layouts.iter()) + RUST_LAYOUTS.iter().zip(c_layouts().iter()) { if rust_name != c_name { results.record_failed(); @@ -490,23 +507,6 @@ fn cross_validate_layout_with_c() { results.expect_total_success(); } -fn get_c_output(name: &str) -> Result> { - let tmpdir = Builder::new().prefix("abi").tempdir()?; - let exe = tmpdir.path().join(name); - let c_file = Path::new("tests").join(name).with_extension("c"); - - let cc = Compiler::new().expect("configured compiler"); - cc.compile(&c_file, &exe)?; - - let mut abi_cmd = Command::new(exe); - let output = abi_cmd.output()?; - if !output.status.success() { - return Err(format!("command {:?} failed, {:?}", &abi_cmd, &output).into()); - } - - Ok(String::from_utf8(output.stdout)?) -} - const RUST_LAYOUTS: &[(&str, Layout)] = &["#### )?; for ctype in ctypes { diff --git a/tests/sys/atk-sys/Cargo.toml b/tests/sys/atk-sys/Cargo.toml index d99e93973..025cfe0f9 100644 --- a/tests/sys/atk-sys/Cargo.toml +++ b/tests/sys/atk-sys/Cargo.toml @@ -76,4 +76,3 @@ features = ["dox"] [dev-dependencies] shell-words = "1.0.0" -tempfile = "3" diff --git a/tests/sys/gdk-pixbuf-sys/Cargo.toml b/tests/sys/gdk-pixbuf-sys/Cargo.toml index 878ceaa4b..7444242a4 100644 --- a/tests/sys/gdk-pixbuf-sys/Cargo.toml +++ b/tests/sys/gdk-pixbuf-sys/Cargo.toml @@ -59,4 +59,3 @@ features = ["dox"] [dev-dependencies] shell-words = "1.0.0" -tempfile = "3" diff --git a/tests/sys/gdk-sys/Cargo.toml b/tests/sys/gdk-sys/Cargo.toml index c7864b46b..ba3867e8c 100644 --- a/tests/sys/gdk-sys/Cargo.toml +++ b/tests/sys/gdk-sys/Cargo.toml @@ -84,4 +84,3 @@ features = ["dox"] [dev-dependencies] shell-words = "1.0.0" -tempfile = "3" diff --git a/tests/sys/gio-sys/Cargo.toml b/tests/sys/gio-sys/Cargo.toml index d2955895a..54284004e 100644 --- a/tests/sys/gio-sys/Cargo.toml +++ b/tests/sys/gio-sys/Cargo.toml @@ -100,4 +100,3 @@ features = ["dox"] [dev-dependencies] shell-words = "1.0.0" -tempfile = "3" diff --git a/tests/sys/glib-sys/Cargo.toml b/tests/sys/glib-sys/Cargo.toml index c13539544..4d9971bd3 100644 --- a/tests/sys/glib-sys/Cargo.toml +++ b/tests/sys/glib-sys/Cargo.toml @@ -90,4 +90,3 @@ features = ["dox"] [dev-dependencies] shell-words = "1.0.0" -tempfile = "3" diff --git a/tests/sys/gobject-sys/Cargo.toml b/tests/sys/gobject-sys/Cargo.toml index 61fb77b73..d415e64ba 100644 --- a/tests/sys/gobject-sys/Cargo.toml +++ b/tests/sys/gobject-sys/Cargo.toml @@ -69,4 +69,3 @@ features = ["dox"] [dev-dependencies] shell-words = "1.0.0" -tempfile = "3" diff --git a/tests/sys/gtk-sys/Cargo.toml b/tests/sys/gtk-sys/Cargo.toml index a1fa1e8f6..f29337130 100644 --- a/tests/sys/gtk-sys/Cargo.toml +++ b/tests/sys/gtk-sys/Cargo.toml @@ -118,4 +118,3 @@ features = ["dox"] [dev-dependencies] shell-words = "1.0.0" -tempfile = "3" diff --git a/tests/sys/pango-sys/Cargo.toml b/tests/sys/pango-sys/Cargo.toml index 576dc6288..3751fd84a 100644 --- a/tests/sys/pango-sys/Cargo.toml +++ b/tests/sys/pango-sys/Cargo.toml @@ -68,4 +68,3 @@ features = ["dox"] [dev-dependencies] shell-words = "1.0.0" -tempfile = "3" diff --git a/tests/sys/secret-sys/Cargo.toml b/tests/sys/secret-sys/Cargo.toml index f0205a419..39c9a5926 100644 --- a/tests/sys/secret-sys/Cargo.toml +++ b/tests/sys/secret-sys/Cargo.toml @@ -43,4 +43,3 @@ features = ["dox"] [dev-dependencies] shell-words = "1.0.0" -tempfile = "3"