Accept Crubit features on the command line, and thread them through to the bindings generator. PiperOrigin-RevId: 665967698 Change-Id: I45453129bbb717717885504496d0390d5bcd75c7
diff --git a/cc_bindings_from_rs/BUILD b/cc_bindings_from_rs/BUILD index d94ddfa..3e8c7c0 100644 --- a/cc_bindings_from_rs/BUILD +++ b/cc_bindings_from_rs/BUILD
@@ -23,8 +23,10 @@ ":toposort", "//common:arc_anyhow", "//common:code_gen_utils", + "//common:crubit_feature", "//common:error_report", "//common:memoized", + "@crate_index//:flagset", "@crate_index//:itertools", "@crate_index//:proc-macro2", "@crate_index//:quote", @@ -58,9 +60,11 @@ ":run_compiler", "//common:arc_anyhow", "//common:code_gen_utils", + "//common:crubit_feature", "//common:error_report", "//common:token_stream_printer", "@crate_index//:clap", + "@crate_index//:flagset", "@crate_index//:itertools", ], ) @@ -95,8 +99,10 @@ ], rustc_flags = ["-Zallow-features=rustc_private"], deps = [ + "//common:crubit_feature", "@crate_index//:anyhow", "@crate_index//:clap", + "@crate_index//:flagset", ], )
diff --git a/cc_bindings_from_rs/bindings.rs b/cc_bindings_from_rs/bindings.rs index a0da3b3..963c109 100644 --- a/cc_bindings_from_rs/bindings.rs +++ b/cc_bindings_from_rs/bindings.rs
@@ -67,6 +67,12 @@ #[input] fn crate_name_to_include_paths(&self) -> Rc<HashMap<Rc<str>, Vec<CcInclude>>>; + /// A map from a crate name to the features enabled on that crate. + // TODO(b/271857814): A crate name might not be globally unique - the key needs to also cover + // a "hash" of the crate version and compilation flags. + #[input] + fn crate_name_to_features(&self) -> Rc<HashMap<Rc<str>, flagset::FlagSet<crubit_feature::CrubitFeature>>>; + /// Error collector for generating reports of errors encountered during the generation of bindings. #[input] fn errors(&self) -> Rc<dyn ErrorReporting>; @@ -74,10 +80,6 @@ #[input] fn no_thunk_name_mangling(&self) -> bool; - // TODO(b/262878759): Provide a set of enabled/disabled Crubit features. - #[input] - fn _features(&self) -> (); - fn support_header(&self, suffix: &'tcx str) -> CcInclude; fn repr_attrs(&self, did: DefId) -> Rc<[rustc_attr::ReprAttr]>; @@ -9262,9 +9264,9 @@ tcx, /* crubit_support_path_format= */ "<crubit/support/for/tests/{header}>".into(), /* crate_name_to_include_paths= */ Default::default(), + /* crate_name_to_features= */ Default::default(), /* errors = */ Rc::new(IgnoreErrors), /* no_thunk_name_mangling= */ false, - /* _features= */ (), ) }
diff --git a/cc_bindings_from_rs/cc_bindings_from_rs.rs b/cc_bindings_from_rs/cc_bindings_from_rs.rs index 30eb716..82598d6 100644 --- a/cc_bindings_from_rs/cc_bindings_from_rs.rs +++ b/cc_bindings_from_rs/cc_bindings_from_rs.rs
@@ -42,13 +42,21 @@ paths.push(CcInclude::user_header(include_path.as_str().into())); } + let mut crate_name_to_features = + <HashMap<Rc<str>, flagset::FlagSet<crubit_feature::CrubitFeature>>>::new(); + for (crate_name, features) in &cmdline.crate_features { + let accumulated_features = + crate_name_to_features.entry(crate_name.as_str().into()).or_default(); + *accumulated_features |= *features + } + Database::new( tcx, crubit_support_path_format, crate_name_to_include_paths.into(), + crate_name_to_features.into(), errors, cmdline.no_thunk_name_mangling, - /* _features= */ (), ) }
diff --git a/cc_bindings_from_rs/cmdline.rs b/cc_bindings_from_rs/cmdline.rs index 4cbd656..44995c5 100644 --- a/cc_bindings_from_rs/cmdline.rs +++ b/cc_bindings_from_rs/cmdline.rs
@@ -48,6 +48,16 @@ // a "hash" of the crate version and compilation flags. pub crate_headers: Vec<(String, String)>, + /// Feature flags enabled for a given crate. Keys are crate names, and + /// values are feature flags. All crates must have their features fully + /// specified, and consistently across rustc invocations, or the + /// behavior is undefined. + /// + /// Example: "--crate-feature=foo=experimental". + #[clap(long = "crate-feature", value_parser = parse_crate_feature, + value_name = "CRATE_NAME=CRUBIT_FEATURE")] + pub crate_features: Vec<(String, flagset::FlagSet<crubit_feature::CrubitFeature>)>, + /// Path to a rustfmt executable that will be used to format the /// Rust source files generated by the tool. #[clap(long, value_parser, value_name = "FILE")] @@ -129,6 +139,25 @@ Ok((crate_name.to_string(), include.to_string())) } +/// Parse cmdline arguments of the following form:`"crate_name=crubit_feature"`. +fn parse_crate_feature( + s: &str, +) -> Result<(String, flagset::FlagSet<crubit_feature::CrubitFeature>)> { + let Some(pos) = s.find('=') else { + bail!("Expected KEY=VALUE syntax but no `=` found in `{s}`"); + }; + + let crate_name = &s[..pos]; + ensure!(!crate_name.is_empty(), "Empty crate names are invalid"); + + let feature_name = &s[(pos + 1)..]; + let Some(features) = crubit_feature::named_features(feature_name.as_bytes()) else { + bail!("Invalid Crubit feature name: {feature_name:?}"); + }; + + Ok((crate_name.to_string(), features)) +} + #[cfg(test)] mod tests { use super::*; @@ -222,29 +251,46 @@ Usage: cc_bindings_from_rs_unittest_executable [OPTIONS] --h-out <FILE> --rs-out <FILE> --crubit-support-path-format <STRING> --clang-format-exe-path <FILE> --rustfmt-exe-path <FILE> [-- <RUSTC_ARGS>...] Arguments: - [RUSTC_ARGS]... Command line arguments of the Rust compiler + [RUSTC_ARGS]... + Command line arguments of the Rust compiler Options: --h-out <FILE> Output path for C++ header file with bindings + --rs-out <FILE> Output path for Rust implementation of the bindings + --crubit-support-path-format <STRING> This is the format to `#include` Crubit C++ support library headers, using `{header}` as the placeholder. Example: `<crubit/support/{header}>` will produce `#include <crubit/support/hdr.h>` + --clang-format-exe-path <FILE> Path to a clang-format executable that will be used to format the C++ header files generated by the tool + --crate-header <CRATE_NAME=INCLUDE_PATH> - Include paths of bindings for dependency crates, generated by previous invocations of Crubit. Keys are crate names, and values are include paths. Example: "--crate-header=foo=some/path/foo_cc_api.h" [aliases: bindings-from-dependency] + Include paths of bindings for dependency crates, generated by previous invocations of Crubit. Keys are crate names, and values are include paths. Example: "--crate-header=foo=some/path/foo_cc_api.h" + + [aliases: bindings-from-dependency] + + --crate-feature <CRATE_NAME=CRUBIT_FEATURE> + Feature flags enabled for a given crate. Keys are crate names, and values are feature flags. All crates must have their features fully specified, and consistently across rustc invocations, or the behavior is undefined. + + Example: "--crate-feature=foo=experimental". + --rustfmt-exe-path <FILE> Path to a rustfmt executable that will be used to format the Rust source files generated by the tool + --rustfmt-config-path <FILE> Path to a rustfmt.toml file that should replace the default formatting of the .rs files generated by the tool + --error-report-out <FILE> Path to the error reporting output file + --no-thunk-name-mangling This is for golden tests only. Using this in production may cause undefined behavior + -h, --help - Print help + Print help (see a summary with '-h') "#; let actual_msg = clap_err.to_string(); assert_eq!( @@ -338,6 +384,73 @@ } #[test] + fn test_crate_features_as_multiple_separate_cmdline_args() { + let cmdline = new_cmdline([ + "--h-out=foo.h", + "--rs-out=foo_impl.rs", + "--crubit-support-path-format=<crubit/support/{header}>", + "--clang-format-exe-path=clang-format.exe", + "--rustfmt-exe-path=rustfmt.exe", + "--crate-feature=dep1=supported", + "--crate-feature=dep1=experimental", + "--crate-feature=dep2=experimental", + ]) + .unwrap(); + + assert_eq!(3, cmdline.crate_features.len()); + assert_eq!("dep1", cmdline.crate_features[0].0); + assert_eq!( + flagset::FlagSet::<crubit_feature::CrubitFeature>::from( + crubit_feature::CrubitFeature::Supported + ), + cmdline.crate_features[0].1 + ); + assert_eq!("dep1", cmdline.crate_features[1].0); + assert_eq!( + flagset::FlagSet::<crubit_feature::CrubitFeature>::from( + crubit_feature::CrubitFeature::Experimental + ), + cmdline.crate_features[1].1 + ); + assert_eq!("dep2", cmdline.crate_features[2].0); + assert_eq!( + flagset::FlagSet::<crubit_feature::CrubitFeature>::from( + crubit_feature::CrubitFeature::Experimental + ), + cmdline.crate_features[2].1 + ); + } + + #[test] + fn test_parse_crate_feature() { + assert_eq!( + parse_crate_feature("foo=supported").unwrap(), + ( + "foo".into(), + flagset::FlagSet::<crubit_feature::CrubitFeature>::from( + crubit_feature::CrubitFeature::Supported + ) + ), + ); + assert_eq!( + parse_crate_feature("").unwrap_err().to_string(), + "Expected KEY=VALUE syntax but no `=` found in ``", + ); + assert_eq!( + parse_crate_feature("no-equal-char").unwrap_err().to_string(), + "Expected KEY=VALUE syntax but no `=` found in `no-equal-char`", + ); + assert_eq!( + parse_crate_feature("=bar").unwrap_err().to_string(), + "Empty crate names are invalid", + ); + assert_eq!( + parse_crate_feature("foo=").unwrap_err().to_string(), + "Invalid Crubit feature name: \"\"", + ); + } + + #[test] fn test_crubit_support_path_format_arg_happy_path() { let cmdline = new_cmdline([ "--h-out=foo.h",
diff --git a/common/crubit_feature.rs b/common/crubit_feature.rs index c6a78d4..e259527 100644 --- a/common/crubit_feature.rs +++ b/common/crubit_feature.rs
@@ -33,6 +33,17 @@ } } +/// Returns the set of features named by this short name. +pub fn named_features(name: &[u8]) -> Option<flagset::FlagSet<CrubitFeature>> { + let features = match name { + b"all" => flagset::FlagSet::<CrubitFeature>::full(), + b"supported" => CrubitFeature::Supported.into(), + b"experimental" => CrubitFeature::Experimental.into(), + _ => return None, + }; + Some(features) +} + /// A newtype around a single named feature flagset, so that it can be /// deserialized from a string instead of an integer. #[derive(Debug, Default, PartialEq, Eq, Clone)] @@ -60,16 +71,11 @@ where E: serde::de::Error, { - let features = match bytes { - b"all" => flagset::FlagSet::<CrubitFeature>::full(), - b"supported" => CrubitFeature::Supported.into(), - b"experimental" => CrubitFeature::Experimental.into(), - other => { - return Err(E::custom(format!( - "Unexpected Crubit feature: {:?}", - String::from_utf8_lossy(other) - ))); - } + let Some(features) = named_features(bytes) else { + return Err(E::custom(format!( + "Unexpected Crubit feature: {:?}", + String::from_utf8_lossy(bytes) + ))); }; Ok(SerializedCrubitFeature(features))