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/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>")
+    }
+}