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;