Minimal `cc_bindings_from_rs` scaffolding.
PiperOrigin-RevId: 475538943
diff --git a/cc_bindings_from_rs/Cargo.toml b/cc_bindings_from_rs/Cargo.toml
new file mode 100644
index 0000000..0956b72
--- /dev/null
+++ b/cc_bindings_from_rs/Cargo.toml
@@ -0,0 +1,21 @@
+# Part of the Crubit project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+# TODO(lukasza): Switch to Bazel. (Having `cargo` support in the long-term
+# would be nice, but in the short-term we expect to use only Bazel during
+# development. Therefore any `cargo` support would probably rot and get stale
+# quickly.)
+
+[package]
+name = "cc_bindings_from_rs"
+version = "0.1.0"
+edition = "2021"
+
+[[bin]]
+name = "cc_bindings_from_rs"
+path = "main.rs"
+
+[dependencies]
+itertools = "0.10.4"
+lazy_static = "1.4.0"
diff --git a/cc_bindings_from_rs/README.md b/cc_bindings_from_rs/README.md
new file mode 100644
index 0000000..cfda157
--- /dev/null
+++ b/cc_bindings_from_rs/README.md
@@ -0,0 +1,35 @@
+# cc_bindings_from_rs
+
+Disclaimer: This project is experimental, under heavy development, and should
+not be used yet.
+
+
+## Invoking as a standalone executable
+
+Most `rustc` cmdline parameters should be supported (e.g. `--crate-type`).
+
+Example:
+
+```
+# Set CARGO_TARGET_DIR to avoid generating build artifacts inside of the source
+# tree (i.e. generate them elsewhere - where `hg status` cannot see them).
+$ export CARGO_TARGET_DIR=$HOME/scratch/cargo-target
+
+$ cat $HOME/scratch/test.rs
+pub fn public_function() {
+ private_function()
+}
+
+fn private_function() {}
+
+$ cargo run -- $HOME/scratch/test.rs --crate-type=lib --sysroot `rustc --print sysroot`
+...
+CRATE NAME: test
+EXPORTED FN: public_function
+```
+
+
+## Contributing
+
+See
+[rs_bindings_from_cc/README.md](../rs_bindings_from_cc/README.md#contributing).
diff --git a/cc_bindings_from_rs/lib.rs b/cc_bindings_from_rs/lib.rs
new file mode 100644
index 0000000..2de8a9d
--- /dev/null
+++ b/cc_bindings_from_rs/lib.rs
@@ -0,0 +1,138 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+use rustc_hir::{Item, ItemKind, Node};
+use rustc_interface::Queries;
+use rustc_middle::middle::exported_symbols::ExportedSymbol;
+use rustc_middle::ty::TyCtxt;
+use rustc_span::def_id::LOCAL_CRATE;
+
+// TODO(lukasza): Replace `get_names_of_exported_fns` with something that can
+// generate C++ bindings.
+pub fn get_names_of_exported_fns(tcx: TyCtxt) -> impl Iterator<Item = String> + '_ {
+ tcx.exported_symbols(LOCAL_CRATE).iter().filter_map(move |(symbol, _)| match symbol {
+ ExportedSymbol::NonGeneric(def_id) => {
+ match tcx.hir().find_by_def_id(def_id.expect_local()).unwrap() {
+ Node::Item(Item { kind: ItemKind::Fn { .. }, .. }) => {
+ Some(tcx.def_path_str(*def_id))
+ }
+ _ => None,
+ }
+ }
+ ExportedSymbol::Generic(..) | ExportedSymbol::DropGlue(_) | ExportedSymbol::NoDefId(_) => {
+ None
+ }
+ })
+}
+
+/// Helper (used by `main` and `test::run_compiler`) for invokind functions
+/// operating on `TyCtxt`.
+pub fn enter_tcx<'tcx, F, T>(
+ queries: &'tcx Queries<'tcx>,
+ f: F,
+) -> rustc_interface::interface::Result<T>
+where
+ F: FnOnce(rustc_middle::ty::TyCtxt<'tcx>) -> T + Send,
+ T: Send,
+{
+ let query_context = queries.global_ctxt()?;
+ Ok(query_context.peek_mut().enter(f))
+}
+
+#[cfg(test)]
+mod tests {
+ use itertools::Itertools;
+
+ #[test]
+ fn test_get_names_of_exported_fns_public_vs_private() {
+ let test_src = r#"
+ pub fn public_function() {
+ private_function()
+ }
+
+ fn private_function() {}
+ "#;
+ let exported_functions = get_names_of_exported_fns(test_src);
+ assert_eq!(1, exported_functions.len());
+ assert_eq!("public_function", exported_functions[0]);
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_panic_when_syntax_errors_in_test_inputs() {
+ get_names_of_exported_fns("syntax error here");
+ }
+
+ fn get_names_of_exported_fns(source: &str) -> Vec<String> {
+ run_compiler(source, |tcx| super::get_names_of_exported_fns(tcx).collect_vec())
+ }
+
+ /// Compiles Rust `source` then calls `f` on the `TyCtxt` representation
+ /// of the compiled `source`.
+ fn run_compiler<F, T>(source: impl Into<String>, f: F) -> T
+ where
+ F: for<'tcx> FnOnce(rustc_middle::ty::TyCtxt<'tcx>) -> T + Send,
+ T: Send,
+ {
+ use lazy_static::lazy_static;
+ use rustc_session::config::{CrateType, Input, Options, OutputType, OutputTypes};
+ use std::path::PathBuf;
+
+ // TODO(lukasza): This probably won't work in Bazel...
+ lazy_static! {
+ static ref RUSTC_SYSROOT: String = {
+ let output = std::process::Command::new("rustc")
+ .arg("--print=sysroot")
+ .current_dir(".")
+ .output()
+ .expect("For now we depend on `rustc` invocation to succeed... sorry...");
+ std::str::from_utf8(&output.stdout)
+ .expect("Only UTF-8 compatible rustc sysroot is supported... sorry...")
+ .trim()
+ .into()
+ };
+ }
+
+ const TEST_FILENAME: &str = "crubit_unittests.rs";
+
+ // Setting `output_types` that will trigger code gen - otherwise some parts of
+ // the analysis will be missing (e.g. `tcx.exported_symbols()`).
+ // The choice of `Bitcode` is somewhat arbitrary (e.g. `Assembly`,
+ // `Mir`, etc. would also trigger code gen).
+ let output_types = OutputTypes::new(&[(OutputType::Bitcode, None /* PathBuf */)]);
+
+ let opts = Options {
+ crate_types: vec![CrateType::Rlib], // Test inputs simulate library crates.
+ maybe_sysroot: Some(PathBuf::from(RUSTC_SYSROOT.clone())),
+ output_types,
+ ..Default::default()
+ };
+
+ let config = rustc_interface::interface::Config {
+ opts,
+ crate_cfg: Default::default(),
+ crate_check_cfg: Default::default(),
+ input: Input::Str {
+ name: rustc_span::FileName::Custom(TEST_FILENAME.to_string()),
+ input: source.into(),
+ },
+ input_path: None,
+ output_file: None,
+ output_dir: None,
+ file_loader: None,
+ diagnostic_output: rustc_session::DiagnosticOutput::Default,
+ lint_caps: Default::default(),
+ parse_sess_created: None,
+ register_lints: None,
+ override_queries: None,
+ make_codegen_backend: None,
+ registry: rustc_errors::registry::Registry::new(rustc_error_codes::DIAGNOSTICS),
+ };
+
+ rustc_interface::interface::run_compiler(config, |compiler| {
+ compiler.enter(|queries| super::enter_tcx(queries, f))
+ })
+ .expect("Test inputs shouldn't cause compilation errors.")
+ }
+}
diff --git a/cc_bindings_from_rs/main.rs b/cc_bindings_from_rs/main.rs
new file mode 100644
index 0000000..990dba4
--- /dev/null
+++ b/cc_bindings_from_rs/main.rs
@@ -0,0 +1,72 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#![feature(rustc_private)]
+#![deny(rustc::internal)]
+
+// TODO(lukasza): Remove the `extern crate` declarations - they shouldn't be
+// needed once we switch to Bazel.
+extern crate rustc_driver;
+extern crate rustc_error_codes;
+extern crate rustc_errors;
+extern crate rustc_hir;
+extern crate rustc_interface;
+extern crate rustc_middle;
+extern crate rustc_session;
+extern crate rustc_span;
+
+use itertools::Itertools;
+use rustc_interface::interface::Compiler;
+use rustc_interface::Queries;
+
+#[derive(Default)]
+struct CompilerCallbacks {}
+
+impl rustc_driver::Callbacks for CompilerCallbacks {
+ fn after_analysis<'tcx>(
+ &mut self,
+ _compiler: &Compiler,
+ queries: &'tcx Queries<'tcx>,
+ ) -> rustc_driver::Compilation {
+ let rustc_result = lib::enter_tcx(queries, |tcx| {
+ // TODO(lukasza): Replace this with actually generating C++ bindings.
+ for fn_name in lib::get_names_of_exported_fns(tcx) {
+ println!("EXPORTED FN: {}", fn_name);
+ }
+ });
+
+ // Expecting no rustc errors here, because `after_analysis` is only called by
+ // `rustc_driver` if earlier compiler analysis was successful (which as the
+ // *last* compilation phase presumably covers *all* errors).
+ rustc_result.expect("Expecting no compile errors inside `after_analysis` callback.");
+
+ rustc_driver::Compilation::Stop
+ }
+}
+
+fn main() {
+ rustc_driver::init_env_logger("CRUBIT_LOG");
+
+ // TODO: Investigate if we should install a signal handler here. See also how
+ // compiler/rustc_driver/src/lib.rs calls `signal_handler::install()`.
+
+ rustc_driver::install_ice_hook();
+
+ // `std::env::args()` will panic if any of the cmdline arguments are not valid
+ // Unicode. This seems okay.
+ let args = std::env::args().collect_vec();
+
+ // Rust compiler unwinds with a special sentinel value to abort compilation on
+ // fatal errors. We use `catch_with_exit_code` to 1) catch such panics and
+ // translate them into an exit code, and 2) resume and propagate other
+ // panics.
+ let exit_code = rustc_driver::catch_with_exit_code(|| {
+ let mut callbacks = CompilerCallbacks::default();
+ rustc_driver::RunCompiler::new(&args, &mut callbacks).run()
+ });
+ std::process::exit(exit_code);
+}
+
+// TODO(lukasza): Make `lib` a separate crate (once we move to Bazel).
+mod lib;