Squashed 'third_party/autocxx/' content from commit 629e8fa53
git-subtree-dir: third_party/autocxx
git-subtree-split: 629e8fa531a633164c0b52e2a3cab536d4cd0849
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I62a03b0049f49adf029e0204639cdb5468dde1a1
diff --git a/gen/build/Cargo.toml b/gen/build/Cargo.toml
new file mode 100644
index 0000000..2895526
--- /dev/null
+++ b/gen/build/Cargo.toml
@@ -0,0 +1,31 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+# https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+# <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+# option. This file may not be copied, modified, or distributed
+# except according to those terms.
+
+[package]
+name = "autocxx-build"
+version = "0.22.0"
+authors = ["Adrian Taylor <adetaylor@chromium.org>"]
+edition = "2021"
+license = "MIT OR Apache-2.0"
+description = "Safe autogenerated interop between Rust and C++"
+repository = "https://github.com/google/autocxx"
+keywords = ["ffi"]
+categories = ["development-tools::ffi", "api-bindings"]
+
+[features]
+runtime = [ "autocxx-engine/runtime" ]
+static = [ "autocxx-engine/static" ]
+
+[dependencies]
+autocxx-engine = { version="=0.22.0", path="../../engine", features = ["build"] }
+env_logger = "0.9.0"
+indexmap = "1.8"
+
+[dependencies.syn]
+version = "1.0"
+features = [ "full" ]
diff --git a/gen/build/README.md b/gen/build/README.md
new file mode 100644
index 0000000..9b91d4a
--- /dev/null
+++ b/gen/build/README.md
@@ -0,0 +1 @@
+This crate is a [component of autocxx](https://google.github.io/autocxx/).
diff --git a/gen/build/src/lib.rs b/gen/build/src/lib.rs
new file mode 100644
index 0000000..c1df580
--- /dev/null
+++ b/gen/build/src/lib.rs
@@ -0,0 +1,51 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![forbid(unsafe_code)]
+
+use autocxx_engine::{BuilderContext, RebuildDependencyRecorder};
+use indexmap::set::IndexSet as HashSet;
+use std::{io::Write, sync::Mutex};
+
+pub type Builder = autocxx_engine::Builder<'static, CargoBuilderContext>;
+
+#[doc(hidden)]
+pub struct CargoBuilderContext;
+
+impl BuilderContext for CargoBuilderContext {
+ fn setup() {
+ env_logger::builder()
+ .format(|buf, record| writeln!(buf, "cargo:warning=MESSAGE:{}", record.args()))
+ .init();
+ }
+ fn get_dependency_recorder() -> Option<Box<dyn RebuildDependencyRecorder>> {
+ Some(Box::new(CargoRebuildDependencyRecorder::new()))
+ }
+}
+
+#[derive(Debug)]
+struct CargoRebuildDependencyRecorder {
+ printed_already: Mutex<HashSet<String>>,
+}
+
+impl CargoRebuildDependencyRecorder {
+ fn new() -> Self {
+ Self {
+ printed_already: Mutex::new(HashSet::new()),
+ }
+ }
+}
+
+impl RebuildDependencyRecorder for CargoRebuildDependencyRecorder {
+ fn record_header_file_dependency(&self, filename: &str) {
+ let mut already = self.printed_already.lock().unwrap();
+ if already.insert(filename.into()) {
+ println!("cargo:rerun-if-changed={}", filename);
+ }
+ }
+}
diff --git a/gen/cmd/Cargo.toml b/gen/cmd/Cargo.toml
new file mode 100644
index 0000000..744d29a
--- /dev/null
+++ b/gen/cmd/Cargo.toml
@@ -0,0 +1,41 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+# https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+# <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+# option. This file may not be copied, modified, or distributed
+# except according to those terms.
+
+[package]
+name = "autocxx-gen"
+version = "0.22.0"
+authors = ["Adrian Taylor <adetaylor@chromium.org>"]
+edition = "2021"
+license = "MIT OR Apache-2.0"
+description = "Safe autogenerated interop between Rust and C++"
+repository = "https://github.com/google/autocxx"
+keywords = ["ffi"]
+categories = ["development-tools::ffi", "api-bindings"]
+
+[features]
+runtime = [ "autocxx-engine/runtime" ]
+static = [ "autocxx-engine/static" ]
+
+[dependencies]
+autocxx-engine = { version="=0.22.0", path="../../engine" }
+clap = { version = "3.1.2", features = ["cargo"] }
+proc-macro2 = "1.0"
+env_logger = "0.9.0"
+miette = { version="4.3", features=["fancy"]}
+pathdiff = "0.2.1"
+indexmap = "1.8"
+
+[dev-dependencies]
+assert_cmd = "1.0.3"
+tempdir = "0.3.7"
+autocxx-integration-tests = { path = "../../integration-tests", version="=0.22.0" }
+# This is necessary for building the projects created
+# by the trybuild test system...
+autocxx = { path="../.." }
+cxx = "1.0.54"
+itertools = "0.10.3"
\ No newline at end of file
diff --git a/gen/cmd/README.md b/gen/cmd/README.md
new file mode 100644
index 0000000..9b91d4a
--- /dev/null
+++ b/gen/cmd/README.md
@@ -0,0 +1 @@
+This crate is a [component of autocxx](https://google.github.io/autocxx/).
diff --git a/gen/cmd/src/depfile.rs b/gen/cmd/src/depfile.rs
new file mode 100644
index 0000000..5bb7a66
--- /dev/null
+++ b/gen/cmd/src/depfile.rs
@@ -0,0 +1,102 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::{
+ fs::File,
+ io::Write,
+ path::{Path, PathBuf},
+};
+
+/// Type which knows how to write a .d file. All outputs depend on all
+/// dependencies.
+pub(crate) struct Depfile {
+ file: File,
+ outputs: Vec<String>,
+ dependencies: Vec<String>,
+ depfile_dir: PathBuf,
+}
+
+impl Depfile {
+ pub(crate) fn new(depfile: &Path) -> std::io::Result<Self> {
+ let file = File::create(depfile)?;
+ Ok(Self {
+ file,
+ outputs: Vec::new(),
+ dependencies: Vec::new(),
+ depfile_dir: depfile.parent().unwrap().to_path_buf(),
+ })
+ }
+
+ pub(crate) fn add_dependency(&mut self, dependency: &Path) {
+ self.dependencies.push(self.relativize(dependency))
+ }
+
+ pub(crate) fn add_output(&mut self, output: &Path) {
+ self.outputs.push(self.relativize(output))
+ }
+
+ pub(crate) fn write(&mut self) -> std::io::Result<()> {
+ let dependency_list = self.dependencies.join(" \\\n ");
+ for output in &self.outputs {
+ self.file
+ .write_all(format!("{}: {}\n\n", output, dependency_list).as_bytes())?
+ }
+ Ok(())
+ }
+
+ /// Return a string giving a relative path from the depfile.
+ fn relativize(&self, path: &Path) -> String {
+ pathdiff::diff_paths(path, &self.depfile_dir)
+ .expect("Unable to make a relative path from the depfile's directory to the dependency")
+ .to_str()
+ .expect("Unable to represent the file path in a UTF8 encoding")
+ .into()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::{fs::File, io::Read};
+
+ use tempdir::TempDir;
+
+ use super::Depfile;
+
+ #[test]
+ fn test_simple_depfile() {
+ let tmp_dir = TempDir::new("depfile-test").unwrap();
+ let f = tmp_dir.path().join("depfile.d");
+ let mut df = Depfile::new(&f).unwrap();
+ df.add_output(&tmp_dir.path().join("a/b"));
+ df.add_dependency(&tmp_dir.path().join("c/d"));
+ df.add_dependency(&tmp_dir.path().join("e/f"));
+ df.write().unwrap();
+
+ let mut f = File::open(&f).unwrap();
+ let mut contents = String::new();
+ f.read_to_string(&mut contents).unwrap();
+ assert_eq!(contents, "a/b: c/d \\\n e/f\n\n");
+ }
+
+ #[test]
+ fn test_multiple_outputs() {
+ let tmp_dir = TempDir::new("depfile-test").unwrap();
+ let f = tmp_dir.path().join("depfile.d");
+ let mut df = Depfile::new(&f).unwrap();
+ df.add_output(&tmp_dir.path().join("a/b"));
+ df.add_output(&tmp_dir.path().join("z"));
+ df.add_dependency(&tmp_dir.path().join("c/d"));
+ df.add_dependency(&tmp_dir.path().join("e/f"));
+ df.write().unwrap();
+
+ let mut f = File::open(&f).unwrap();
+ let mut contents = String::new();
+ f.read_to_string(&mut contents).unwrap();
+ assert_eq!(contents, "a/b: c/d \\\n e/f\n\nz: c/d \\\n e/f\n\n");
+ }
+}
diff --git a/gen/cmd/src/main.rs b/gen/cmd/src/main.rs
new file mode 100644
index 0000000..20f278b
--- /dev/null
+++ b/gen/cmd/src/main.rs
@@ -0,0 +1,428 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![forbid(unsafe_code)]
+
+mod depfile;
+
+use autocxx_engine::{
+ generate_rs_archive, generate_rs_single, parse_file, AutocxxgenHeaderNamer, CxxgenHeaderNamer,
+ RebuildDependencyRecorder,
+};
+use clap::{crate_authors, crate_version, Arg, ArgGroup, Command};
+use depfile::Depfile;
+use indexmap::IndexSet;
+use miette::IntoDiagnostic;
+use std::cell::RefCell;
+use std::io::{Read, Write};
+use std::path::PathBuf;
+use std::rc::Rc;
+use std::{cell::Cell, fs::File, path::Path};
+
+pub(crate) static BLANK: &str = "// Blank autocxx placeholder";
+
+static LONG_HELP: &str = "
+Command line utility to expand the Rust 'autocxx' include_cpp! directive.
+
+This tool can generate both the C++ and Rust side binding code for
+a Rust file containing an include_cpp! directive.
+
+If you're using cargo, don't use this: use autocxx_build instead,
+which is much easier to include in build.rs build scripts. You'd likely
+use this tool only if you're using some non-Cargo build system. If
+that's you, read on.
+
+This tool has three modes: generate the C++; or generate
+a Rust file which can be included by the autocxx_macro; or generate an archive
+containing multiple Rust files to be expanded by different autocxx macros.
+You may specify multiple modes, or of course, invoke the tool multiple times.
+
+In any mode, you'll need to pass the source Rust file name and the C++
+include path. You may pass multiple Rust files, each of which may contain
+multiple include_cpp! or cxx::bridge macros.
+
+There are three basic ways to use this tool, depending on the flexibility
+of your build system.
+
+Does your build system require fixed output filenames, or can it enumerate
+whatever files are generated?
+
+If it's flexible, then use
+ --gen-rs-include --gen-cpp
+An arbitrary number of .h, .cc and .rs files will be generated, depending
+on how many cxx::bridge and include_cpp macros are encountered and their contents.
+When building the rust code, simply ensure that AUTOCXX_RS or OUT_DIR is set to
+teach rustc where to find these .rs files.
+
+If your build system needs to be told exactly what C++ files are generated,
+additionally use --generate-exact <N> You are then guaranteed to get
+exactly 'n' files as follows:
+ gen<n>.h
+ autocxxgen<n>.h
+ gen<n>.cc
+Some of them may be blank. If the tool finds too many include_cpp or cxx::bridge
+macros to fit within that allowance, the build will fail.
+
+If your build system additionally requires that Rust files have fixed
+filenames, then you should use
+ --gen-rs-archive
+instead of
+ --gen-rs-include
+and you will need to give AUTOCXX_RS_JSON_ARCHIVE when building the Rust code.
+The output filename is named gen.rs.json.
+
+This teaches rustc (and the autocxx macro) that all the different Rust bindings
+for multiple different autocxx macros have been archived into this single file.
+";
+
+fn main() -> miette::Result<()> {
+ let matches = Command::new("autocxx-gen")
+ .version(crate_version!())
+ .author(crate_authors!())
+ .about("Generates bindings files from Rust files that contain include_cpp! macros")
+ .long_about(LONG_HELP)
+ .arg(
+ Arg::new("INPUT")
+ .help("Sets the input .rs files to use")
+ .required(true)
+ .multiple_occurrences(true)
+ )
+ .arg(
+ Arg::new("outdir")
+ .short('o')
+ .long("outdir")
+ .allow_invalid_utf8(true)
+ .value_name("PATH")
+ .help("output directory path")
+ .takes_value(true)
+ .required(true),
+ )
+ .arg(
+ Arg::new("inc")
+ .short('I')
+ .long("inc")
+ .multiple_occurrences(true)
+ .number_of_values(1)
+ .value_name("INCLUDE DIRS")
+ .help("include path")
+ .takes_value(true),
+ )
+ .arg(
+ Arg::new("cpp-extension")
+ .long("cpp-extension")
+ .value_name("EXTENSION")
+ .default_value("cc")
+ .help("C++ filename extension")
+ .takes_value(true),
+ )
+ .arg(
+ Arg::new("gen-cpp")
+ .long("gen-cpp")
+ .help("whether to generate C++ implementation and header files")
+ )
+ .arg(
+ Arg::new("gen-rs-include")
+ .long("gen-rs-include")
+ .help("whether to generate Rust files for inclusion using autocxx_macro (suffix will be .include.rs)")
+ )
+ .arg(
+ Arg::new("gen-rs-archive")
+ .long("gen-rs-archive")
+ .help("whether to generate an archive of multiple sets of Rust bindings for use by autocxx_macro (suffix will be .rs.json)")
+ )
+ .group(ArgGroup::new("mode")
+ .required(true)
+ .multiple(true)
+ .arg("gen-cpp")
+ .arg("gen-rs-include")
+ .arg("gen-rs-archive")
+ )
+ .arg(
+ Arg::new("generate-exact")
+ .long("generate-exact")
+ .value_name("NUM")
+ .help("assume and ensure there are exactly NUM bridge blocks in the file. Only applies for --gen-cpp or --gen-rs-include")
+ .takes_value(true),
+ )
+ .arg(
+ Arg::new("fix-rs-include-name")
+ .long("fix-rs-include-name")
+ .help("Make the name of the .rs file predictable. You must set AUTOCXX_RS_FILE during Rust build time to educate autocxx_macro about your choice.")
+ .requires("gen-rs-include")
+ )
+ .arg(
+ Arg::new("auto-allowlist")
+ .long("auto-allowlist")
+ .help("Dynamically construct allowlist from real uses of APIs.")
+ )
+ .arg(
+ Arg::new("suppress-system-headers")
+ .long("suppress-system-headers")
+ .help("Do not refer to any system headers from generated code. May be useful for minimization.")
+ )
+ .arg(
+ Arg::new("cxx-impl-annotations")
+ .long("cxx-impl-annotations")
+ .value_name("ANNOTATION")
+ .help("prefix for symbols to be exported from C++ bindings, e.g. __attribute__ ((visibility (\"default\")))")
+ .takes_value(true),
+ )
+ .arg(
+ Arg::new("cxx-h-path")
+ .long("cxx-h-path")
+ .value_name("PREFIX")
+ .help("prefix for path to cxx.h (from the cxx crate) within #include statements. Must end in /")
+ .takes_value(true),
+ )
+ .arg(
+ Arg::new("cxxgen-h-path")
+ .long("cxxgen-h-path")
+ .value_name("PREFIX")
+ .help("prefix for path to cxxgen.h (which we generate into the output directory) within #include statements. Must end in /")
+ .takes_value(true),
+ )
+ .arg(
+ Arg::new("depfile")
+ .long("depfile")
+ .value_name("DEPFILE")
+ .help("A .d file to write")
+ .takes_value(true),
+ )
+ .arg(
+ Arg::new("clang-args")
+ .last(true)
+ .multiple_occurrences(true)
+ .help("Extra arguments to pass to Clang"),
+ )
+ .get_matches();
+
+ env_logger::builder().init();
+ let incs = matches
+ .values_of("inc")
+ .unwrap_or_default()
+ .map(PathBuf::from)
+ .collect::<Vec<_>>();
+ let extra_clang_args: Vec<_> = matches
+ .values_of("clang-args")
+ .unwrap_or_default()
+ .collect();
+ let suppress_system_headers = matches.is_present("suppress-system-headers");
+ let desired_number = matches
+ .value_of("generate-exact")
+ .map(|s| s.parse::<usize>().unwrap());
+ let autocxxgen_header_counter = Cell::new(0);
+ let autocxxgen_header_namer = if desired_number.is_some() {
+ AutocxxgenHeaderNamer(Box::new(|_| {
+ let r = name_autocxxgen_h(autocxxgen_header_counter.get());
+ autocxxgen_header_counter.set(autocxxgen_header_counter.get() + 1);
+ r
+ }))
+ } else {
+ Default::default()
+ };
+ let cxxgen_header_counter = Cell::new(0);
+ let cxxgen_header_namer = if desired_number.is_some() {
+ CxxgenHeaderNamer(Box::new(|| {
+ let r = name_cxxgen_h(cxxgen_header_counter.get());
+ cxxgen_header_counter.set(cxxgen_header_counter.get() + 1);
+ r
+ }))
+ } else {
+ Default::default()
+ };
+ let cpp_codegen_options = autocxx_engine::CppCodegenOptions {
+ suppress_system_headers,
+ cxx_impl_annotations: get_option_string("cxx-impl-annotations", &matches),
+ path_to_cxx_h: get_option_string("cxx-h-path", &matches),
+ path_to_cxxgen_h: get_option_string("cxxgen-h-path", &matches),
+ autocxxgen_header_namer,
+ cxxgen_header_namer,
+ };
+ let depfile = match matches.value_of("depfile") {
+ None => None,
+ Some(depfile_path) => {
+ let depfile_path = PathBuf::from(depfile_path);
+ Some(Rc::new(RefCell::new(
+ Depfile::new(&depfile_path).into_diagnostic()?,
+ )))
+ }
+ };
+ let auto_allowlist = matches.is_present("auto-allowlist");
+
+ let mut parsed_files = Vec::new();
+ for input in matches.values_of("INPUT").expect("No INPUT was provided") {
+ // Parse all the .rs files we're asked to process, first.
+ // Spot any fundamental parsing or command line problems before we start
+ // to do the complex processing.
+ let parsed_file = parse_file(input, auto_allowlist)?;
+ parsed_files.push(parsed_file);
+ }
+
+ for parsed_file in parsed_files.iter_mut() {
+ // Now actually handle all the include_cpp directives we found,
+ // which is the complex bit where we interpret all the C+.
+ let dep_recorder: Option<Box<dyn RebuildDependencyRecorder>> = depfile
+ .as_ref()
+ .map(|rc| get_dependency_recorder(rc.clone()));
+ parsed_file.resolve_all(
+ incs.clone(),
+ &extra_clang_args,
+ dep_recorder,
+ &cpp_codegen_options,
+ )?;
+ }
+
+ // Finally start to write the C++ and Rust out.
+ let outdir: PathBuf = matches.value_of_os("outdir").unwrap().into();
+ let mut writer = FileWriter {
+ depfile: &depfile,
+ outdir: &outdir,
+ written: IndexSet::new(),
+ };
+ if matches.is_present("gen-cpp") {
+ let cpp = matches.value_of("cpp-extension").unwrap();
+ let name_cc_file = |counter| format!("gen{}.{}", counter, cpp);
+ let mut counter = 0usize;
+ for include_cxx in parsed_files
+ .iter()
+ .flat_map(|file| file.get_cpp_buildables())
+ {
+ let generations = include_cxx
+ .generate_h_and_cxx(&cpp_codegen_options)
+ .expect("Unable to generate header and C++ code");
+ for pair in generations.0 {
+ let cppname = name_cc_file(counter);
+ writer.write_to_file(cppname, &pair.implementation.unwrap_or_default())?;
+ writer.write_to_file(pair.header_name, &pair.header)?;
+ counter += 1;
+ }
+ }
+ drop(cpp_codegen_options);
+ // Write placeholders to ensure we always make exactly 'n' of each file type.
+ writer.write_placeholders(counter, desired_number, name_cc_file)?;
+ writer.write_placeholders(
+ cxxgen_header_counter.into_inner(),
+ desired_number,
+ name_cxxgen_h,
+ )?;
+ writer.write_placeholders(
+ autocxxgen_header_counter.into_inner(),
+ desired_number,
+ name_autocxxgen_h,
+ )?;
+ }
+ //writer.write_placeholders(header_counter.into_inner(), desired_number, "h")?;
+ if matches.is_present("gen-rs-include") {
+ let rust_buildables = parsed_files
+ .iter()
+ .flat_map(|parsed_file| parsed_file.get_rs_outputs());
+ for (counter, include_cxx) in rust_buildables.enumerate() {
+ let rs_code = generate_rs_single(include_cxx);
+ let fname = if matches.is_present("fix-rs-include-name") {
+ format!("gen{}.include.rs", counter)
+ } else {
+ rs_code.filename
+ };
+ writer.write_to_file(fname, rs_code.code.as_bytes())?;
+ }
+ }
+ if matches.is_present("gen-rs-archive") {
+ let rust_buildables = parsed_files
+ .iter()
+ .flat_map(|parsed_file| parsed_file.get_rs_outputs());
+ let json = generate_rs_archive(rust_buildables);
+ eprintln!("Writing to gen.rs.json in {:?}", outdir);
+ writer.write_to_file("gen.rs.json".into(), json.as_bytes())?;
+ }
+ if let Some(depfile) = depfile {
+ depfile.borrow_mut().write().into_diagnostic()?;
+ }
+ Ok(())
+}
+
+fn name_autocxxgen_h(counter: usize) -> String {
+ format!("autocxxgen{}.h", counter)
+}
+
+fn name_cxxgen_h(counter: usize) -> String {
+ format!("gen{}.h", counter)
+}
+
+fn get_dependency_recorder(depfile: Rc<RefCell<Depfile>>) -> Box<dyn RebuildDependencyRecorder> {
+ Box::new(RecordIntoDepfile(depfile))
+}
+
+fn get_option_string(option: &str, matches: &clap::ArgMatches) -> Option<String> {
+ let cxx_impl_annotations = matches.value_of(option).map(|s| s.to_string());
+ cxx_impl_annotations
+}
+
+struct FileWriter<'a> {
+ depfile: &'a Option<Rc<RefCell<Depfile>>>,
+ outdir: &'a Path,
+ written: IndexSet<String>,
+}
+
+impl<'a> FileWriter<'a> {
+ fn write_placeholders<F: FnOnce(usize) -> String + Copy>(
+ &mut self,
+ mut counter: usize,
+ desired_number: Option<usize>,
+ filename: F,
+ ) -> miette::Result<()> {
+ if let Some(desired_number) = desired_number {
+ if counter > desired_number {
+ return Err(miette::Report::msg("More files were generated than expected. Increase the value passed to --generate-exact or reduce the number of include_cpp! sections."));
+ }
+ while counter < desired_number {
+ let fname = filename(counter);
+ self.write_to_file(fname, BLANK.as_bytes())?;
+ counter += 1;
+ }
+ }
+ Ok(())
+ }
+
+ fn write_to_file(&mut self, filename: String, content: &[u8]) -> miette::Result<()> {
+ let path = self.outdir.join(&filename);
+ if let Some(depfile) = self.depfile {
+ depfile.borrow_mut().add_output(&path);
+ }
+ {
+ let f = File::open(&path);
+ if let Ok(mut f) = f {
+ let mut existing_content = Vec::new();
+ let r = f.read_to_end(&mut existing_content);
+ if r.is_ok() && existing_content == content {
+ return Ok(()); // don't change timestamp on existing file unnecessarily
+ }
+ }
+ }
+ let mut f = File::create(&path).into_diagnostic()?;
+ f.write_all(content).into_diagnostic()?;
+ if self.written.contains(&filename) {
+ return Err(miette::Report::msg(format!("autocxx_gen would write two files entitled '{}' which would have conflicting contents. Consider using --generate-exact.", filename)));
+ }
+ self.written.insert(filename);
+ Ok(())
+ }
+}
+
+struct RecordIntoDepfile(Rc<RefCell<Depfile>>);
+
+impl RebuildDependencyRecorder for RecordIntoDepfile {
+ fn record_header_file_dependency(&self, filename: &str) {
+ self.0.borrow_mut().add_dependency(&PathBuf::from(filename))
+ }
+}
+
+impl std::fmt::Debug for RecordIntoDepfile {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "<depfile>")
+ }
+}
diff --git a/gen/cmd/tests/cmd_test.rs b/gen/cmd/tests/cmd_test.rs
new file mode 100644
index 0000000..d0e671e
--- /dev/null
+++ b/gen/cmd/tests/cmd_test.rs
@@ -0,0 +1,302 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::{convert::TryInto, fs::File, io::Write, path::Path};
+
+use indexmap::map::IndexMap as HashMap;
+
+use assert_cmd::Command;
+use autocxx_integration_tests::{build_from_folder, RsFindMode};
+use itertools::Itertools;
+use tempdir::TempDir;
+
+static MAIN_RS: &str = concat!(
+ include_str!("../../../demo/src/main.rs"),
+ "#[link(name = \"autocxx-demo\")]\nextern \"C\" {}"
+);
+static INPUT_H: &str = include_str!("../../../demo/src/input.h");
+static BLANK: &str = "// Blank autocxx placeholder";
+
+static MAIN2_RS: &str = concat!(
+ include_str!("data/main2.rs"),
+ "#[link(name = \"autocxx-demo\")]\nextern \"C\" {}"
+);
+static DIRECTIVE1_RS: &str = include_str!("data/directive1.rs");
+static DIRECTIVE2_RS: &str = include_str!("data/directive2.rs");
+static INPUT2_H: &str = include_str!("data/input2.h");
+static INPUT3_H: &str = include_str!("data/input3.h");
+
+const KEEP_TEMPDIRS: bool = false;
+
+#[test]
+fn test_help() -> Result<(), Box<dyn std::error::Error>> {
+ let mut cmd = Command::cargo_bin("autocxx-gen")?;
+ cmd.arg("-h").assert().success();
+ Ok(())
+}
+
+enum RsGenMode {
+ Single,
+ Archive,
+}
+
+fn base_test<F>(
+ tmp_dir: &TempDir,
+ rs_gen_mode: RsGenMode,
+ arg_modifier: F,
+) -> Result<(), Box<dyn std::error::Error>>
+where
+ F: FnOnce(&mut Command),
+{
+ let mut standard_files = HashMap::new();
+ standard_files.insert("input.h", INPUT_H.as_bytes());
+ standard_files.insert("main.rs", MAIN_RS.as_bytes());
+ let result = base_test_ex(
+ tmp_dir,
+ rs_gen_mode,
+ arg_modifier,
+ standard_files,
+ vec!["main.rs"],
+ );
+ assert_contentful(tmp_dir, "gen0.cc");
+ result
+}
+
+fn base_test_ex<F>(
+ tmp_dir: &TempDir,
+ rs_gen_mode: RsGenMode,
+ arg_modifier: F,
+ files_to_write: HashMap<&str, &[u8]>,
+ files_to_process: Vec<&str>,
+) -> Result<(), Box<dyn std::error::Error>>
+where
+ F: FnOnce(&mut Command),
+{
+ let demo_code_dir = tmp_dir.path().join("demo");
+ std::fs::create_dir(&demo_code_dir).unwrap();
+ for (filename, content) in files_to_write {
+ write_to_file(&demo_code_dir, filename, content);
+ }
+ let mut cmd = Command::cargo_bin("autocxx-gen")?;
+ arg_modifier(&mut cmd);
+ cmd.arg("--inc")
+ .arg(demo_code_dir.to_str().unwrap())
+ .arg("--outdir")
+ .arg(tmp_dir.path().to_str().unwrap())
+ .arg("--gen-cpp");
+ cmd.arg(match rs_gen_mode {
+ RsGenMode::Single => "--gen-rs-include",
+ RsGenMode::Archive => "--gen-rs-archive",
+ });
+ for file in files_to_process {
+ cmd.arg(demo_code_dir.join(file));
+ }
+ let output = cmd.output();
+ if let Ok(output) = output {
+ eprintln!("Cmd stdout: {:?}", std::str::from_utf8(&output.stdout));
+ eprintln!("Cmd stderr: {:?}", std::str::from_utf8(&output.stderr));
+ }
+ cmd.assert().success();
+ Ok(())
+}
+
+#[test]
+fn test_gen() -> Result<(), Box<dyn std::error::Error>> {
+ let tmp_dir = TempDir::new("example")?;
+ base_test(&tmp_dir, RsGenMode::Single, |_| {})?;
+ File::create(tmp_dir.path().join("cxx.h"))
+ .and_then(|mut cxx_h| cxx_h.write_all(autocxx_engine::HEADER.as_bytes()))?;
+ std::env::set_var("OUT_DIR", tmp_dir.path().to_str().unwrap());
+ let r = build_from_folder(
+ tmp_dir.path(),
+ &tmp_dir.path().join("demo/main.rs"),
+ vec![tmp_dir.path().join("autocxx-ffi-default-gen.rs")],
+ &["gen0.cc"],
+ RsFindMode::AutocxxRs,
+ );
+ if KEEP_TEMPDIRS {
+ println!("Tempdir: {:?}", tmp_dir.into_path().to_str());
+ }
+ r.unwrap();
+ Ok(())
+}
+
+#[test]
+fn test_gen_archive() -> Result<(), Box<dyn std::error::Error>> {
+ let tmp_dir = TempDir::new("example")?;
+ base_test(&tmp_dir, RsGenMode::Archive, |_| {})?;
+ File::create(tmp_dir.path().join("cxx.h"))
+ .and_then(|mut cxx_h| cxx_h.write_all(autocxx_engine::HEADER.as_bytes()))?;
+ let r = build_from_folder(
+ tmp_dir.path(),
+ &tmp_dir.path().join("demo/main.rs"),
+ vec![tmp_dir.path().join("gen.rs.json")],
+ &["gen0.cc"],
+ RsFindMode::AutocxxRsArchive,
+ );
+ if KEEP_TEMPDIRS {
+ println!("Tempdir: {:?}", tmp_dir.into_path().to_str());
+ }
+ r.unwrap();
+ Ok(())
+}
+
+#[test]
+fn test_gen_multiple_in_archive() -> Result<(), Box<dyn std::error::Error>> {
+ let tmp_dir = TempDir::new("example")?;
+
+ let mut files = HashMap::new();
+ files.insert("input2.h", INPUT2_H.as_bytes());
+ files.insert("input3.h", INPUT3_H.as_bytes());
+ files.insert("main.rs", MAIN2_RS.as_bytes());
+ files.insert("directive1.rs", DIRECTIVE1_RS.as_bytes());
+ files.insert("directive2.rs", DIRECTIVE2_RS.as_bytes());
+ base_test_ex(
+ &tmp_dir,
+ RsGenMode::Archive,
+ |cmd| {
+ cmd.arg("--generate-exact").arg("8");
+ },
+ files,
+ vec!["directive1.rs", "directive2.rs"],
+ )?;
+ File::create(tmp_dir.path().join("cxx.h"))
+ .and_then(|mut cxx_h| cxx_h.write_all(autocxx_engine::HEADER.as_bytes()))?;
+ // We've asked to create 8 C++ files, mostly blank. Build 'em all.
+ let cpp_files = (0..7).map(|id| format!("gen{}.cc", id)).collect_vec();
+ let cpp_files = cpp_files.iter().map(|s| s.as_str()).collect_vec();
+ let r = build_from_folder(
+ tmp_dir.path(),
+ &tmp_dir.path().join("demo/main.rs"),
+ vec![tmp_dir.path().join("gen.rs.json")],
+ &cpp_files,
+ RsFindMode::AutocxxRsArchive,
+ );
+ if KEEP_TEMPDIRS {
+ println!("Tempdir: {:?}", tmp_dir.into_path().to_str());
+ }
+ r.unwrap();
+ Ok(())
+}
+
+#[test]
+fn test_include_prefixes() -> Result<(), Box<dyn std::error::Error>> {
+ let tmp_dir = TempDir::new("example")?;
+ base_test(&tmp_dir, RsGenMode::Single, |cmd| {
+ cmd.arg("--cxx-h-path")
+ .arg("foo/")
+ .arg("--cxxgen-h-path")
+ .arg("bar/")
+ .arg("--generate-exact")
+ .arg("3");
+ })?;
+ assert_contains(&tmp_dir, "autocxxgen0.h", "foo/cxx.h");
+ // Currently we don't test cxxgen-h-path because we build the demo code
+ // which doesn't refer to generated cxx header code.
+ Ok(())
+}
+
+#[test]
+fn test_gen_fixed_num() -> Result<(), Box<dyn std::error::Error>> {
+ let tmp_dir = TempDir::new("example")?;
+ let depfile = tmp_dir.path().join("test.d");
+ base_test(&tmp_dir, RsGenMode::Single, |cmd| {
+ cmd.arg("--generate-exact")
+ .arg("2")
+ .arg("--depfile")
+ .arg(depfile);
+ })?;
+ assert_contentful(&tmp_dir, "gen0.cc");
+ assert_contentful(&tmp_dir, "gen0.h");
+ assert_not_contentful(&tmp_dir, "gen1.cc");
+ assert_contentful(&tmp_dir, "autocxxgen0.h");
+ assert_not_contentful(&tmp_dir, "gen1.h");
+ assert_not_contentful(&tmp_dir, "autocxxgen1.h");
+ assert_contentful(&tmp_dir, "autocxx-ffi-default-gen.rs");
+ assert_contentful(&tmp_dir, "test.d");
+ File::create(tmp_dir.path().join("cxx.h"))
+ .and_then(|mut cxx_h| cxx_h.write_all(autocxx_engine::HEADER.as_bytes()))?;
+ let r = build_from_folder(
+ tmp_dir.path(),
+ &tmp_dir.path().join("demo/main.rs"),
+ vec![tmp_dir.path().join("autocxx-ffi-default-gen.rs")],
+ &["gen0.cc"],
+ RsFindMode::AutocxxRs,
+ );
+ if KEEP_TEMPDIRS {
+ println!("Tempdir: {:?}", tmp_dir.into_path().to_str());
+ }
+ r.unwrap();
+ Ok(())
+}
+
+#[test]
+fn test_gen_preprocess() -> Result<(), Box<dyn std::error::Error>> {
+ let tmp_dir = TempDir::new("example")?;
+ let prepro_path = tmp_dir.path().join("preprocessed.h");
+ base_test(&tmp_dir, RsGenMode::Single, |cmd| {
+ cmd.env("AUTOCXX_PREPROCESS", prepro_path.to_str().unwrap());
+ })?;
+ assert_contentful(&tmp_dir, "preprocessed.h");
+ // Check that a random thing from one of the headers in
+ // `ALL_KNOWN_SYSTEM_HEADERS` is included.
+ assert!(std::fs::read_to_string(prepro_path)?.contains("integer_sequence"));
+ Ok(())
+}
+
+#[test]
+fn test_gen_repro() -> Result<(), Box<dyn std::error::Error>> {
+ let tmp_dir = TempDir::new("example")?;
+ let repro_path = tmp_dir.path().join("repro.json");
+ base_test(&tmp_dir, RsGenMode::Single, |cmd| {
+ cmd.env("AUTOCXX_REPRO_CASE", repro_path.to_str().unwrap());
+ })?;
+ assert_contentful(&tmp_dir, "repro.json");
+ // Check that a random thing from one of the headers in
+ // `ALL_KNOWN_SYSTEM_HEADERS` is included.
+ assert!(std::fs::read_to_string(repro_path)?.contains("integer_sequence"));
+ Ok(())
+}
+
+fn write_to_file(dir: &Path, filename: &str, content: &[u8]) {
+ let path = dir.join(filename);
+ let mut f = File::create(&path).expect("Unable to create file");
+ f.write_all(content).expect("Unable to write file");
+}
+
+fn assert_contentful(outdir: &TempDir, fname: &str) {
+ let p = outdir.path().join(fname);
+ if !p.exists() {
+ panic!("File {} didn't exist", p.to_string_lossy());
+ }
+ assert!(
+ p.metadata().unwrap().len() > BLANK.len().try_into().unwrap(),
+ "File {} is empty",
+ fname
+ );
+}
+
+fn assert_not_contentful(outdir: &TempDir, fname: &str) {
+ let p = outdir.path().join(fname);
+ if !p.exists() {
+ panic!("File {} didn't exist", p.to_string_lossy());
+ }
+ assert!(
+ p.metadata().unwrap().len() <= BLANK.len().try_into().unwrap(),
+ "File {} is not empty; it contains {}",
+ fname,
+ std::fs::read_to_string(&p).unwrap_or_default()
+ );
+}
+
+fn assert_contains(outdir: &TempDir, fname: &str, pattern: &str) {
+ let p = outdir.path().join(fname);
+ let content = std::fs::read_to_string(&p).expect(fname);
+ eprintln!("content = {}", content);
+ assert!(content.contains(pattern));
+}
diff --git a/gen/cmd/tests/data/directive1.rs b/gen/cmd/tests/data/directive1.rs
new file mode 100644
index 0000000..792ab06
--- /dev/null
+++ b/gen/cmd/tests/data/directive1.rs
@@ -0,0 +1,16 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use autocxx::prelude::*;
+include_cpp! {
+ #include "input2.h"
+ safety!(unsafe_ffi)
+ generate!("get_hello")
+}
+
+pub use ffi::get_hello;
\ No newline at end of file
diff --git a/gen/cmd/tests/data/directive2.rs b/gen/cmd/tests/data/directive2.rs
new file mode 100644
index 0000000..eaa451b
--- /dev/null
+++ b/gen/cmd/tests/data/directive2.rs
@@ -0,0 +1,16 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use autocxx::prelude::*;
+include_cpp! {
+ #include "input3.h"
+ safety!(unsafe_ffi)
+ generate!("get_goodbye")
+}
+
+pub use ffi::get_goodbye;
diff --git a/gen/cmd/tests/data/input2.h b/gen/cmd/tests/data/input2.h
new file mode 100644
index 0000000..715b8ec
--- /dev/null
+++ b/gen/cmd/tests/data/input2.h
@@ -0,0 +1,15 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#pragma once
+
+#include <string>
+
+inline std::string get_hello() {
+ return "hello!";
+}
\ No newline at end of file
diff --git a/gen/cmd/tests/data/input3.h b/gen/cmd/tests/data/input3.h
new file mode 100644
index 0000000..d5d2c87
--- /dev/null
+++ b/gen/cmd/tests/data/input3.h
@@ -0,0 +1,15 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#pragma once
+
+#include <string>
+
+inline std::string get_goodbye() {
+ return "goodbye!";
+}
\ No newline at end of file
diff --git a/gen/cmd/tests/data/main2.rs b/gen/cmd/tests/data/main2.rs
new file mode 100644
index 0000000..1509843
--- /dev/null
+++ b/gen/cmd/tests/data/main2.rs
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+mod directive1;
+mod directive2;
+
+fn main() {
+ println!("C++ says {} then {}", directive1::get_hello().as_ref().unwrap().to_string_lossy(),
+ directive2::get_goodbye().as_ref().unwrap().to_string_lossy());
+}