diff --git a/build.rs b/build.rs index 5644b02c1..8390b5f62 100644 --- a/build.rs +++ b/build.rs @@ -16,19 +16,6 @@ fn generate_tests() { println!("cargo:rerun-if-changed={}", tests_dir.display()); - // Try to make a decent guess at where our binary will end up in. - // - // TODO(emilio): Ideally running tests will just use the library-version of - // cbindgen instead of the built binary. - let cbindgen_path = out_dir - .parent() - .unwrap() - .parent() - .unwrap() - .parent() - .unwrap() - .join("cbindgen"); - for entry in entries { let path_segment = if entry.file_type().unwrap().is_file() { match entry.path().extension().and_then(OsStr::to_str) { @@ -53,8 +40,47 @@ fn generate_tests() { writeln!( dst, - "test_file!({:?}, test_{}, {:?}, {:?});", - cbindgen_path, + "test_file!(test_{}, {:?}, {:?});", + identifier, + path_segment, + entry.path(), + ) + .unwrap(); + } + + dst.flush().unwrap(); +} + +fn generate_depfile_tests() { + use std::env; + use std::fs::{self, File}; + use std::io::Write; + use std::path::{Path, PathBuf}; + + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let mut dst = File::create(Path::new(&out_dir).join("depfile_tests.rs")).unwrap(); + + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let tests_dir = manifest_dir.join("tests").join("depfile"); + let tests = fs::read_dir(&tests_dir).unwrap(); + + let entries = tests.map(|t| t.expect("Couldn't read test file")); + + println!("cargo:rerun-if-changed={}", tests_dir.display()); + + for entry in entries { + if entry.file_type().unwrap().is_file() { + continue; + }; + let path_segment = entry.file_name().to_str().unwrap().to_owned(); + + let identifier = path_segment + .replace(|c| !char::is_alphanumeric(c), "_") + .replace("__", "_"); + + writeln!( + dst, + "test_file!(test_depfile_{}, {:?}, {:?});", identifier, path_segment, entry.path(), @@ -67,4 +93,5 @@ fn generate_tests() { fn main() { generate_tests(); + generate_depfile_tests(); } diff --git a/src/bindgen/bindings.rs b/src/bindgen/bindings.rs index a03b2826d..cdacdadca 100644 --- a/src/bindgen/bindings.rs +++ b/src/bindgen/bindings.rs @@ -29,6 +29,7 @@ pub struct Bindings { constants: Vec, items: Vec, functions: Vec, + source_files: Vec, /// Bindings are generated by a recursive call to cbindgen /// and shouldn't do anything when written anywhere. noop: bool, @@ -50,6 +51,7 @@ impl Bindings { globals: Vec, items: Vec, functions: Vec, + source_files: Vec, noop: bool, ) -> Bindings { Bindings { @@ -61,6 +63,7 @@ impl Bindings { constants, items, functions, + source_files, noop, } } @@ -128,6 +131,47 @@ impl Bindings { fields } + pub fn generate_depfile>(&self, header_path: P, depfile_path: P) { + if let Some(dir) = depfile_path.as_ref().parent() { + if !dir.exists() { + std::fs::create_dir_all(dir).unwrap() + } + } + let canon_header_path = header_path.as_ref().canonicalize().unwrap(); + let mut canon_source_files: Vec<_> = self + .source_files + .iter() + .chain(self.config.config_path.as_ref().into_iter()) + .map(|p| p.canonicalize().unwrap()) + .collect(); + // Sorting makes testing easier by ensuring the output is ordered. + canon_source_files.sort_unstable(); + + // When writing the depfile we must escape whitespace in paths to avoid it being interpreted + // as a seperator. + // It is not clear how to otherwise _correctly_ replace whitespace in a non-unicode + // compliant slice, without knowing the encoding, so we lossy convert such cases, + // to avoid panics. + let mut depfile = File::create(depfile_path).unwrap(); + write!( + &mut depfile, + "{}:", + canon_header_path.to_string_lossy().replace(' ', "\\ ") + ) + .expect("Writing header name to depfile failed"); + canon_source_files.into_iter().for_each(|source_file| { + // Add line-continue and line-break and then indent with 4 spaces. + // This makes the output more human-readable. + depfile.write_all(b" \\\n ").unwrap(); + let escaped_path = source_file.to_string_lossy().replace(' ', "\\ "); + depfile.write_all(escaped_path.as_bytes()).unwrap(); + }); + + writeln!(&mut depfile).unwrap(); + + depfile.flush().unwrap(); + } + pub fn write_to_file>(&self, path: P) -> bool { if self.noop { return false; diff --git a/src/bindgen/builder.rs b/src/bindgen/builder.rs index 17d4ad1df..a0328b409 100644 --- a/src/bindgen/builder.rs +++ b/src/bindgen/builder.rs @@ -359,6 +359,7 @@ impl Builder { Default::default(), Default::default(), Default::default(), + Default::default(), true, )); } @@ -391,6 +392,8 @@ impl Builder { result.extend_with(&parser::parse_lib(cargo, &self.config)?); } + result.source_files.extend_from_slice(self.srcs.as_slice()); + Library::new( self.config, result.constants, @@ -401,6 +404,7 @@ impl Builder { result.opaque_items, result.typedefs, result.functions, + result.source_files, ) .generate() } diff --git a/src/bindgen/config.rs b/src/bindgen/config.rs index fbf35cbe1..c7590355a 100644 --- a/src/bindgen/config.rs +++ b/src/bindgen/config.rs @@ -5,7 +5,7 @@ use std::collections::{BTreeMap, HashMap}; use std::default::Default; use std::str::FromStr; -use std::{fmt, fs, path::Path as StdPath}; +use std::{fmt, fs, path::Path as StdPath, path::PathBuf as StdPathBuf}; use serde::de::value::{MapAccessDeserializer, SeqAccessDeserializer}; use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor}; @@ -1003,6 +1003,8 @@ pub struct Config { pub only_target_dependencies: bool, /// Configuration options specific to Cython. pub cython: CythonConfig, + #[serde(skip)] + pub(crate) config_path: Option, } impl Default for Config { @@ -1045,6 +1047,7 @@ impl Default for Config { pointer: PtrConfig::default(), only_target_dependencies: false, cython: CythonConfig::default(), + config_path: None, } } } @@ -1086,10 +1089,10 @@ impl Config { ) })?; - match toml::from_str::(&config_text) { - Ok(x) => Ok(x), - Err(e) => Err(format!("Couldn't parse config file: {}.", e)), - } + let mut config = toml::from_str::(&config_text) + .map_err(|e| format!("Couldn't parse config file: {}.", e))?; + config.config_path = Some(StdPathBuf::from(file_name.as_ref())); + Ok(config) } pub fn from_root_or_default>(root: P) -> Config { diff --git a/src/bindgen/library.rs b/src/bindgen/library.rs index c00a6ebc6..cb4cfbd3a 100644 --- a/src/bindgen/library.rs +++ b/src/bindgen/library.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use std::collections::HashMap; +use std::path::PathBuf; use crate::bindgen::bindings::Bindings; use crate::bindgen::config::{Config, Language, SortKey}; @@ -25,6 +26,7 @@ pub struct Library { opaque_items: ItemMap, typedefs: ItemMap, functions: Vec, + source_files: Vec, } impl Library { @@ -39,6 +41,7 @@ impl Library { opaque_items: ItemMap, typedefs: ItemMap, functions: Vec, + source_files: Vec, ) -> Library { Library { config, @@ -50,6 +53,7 @@ impl Library { opaque_items, typedefs, functions, + source_files, } } @@ -135,6 +139,7 @@ impl Library { globals, items, functions, + self.source_files, false, )) } diff --git a/src/bindgen/parser.rs b/src/bindgen/parser.rs index a964cd2d8..e8add192a 100644 --- a/src/bindgen/parser.rs +++ b/src/bindgen/parser.rs @@ -57,6 +57,7 @@ pub fn parse_src(src_file: &FilePath, config: &Config) -> ParseResult { }; context.parse_mod(&pkg_ref, src_file, 0)?; + context.out.source_files = context.cache_src.keys().map(|k| k.to_owned()).collect(); Ok(context.out) } @@ -79,6 +80,7 @@ pub(crate) fn parse_lib(lib: Cargo, config: &Config) -> ParseResult { let binding_crate = context.lib.as_ref().unwrap().binding_crate_ref(); context.parse_crate(&binding_crate)?; + context.out.source_files = context.cache_src.keys().map(|k| k.to_owned()).collect(); Ok(context.out) } @@ -406,6 +408,7 @@ pub struct Parse { pub opaque_items: ItemMap, pub typedefs: ItemMap, pub functions: Vec, + pub source_files: Vec, } impl Parse { @@ -419,6 +422,7 @@ impl Parse { opaque_items: ItemMap::default(), typedefs: ItemMap::default(), functions: Vec::new(), + source_files: Vec::new(), } } @@ -466,6 +470,7 @@ impl Parse { self.opaque_items.extend_with(&other.opaque_items); self.typedefs.extend_with(&other.typedefs); self.functions.extend_from_slice(&other.functions); + self.source_files.extend_from_slice(&other.source_files); } fn load_syn_crate_mod<'a>( diff --git a/src/main.rs b/src/main.rs index 812366f49..ea2ef88f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -262,6 +262,20 @@ fn main() { .help("Report errors only (overrides verbosity options).") .required(false), ) + .arg( + Arg::new("depfile") + .value_name("PATH") + .long("depfile") + .takes_value(true) + .min_values(1) + .max_values(1) + .required(false) + .help("Generate a depfile at the given Path listing the source files \ + cbindgen traversed when generating the bindings. Useful when \ + integrating cbindgen into 3rd party build-systems. \ + This option is ignored if `--out` is missing." + ) + ) .get_matches(); if !matches.is_present("out") && matches.is_present("verify") { @@ -306,6 +320,9 @@ fn main() { error!("Bindings changed: {}", file); std::process::exit(2); } + if let Some(depfile) = matches.value_of("depfile") { + bindings.generate_depfile(file, depfile) + } } _ => { bindings.write(io::stdout()); diff --git a/tests/depfile.rs b/tests/depfile.rs new file mode 100644 index 000000000..8dc1859f3 --- /dev/null +++ b/tests/depfile.rs @@ -0,0 +1,109 @@ +use std::fs::read_to_string; +use std::path::PathBuf; +use std::process::Command; + +static CBINDGEN_PATH: &str = env!("CARGO_BIN_EXE_cbindgen"); + +fn test_project(project_path: &str) { + let mut cmake_cmd = Command::new("cmake"); + cmake_cmd.arg("--version"); + cmake_cmd + .output() + .expect("CMake --version failed - Is CMake installed?"); + + let mut cmake_configure = Command::new("cmake"); + let build_dir = PathBuf::from(project_path).join("build"); + if build_dir.exists() { + std::fs::remove_dir_all(&build_dir).expect("Failed to remove old build directory"); + } + let project_dir = PathBuf::from(project_path); + + let cbindgen_define = format!("-DCBINDGEN_PATH={}", CBINDGEN_PATH); + cmake_configure + .arg("-S") + .arg(project_path) + .arg("-B") + .arg(&build_dir) + .arg(cbindgen_define); + let output = cmake_configure.output().expect("Failed to execute process"); + let stdout_str = String::from_utf8(output.stdout).unwrap(); + let stderr_str = String::from_utf8(output.stderr).unwrap(); + assert!( + output.status.success(), + "Configuring test project failed: stdout: `{}`, stderr: `{}`", + stdout_str, + stderr_str + ); + let depfile_path = build_dir.join("depfile.d"); + assert!( + !depfile_path.exists(), + "depfile should not exist before building" + ); + + // Do the clean first build + let mut cmake_build = Command::new("cmake"); + cmake_build.arg("--build").arg(&build_dir); + let output = cmake_build.output().expect("Failed to execute process"); + assert!(output.status.success(), "Building test project failed"); + let out_str = String::from_utf8(output.stdout).unwrap(); + assert!( + out_str.contains("Running cbindgen"), + "cbindgen rule did not run. Output: {}", + out_str + ); + + assert!( + depfile_path.exists(), + "depfile does not exist after building" + ); + + let expected_dependencies_filepath = PathBuf::from(project_path) + .join("expectations") + .join("dependencies"); + assert!( + expected_dependencies_filepath.exists(), + "Test did not define expected dependencies. Please read the Readme.md" + ); + let expected_deps = + read_to_string(expected_dependencies_filepath).expect("Failed to read dependencies"); + let depinfo = read_to_string(depfile_path).expect("Failed to read dependencies"); + // Assumes a single rule in the file - all deps are listed to the rhs of the `:`. + let actual_deps = depinfo.split(':').collect::>()[1]; + // Strip the line breaks. + let actual_deps = actual_deps.replace("\\\n", " "); + // I don't want to deal with supporting escaped whitespace when splitting at whitespace, + // so the tests don't support being run in a directory containing whitespace. + assert!( + !actual_deps.contains("\\ "), + "The tests directory may not contain any whitespace" + ); + let dep_list: Vec<&str> = actual_deps.split_ascii_whitespace().collect(); + let expected_dep_list: Vec = expected_deps + .lines() + .map(|dep| project_dir.join(dep).to_str().unwrap().to_string()) + .collect(); + assert_eq!(dep_list, expected_dep_list); + + let output = cmake_build.output().expect("Failed to execute process"); + assert!(output.status.success(), "Building test project failed"); + let out_str = String::from_utf8(output.stdout).unwrap(); + assert!( + !out_str.contains("Running cbindgen"), + "cbindgen rule ran on second build" + ); + + std::fs::remove_dir_all(build_dir).expect("Failed to remove old build directory"); + () +} + +macro_rules! test_file { + ($test_function_name:ident, $name:expr, $file:tt) => { + #[test] + fn $test_function_name() { + test_project($file); + } + }; +} + +// This file is generated by build.rs +include!(concat!(env!("OUT_DIR"), "/depfile_tests.rs")); diff --git a/tests/depfile/Readme.md b/tests/depfile/Readme.md new file mode 100644 index 000000000..d7f8b1a73 --- /dev/null +++ b/tests/depfile/Readme.md @@ -0,0 +1,11 @@ +This a folder containing tests for `--depfile` parameter. +Each test is in a subfolder and defines a minimum CMake project, +which uses cbindgen to generate Rust bindings and the `--depfile` +parameter to determine when to regenerate. +The outer test can the build the project, assert that rebuilding does not regenerate the +bindings, and then assert that touching the files involved does trigger rebuilding. + +The test project must contain an `expectations` folder, containing a file `dependencies`. +This `dependencies` should list all files that should be listed as dependencies in the generated +depfile. The paths should be relative to the project folder (i.e. to the folder containing +`expectations`). diff --git a/tests/depfile/cbindgen_test.cmake b/tests/depfile/cbindgen_test.cmake new file mode 100644 index 000000000..752c3c3b6 --- /dev/null +++ b/tests/depfile/cbindgen_test.cmake @@ -0,0 +1,27 @@ +# Common code used across the different tests + +if(NOT DEFINED CBINDGEN_PATH) + message(FATAL_ERROR "Path to cbindgen not specified") +endif() + +# Promote to cache +set(CBINDGEN_PATH "${CBINDGEN_PATH}" CACHE INTERNAL "") + +function(add_cbindgen_command custom_target_name header_destination) + # Place the depfile always at the same location, so the outer test framework can locate the file easily + set(depfile_destination "${CMAKE_BINARY_DIR}/depfile.d") + add_custom_command( + OUTPUT + "${header_destination}" "${depfile_destination}" + COMMAND + "${CBINDGEN_PATH}" + --output "${header_destination}" + --depfile "${depfile_destination}" + ${ARGN} + DEPFILE "${depfile_destination}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Running cbindgen" + COMMAND_EXPAND_LISTS + ) + add_custom_target("${custom_target_name}" ALL DEPENDS "${header_destination}") +endfunction() \ No newline at end of file diff --git a/tests/depfile/single_crate/.gitignore b/tests/depfile/single_crate/.gitignore new file mode 100644 index 000000000..378eac25d --- /dev/null +++ b/tests/depfile/single_crate/.gitignore @@ -0,0 +1 @@ +build diff --git a/tests/depfile/single_crate/CMakeLists.txt b/tests/depfile/single_crate/CMakeLists.txt new file mode 100644 index 000000000..3c7862478 --- /dev/null +++ b/tests/depfile/single_crate/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.21.0) + +project(depfile_test + LANGUAGES C + DESCRIPTION "A CMake Project to test the --depfile output from cbindgen" +) + +include(../cbindgen_test.cmake) + +add_cbindgen_command(gen_bindings + "${CMAKE_CURRENT_BINARY_DIR}/single_crate.h" +) diff --git a/tests/depfile/single_crate/Cargo.lock b/tests/depfile/single_crate/Cargo.lock new file mode 100644 index 000000000..26d416263 --- /dev/null +++ b/tests/depfile/single_crate/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "single_crate" +version = "0.1.0" diff --git a/tests/depfile/single_crate/Cargo.toml b/tests/depfile/single_crate/Cargo.toml new file mode 100644 index 000000000..66eff1125 --- /dev/null +++ b/tests/depfile/single_crate/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "single_crate" +version = "0.1.0" +authors = ["cbindgen"] + +[features] +cbindgen = [] diff --git a/tests/depfile/single_crate/expectations/dependencies b/tests/depfile/single_crate/expectations/dependencies new file mode 100644 index 000000000..7a9b14c93 --- /dev/null +++ b/tests/depfile/single_crate/expectations/dependencies @@ -0,0 +1,3 @@ +src/alias/mod.rs +src/annotation.rs +src/lib.rs diff --git a/tests/depfile/single_crate/src/alias/mod.rs b/tests/depfile/single_crate/src/alias/mod.rs new file mode 100644 index 000000000..86e3e0049 --- /dev/null +++ b/tests/depfile/single_crate/src/alias/mod.rs @@ -0,0 +1,32 @@ +#[repr(C)] +struct Dep { + a: i32, + b: f32, +} + +#[repr(C)] +struct Foo { + a: X, + b: X, + c: Dep, +} + +#[repr(u32)] +enum Status { + Ok, + Err, +} + +type IntFoo = Foo; +type DoubleFoo = Foo; + +type Unit = i32; +type SpecialStatus = Status; + +#[no_mangle] +pub extern "C" fn root( + x: IntFoo, + y: DoubleFoo, + z: Unit, + w: SpecialStatus +) { } diff --git a/tests/depfile/single_crate/src/annotation.rs b/tests/depfile/single_crate/src/annotation.rs new file mode 100644 index 000000000..70705923e --- /dev/null +++ b/tests/depfile/single_crate/src/annotation.rs @@ -0,0 +1,43 @@ +/// cbindgen:derive-lt=true +/// cbindgen:derive-lte=true +/// cbindgen:derive-constructor=true +/// cbindgen:rename-all=GeckoCase +#[repr(C)] +struct A(i32); + +/// cbindgen:field-names=[x, y] +#[repr(C)] +struct B(i32, f32); + +/// cbindgen:trailing-values=[Z, W] +#[repr(u32)] +enum C { + X = 2, + Y, +} + +/// cbindgen:derive-helper-methods=true +#[repr(u8)] +enum F { + Foo(i16), + Bar { x: u8, y: i16 }, + Baz +} + +/// cbindgen:derive-helper-methods +#[repr(C, u8)] +enum H { + Hello(i16), + There { x: u8, y: i16 }, + Everyone +} + +#[no_mangle] +pub extern "C" fn root( + x: A, + y: B, + z: C, + f: F, + h: H, +) { } + diff --git a/tests/depfile/single_crate/src/lib.rs b/tests/depfile/single_crate/src/lib.rs new file mode 100644 index 000000000..c604cbd38 --- /dev/null +++ b/tests/depfile/single_crate/src/lib.rs @@ -0,0 +1,2 @@ +mod alias; +mod annotation; diff --git a/tests/depfile/single_crate_config/.gitignore b/tests/depfile/single_crate_config/.gitignore new file mode 100644 index 000000000..378eac25d --- /dev/null +++ b/tests/depfile/single_crate_config/.gitignore @@ -0,0 +1 @@ +build diff --git a/tests/depfile/single_crate_config/CMakeLists.txt b/tests/depfile/single_crate_config/CMakeLists.txt new file mode 100644 index 000000000..db7229460 --- /dev/null +++ b/tests/depfile/single_crate_config/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.21.0) + +project(depfile_test + LANGUAGES C + DESCRIPTION "A CMake Project to test the --depfile output from cbindgen" + ) + +include(../cbindgen_test.cmake) + +add_cbindgen_command(gen_bindings + "${CMAKE_CURRENT_BINARY_DIR}/single_crate.h" + --config "${CMAKE_CURRENT_SOURCE_DIR}/config.toml" + ) diff --git a/tests/depfile/single_crate_config/Cargo.lock b/tests/depfile/single_crate_config/Cargo.lock new file mode 100644 index 000000000..26d416263 --- /dev/null +++ b/tests/depfile/single_crate_config/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "single_crate" +version = "0.1.0" diff --git a/tests/depfile/single_crate_config/Cargo.toml b/tests/depfile/single_crate_config/Cargo.toml new file mode 100644 index 000000000..66eff1125 --- /dev/null +++ b/tests/depfile/single_crate_config/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "single_crate" +version = "0.1.0" +authors = ["cbindgen"] + +[features] +cbindgen = [] diff --git a/tests/depfile/single_crate_config/config.toml b/tests/depfile/single_crate_config/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/depfile/single_crate_config/expectations/dependencies b/tests/depfile/single_crate_config/expectations/dependencies new file mode 100644 index 000000000..bf8ccb39e --- /dev/null +++ b/tests/depfile/single_crate_config/expectations/dependencies @@ -0,0 +1,4 @@ +config.toml +src/alias/mod.rs +src/annotation.rs +src/lib.rs diff --git a/tests/depfile/single_crate_config/src/alias/mod.rs b/tests/depfile/single_crate_config/src/alias/mod.rs new file mode 100644 index 000000000..6c045d294 --- /dev/null +++ b/tests/depfile/single_crate_config/src/alias/mod.rs @@ -0,0 +1,32 @@ +#[repr(C)] +struct Dep { + a: i32, + b: f32, +} + +#[repr(C)] +struct Foo { + a: X, + b: X, + c: Dep, +} + +#[repr(u32)] +enum Status { + Ok, + Err, +} + +type IntFoo = Foo; +type DoubleFoo = Foo; + +type Unit = i32; +type SpecialStatus = Status; + +#[no_mangle] +pub extern "C" fn root( + x: IntFoo, + y: DoubleFoo, + z: Unit, + w: SpecialStatus, +) {} diff --git a/tests/depfile/single_crate_config/src/annotation.rs b/tests/depfile/single_crate_config/src/annotation.rs new file mode 100644 index 000000000..54cd52662 --- /dev/null +++ b/tests/depfile/single_crate_config/src/annotation.rs @@ -0,0 +1,43 @@ +/// cbindgen:derive-lt=true +/// cbindgen:derive-lte=true +/// cbindgen:derive-constructor=true +/// cbindgen:rename-all=GeckoCase +#[repr(C)] +struct A(i32); + +/// cbindgen:field-names=[x, y] +#[repr(C)] +struct B(i32, f32); + +/// cbindgen:trailing-values=[Z, W] +#[repr(u32)] +enum C { + X = 2, + Y, +} + +/// cbindgen:derive-helper-methods=true +#[repr(u8)] +enum F { + Foo(i16), + Bar { x: u8, y: i16 }, + Baz, +} + +/// cbindgen:derive-helper-methods +#[repr(C, u8)] +enum H { + Hello(i16), + There { x: u8, y: i16 }, + Everyone, +} + +#[no_mangle] +pub extern "C" fn root( + x: A, + y: B, + z: C, + f: F, + h: H, +) {} + diff --git a/tests/depfile/single_crate_config/src/lib.rs b/tests/depfile/single_crate_config/src/lib.rs new file mode 100644 index 000000000..c604cbd38 --- /dev/null +++ b/tests/depfile/single_crate_config/src/lib.rs @@ -0,0 +1,2 @@ +mod alias; +mod annotation; diff --git a/tests/depfile/single_crate_default_config/.gitignore b/tests/depfile/single_crate_default_config/.gitignore new file mode 100644 index 000000000..378eac25d --- /dev/null +++ b/tests/depfile/single_crate_default_config/.gitignore @@ -0,0 +1 @@ +build diff --git a/tests/depfile/single_crate_default_config/CMakeLists.txt b/tests/depfile/single_crate_default_config/CMakeLists.txt new file mode 100644 index 000000000..7a2b0ab9b --- /dev/null +++ b/tests/depfile/single_crate_default_config/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.21.0) + +project(depfile_test + LANGUAGES C + DESCRIPTION "A CMake Project to test the --depfile output from cbindgen" + ) + +include(../cbindgen_test.cmake) + +add_cbindgen_command(gen_bindings + "${CMAKE_CURRENT_BINARY_DIR}/single_crate.h" + ) diff --git a/tests/depfile/single_crate_default_config/Cargo.lock b/tests/depfile/single_crate_default_config/Cargo.lock new file mode 100644 index 000000000..26d416263 --- /dev/null +++ b/tests/depfile/single_crate_default_config/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "single_crate" +version = "0.1.0" diff --git a/tests/depfile/single_crate_default_config/Cargo.toml b/tests/depfile/single_crate_default_config/Cargo.toml new file mode 100644 index 000000000..66eff1125 --- /dev/null +++ b/tests/depfile/single_crate_default_config/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "single_crate" +version = "0.1.0" +authors = ["cbindgen"] + +[features] +cbindgen = [] diff --git a/tests/depfile/single_crate_default_config/cbindgen.toml b/tests/depfile/single_crate_default_config/cbindgen.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/depfile/single_crate_default_config/expectations/dependencies b/tests/depfile/single_crate_default_config/expectations/dependencies new file mode 100644 index 000000000..693c6f3c0 --- /dev/null +++ b/tests/depfile/single_crate_default_config/expectations/dependencies @@ -0,0 +1,4 @@ +cbindgen.toml +src/alias/mod.rs +src/annotation.rs +src/lib.rs diff --git a/tests/depfile/single_crate_default_config/src/alias/mod.rs b/tests/depfile/single_crate_default_config/src/alias/mod.rs new file mode 100644 index 000000000..6c045d294 --- /dev/null +++ b/tests/depfile/single_crate_default_config/src/alias/mod.rs @@ -0,0 +1,32 @@ +#[repr(C)] +struct Dep { + a: i32, + b: f32, +} + +#[repr(C)] +struct Foo { + a: X, + b: X, + c: Dep, +} + +#[repr(u32)] +enum Status { + Ok, + Err, +} + +type IntFoo = Foo; +type DoubleFoo = Foo; + +type Unit = i32; +type SpecialStatus = Status; + +#[no_mangle] +pub extern "C" fn root( + x: IntFoo, + y: DoubleFoo, + z: Unit, + w: SpecialStatus, +) {} diff --git a/tests/depfile/single_crate_default_config/src/annotation.rs b/tests/depfile/single_crate_default_config/src/annotation.rs new file mode 100644 index 000000000..54cd52662 --- /dev/null +++ b/tests/depfile/single_crate_default_config/src/annotation.rs @@ -0,0 +1,43 @@ +/// cbindgen:derive-lt=true +/// cbindgen:derive-lte=true +/// cbindgen:derive-constructor=true +/// cbindgen:rename-all=GeckoCase +#[repr(C)] +struct A(i32); + +/// cbindgen:field-names=[x, y] +#[repr(C)] +struct B(i32, f32); + +/// cbindgen:trailing-values=[Z, W] +#[repr(u32)] +enum C { + X = 2, + Y, +} + +/// cbindgen:derive-helper-methods=true +#[repr(u8)] +enum F { + Foo(i16), + Bar { x: u8, y: i16 }, + Baz, +} + +/// cbindgen:derive-helper-methods +#[repr(C, u8)] +enum H { + Hello(i16), + There { x: u8, y: i16 }, + Everyone, +} + +#[no_mangle] +pub extern "C" fn root( + x: A, + y: B, + z: C, + f: F, + h: H, +) {} + diff --git a/tests/depfile/single_crate_default_config/src/lib.rs b/tests/depfile/single_crate_default_config/src/lib.rs new file mode 100644 index 000000000..c604cbd38 --- /dev/null +++ b/tests/depfile/single_crate_default_config/src/lib.rs @@ -0,0 +1,2 @@ +mod alias; +mod annotation; diff --git a/tests/profile.rs b/tests/profile.rs index 4e8b489d2..69433a2be 100644 --- a/tests/profile.rs +++ b/tests/profile.rs @@ -40,26 +40,14 @@ fn build_using_bin(extra_args: &[&str]) -> tempfile::TempDir { .tempdir() .expect("Creating tmp dir failed"); - let cbindgen_path = match option_env!("CARGO_BIN_EXE_cbindgen") { - Some(path) => path.into(), - None => { - // We must be on an older version of Rust. - // Guess where cbindgen would be relative to OUT_DIR. - let mut path = PathBuf::from(env!("OUT_DIR")); - path.pop(); - path.pop(); - path.pop(); - path.push("cbindgen"); - path.into_os_string() - } - }; + let cbindgen_path = env!("CARGO_BIN_EXE_cbindgen"); Command::new(cbindgen_path) .current_dir(expand_dep_test_dir) .env("CARGO_EXPAND_TARGET_DIR", tmp_dir.path()) .args(extra_args) .output() - .expect("build should succed"); + .expect("build should succeed"); tmp_dir } diff --git a/tests/tests.rs b/tests/tests.rs index 88a1c400e..ec389d6e4 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2,10 +2,15 @@ extern crate cbindgen; use cbindgen::*; use std::collections::HashSet; +use std::fs::File; +use std::io::Read; use std::path::Path; use std::process::Command; use std::{env, fs, str}; +// Set automatically by cargo for integration tests +static CBINDGEN_PATH: &str = env!("CARGO_BIN_EXE_cbindgen"); + fn style_str(style: Style) -> &'static str { match style { Style::Both => "both", @@ -15,15 +20,30 @@ fn style_str(style: Style) -> &'static str { } fn run_cbindgen( - cbindgen_path: &'static str, path: &Path, - output: &Path, + output: Option<&Path>, language: Language, cpp_compat: bool, style: Option