From 25132a3690fdaa234beed6e74bc18c655cdb8012 Mon Sep 17 00:00:00 2001 From: Jonathan Schwender Date: Wed, 8 Mar 2023 16:26:24 +0100 Subject: [PATCH] Add --depfile option Add an option to output a depfile for outside build-systems to learn the source file dependencies of the bindings. This can be used by 3rd party build system integrations to only rerun bindgen when necessary. Testing is done via CMake integration tests, since CMake is a 3rd party buildsystem which supports depfiles. --- build.rs | 41 +++++++ src/bindgen/bindings.rs | 44 +++++++ src/bindgen/builder.rs | 4 + src/bindgen/config.rs | 13 ++- src/bindgen/library.rs | 5 + src/bindgen/parser.rs | 5 + src/main.rs | 17 +++ tests/depfile.rs | 109 ++++++++++++++++++ tests/depfile/Readme.md | 11 ++ tests/depfile/cbindgen_test.cmake | 27 +++++ tests/depfile/single_crate/.gitignore | 1 + tests/depfile/single_crate/CMakeLists.txt | 12 ++ tests/depfile/single_crate/Cargo.lock | 7 ++ tests/depfile/single_crate/Cargo.toml | 7 ++ .../single_crate/expectations/dependencies | 3 + tests/depfile/single_crate/src/alias/mod.rs | 32 +++++ tests/depfile/single_crate/src/annotation.rs | 43 +++++++ tests/depfile/single_crate/src/lib.rs | 2 + tests/depfile/single_crate_config/.gitignore | 1 + .../single_crate_config/CMakeLists.txt | 13 +++ tests/depfile/single_crate_config/Cargo.lock | 7 ++ tests/depfile/single_crate_config/Cargo.toml | 7 ++ tests/depfile/single_crate_config/config.toml | 0 .../expectations/dependencies | 4 + .../single_crate_config/src/alias/mod.rs | 32 +++++ .../single_crate_config/src/annotation.rs | 43 +++++++ tests/depfile/single_crate_config/src/lib.rs | 2 + .../single_crate_default_config/.gitignore | 1 + .../CMakeLists.txt | 12 ++ .../single_crate_default_config/Cargo.lock | 7 ++ .../single_crate_default_config/Cargo.toml | 7 ++ .../single_crate_default_config/cbindgen.toml | 0 .../expectations/dependencies | 4 + .../src/alias/mod.rs | 32 +++++ .../src/annotation.rs | 43 +++++++ .../single_crate_default_config/src/lib.rs | 2 + tests/tests.rs | 83 ++++++++++++- 37 files changed, 674 insertions(+), 9 deletions(-) create mode 100644 tests/depfile.rs create mode 100644 tests/depfile/Readme.md create mode 100644 tests/depfile/cbindgen_test.cmake create mode 100644 tests/depfile/single_crate/.gitignore create mode 100644 tests/depfile/single_crate/CMakeLists.txt create mode 100644 tests/depfile/single_crate/Cargo.lock create mode 100644 tests/depfile/single_crate/Cargo.toml create mode 100644 tests/depfile/single_crate/expectations/dependencies create mode 100644 tests/depfile/single_crate/src/alias/mod.rs create mode 100644 tests/depfile/single_crate/src/annotation.rs create mode 100644 tests/depfile/single_crate/src/lib.rs create mode 100644 tests/depfile/single_crate_config/.gitignore create mode 100644 tests/depfile/single_crate_config/CMakeLists.txt create mode 100644 tests/depfile/single_crate_config/Cargo.lock create mode 100644 tests/depfile/single_crate_config/Cargo.toml create mode 100644 tests/depfile/single_crate_config/config.toml create mode 100644 tests/depfile/single_crate_config/expectations/dependencies create mode 100644 tests/depfile/single_crate_config/src/alias/mod.rs create mode 100644 tests/depfile/single_crate_config/src/annotation.rs create mode 100644 tests/depfile/single_crate_config/src/lib.rs create mode 100644 tests/depfile/single_crate_default_config/.gitignore create mode 100644 tests/depfile/single_crate_default_config/CMakeLists.txt create mode 100644 tests/depfile/single_crate_default_config/Cargo.lock create mode 100644 tests/depfile/single_crate_default_config/Cargo.toml create mode 100644 tests/depfile/single_crate_default_config/cbindgen.toml create mode 100644 tests/depfile/single_crate_default_config/expectations/dependencies create mode 100644 tests/depfile/single_crate_default_config/src/alias/mod.rs create mode 100644 tests/depfile/single_crate_default_config/src/annotation.rs create mode 100644 tests/depfile/single_crate_default_config/src/lib.rs diff --git a/build.rs b/build.rs index e8257068d..8390b5f62 100644 --- a/build.rs +++ b/build.rs @@ -51,6 +51,47 @@ fn generate_tests() { 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(), + ) + .unwrap(); + } + + dst.flush().unwrap(); +} + 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/tests.rs b/tests/tests.rs index a027a295d..ec389d6e4 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2,6 +2,8 @@ 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}; @@ -19,13 +21,29 @@ fn style_str(style: Style) -> &'static str { fn run_cbindgen( path: &Path, - output: &Path, + output: Option<&Path>, language: Language, cpp_compat: bool, style: Option