Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame^] | 1 | // Copyright 2020 Google LLC |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 4 | // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 5 | // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
| 6 | // option. This file may not be copied, modified, or distributed |
| 7 | // except according to those terms. |
| 8 | |
| 9 | #![forbid(unsafe_code)] |
| 10 | |
| 11 | use std::{ |
| 12 | borrow::Cow, |
| 13 | fs::File, |
| 14 | io::Write, |
| 15 | os::unix::prelude::PermissionsExt, |
| 16 | path::{Path, PathBuf}, |
| 17 | }; |
| 18 | |
| 19 | use autocxx_engine::{get_clang_path, make_clang_args, preprocess}; |
| 20 | use autocxx_parser::IncludeCppConfig; |
| 21 | use clap::{crate_authors, crate_version, Arg, ArgMatches, Command}; |
| 22 | use indexmap::IndexSet; |
| 23 | use indoc::indoc; |
| 24 | use itertools::Itertools; |
| 25 | use quote::ToTokens; |
| 26 | use regex::Regex; |
| 27 | use tempfile::TempDir; |
| 28 | |
| 29 | static LONG_HELP: &str = indoc! {" |
| 30 | Command line utility to minimize autocxx bug cases. |
| 31 | |
| 32 | This is a wrapper for creduce. |
| 33 | |
| 34 | Example command-line: |
| 35 | autocxx-reduce file -I my-inc-dir -h my-header -d 'generate!(\"MyClass\")' -k -- --n 64 |
| 36 | "}; |
| 37 | |
| 38 | fn main() { |
| 39 | let matches = Command::new("autocxx-reduce") |
| 40 | .version(crate_version!()) |
| 41 | .author(crate_authors!()) |
| 42 | .about("Reduce a C++ test case") |
| 43 | .long_about(LONG_HELP) |
| 44 | .subcommand(Command::new("file") |
| 45 | .about("reduce a header file") |
| 46 | |
| 47 | .arg( |
| 48 | Arg::new("inc") |
| 49 | .short('I') |
| 50 | .long("inc") |
| 51 | .multiple_occurrences(true) |
| 52 | .number_of_values(1) |
| 53 | .value_name("INCLUDE DIRS") |
| 54 | .help("include path") |
| 55 | .takes_value(true), |
| 56 | ) |
| 57 | .arg( |
| 58 | Arg::new("define") |
| 59 | .short('D') |
| 60 | .long("define") |
| 61 | .multiple_occurrences(true) |
| 62 | .number_of_values(1) |
| 63 | .value_name("DEFINE") |
| 64 | .help("macro definition") |
| 65 | .takes_value(true), |
| 66 | ) |
| 67 | .arg( |
| 68 | Arg::new("header") |
| 69 | .long("header") |
| 70 | .multiple_occurrences(true) |
| 71 | .number_of_values(1) |
| 72 | .required(true) |
| 73 | .value_name("HEADER") |
| 74 | .help("header file name") |
| 75 | .takes_value(true), |
| 76 | ) |
| 77 | |
| 78 | .arg( |
| 79 | Arg::new("directive") |
| 80 | .short('d') |
| 81 | .long("directive") |
| 82 | .multiple_occurrences(true) |
| 83 | .number_of_values(1) |
| 84 | .value_name("DIRECTIVE") |
| 85 | .help("directives to put within include_cpp!") |
| 86 | .takes_value(true), |
| 87 | ) |
| 88 | ) |
| 89 | .subcommand(Command::new("repro") |
| 90 | .about("reduce a repro case JSON file") |
| 91 | .arg( |
| 92 | Arg::new("repro") |
| 93 | .short('r') |
| 94 | .long("repro") |
| 95 | .required(true) |
| 96 | .value_name("REPRODUCTION CASE JSON") |
| 97 | .help("reproduction case JSON file name") |
| 98 | .takes_value(true), |
| 99 | ) |
| 100 | .arg( |
| 101 | Arg::new("header") |
| 102 | .long("header") |
| 103 | .multiple_occurrences(true) |
| 104 | .number_of_values(1) |
| 105 | .value_name("HEADER") |
| 106 | .help("header file name; specify to resume a part-completed run") |
| 107 | .takes_value(true), |
| 108 | ) |
| 109 | ) |
| 110 | .arg( |
| 111 | Arg::new("problem") |
| 112 | .short('p') |
| 113 | .long("problem") |
| 114 | .required(true) |
| 115 | .value_name("PROBLEM") |
| 116 | .help("problem string we're looking for... may be in logs, or in generated C++, or generated .rs") |
| 117 | .takes_value(true), |
| 118 | ) |
| 119 | .arg( |
| 120 | Arg::new("creduce") |
| 121 | .long("creduce") |
| 122 | .value_name("PATH") |
| 123 | .help("creduce binary location") |
| 124 | .default_value("creduce") |
| 125 | .takes_value(true), |
| 126 | ) |
| 127 | .arg( |
| 128 | Arg::new("output") |
| 129 | .short('o') |
| 130 | .long("output") |
| 131 | .value_name("OUTPUT") |
| 132 | .help("where to write minimized output") |
| 133 | .takes_value(true), |
| 134 | ) |
| 135 | .arg( |
| 136 | Arg::new("gen-cmd") |
| 137 | .short('g') |
| 138 | .long("gen-cmd") |
| 139 | .value_name("GEN-CMD") |
| 140 | .help("where to find autocxx-gen") |
| 141 | .takes_value(true), |
| 142 | ) |
| 143 | .arg( |
| 144 | Arg::new("keep") |
| 145 | .short('k') |
| 146 | .long("keep-dir") |
| 147 | .help("keep the temporary directory for debugging purposes"), |
| 148 | ) |
| 149 | .arg( |
| 150 | Arg::new("clang-args") |
| 151 | .short('c') |
| 152 | .long("clang-arg") |
| 153 | .multiple_occurrences(true) |
| 154 | .value_name("CLANG_ARG") |
| 155 | .help("Extra arguments to pass to Clang"), |
| 156 | ) |
| 157 | .arg( |
| 158 | Arg::new("creduce-args") |
| 159 | .long("creduce-arg") |
| 160 | .multiple_occurrences(true) |
| 161 | .value_name("CREDUCE_ARG") |
| 162 | .help("Extra arguments to pass to Clang"), |
| 163 | ) |
| 164 | .arg( |
| 165 | Arg::new("no-precompile") |
| 166 | .long("no-precompile") |
| 167 | .help("Do not precompile the C++ header before passing to autocxxgen"), |
| 168 | ) |
| 169 | .arg( |
| 170 | Arg::new("no-postcompile") |
| 171 | .long("no-postcompile") |
| 172 | .help("Do not post-compile the C++ generated by autocxxgen"), |
| 173 | ) |
| 174 | .arg( |
| 175 | Arg::new("suppress-cxx-inclusions") |
| 176 | .long("suppress-cxx-inclusions") |
| 177 | .takes_value(true) |
| 178 | .possible_value("yes") |
| 179 | .possible_value("no") |
| 180 | .possible_value("auto") |
| 181 | .default_value("auto") |
| 182 | .help("Whether the preprocessed header already includes cxx.h. If so, we'll try to suppress the natural behavior of cxx to include duplicate definitions of some of the types within gen0.cc.") |
| 183 | ) |
| 184 | .arg_required_else_help(true) |
| 185 | .get_matches(); |
| 186 | run(matches).unwrap(); |
| 187 | } |
| 188 | |
| 189 | fn run(matches: ArgMatches) -> Result<(), std::io::Error> { |
| 190 | let keep_tmp = matches.is_present("keep"); |
| 191 | let tmp_dir = TempDir::new()?; |
| 192 | let r = do_run(matches, &tmp_dir); |
| 193 | if keep_tmp { |
| 194 | println!( |
| 195 | "Keeping temp dir created at: {}", |
| 196 | tmp_dir.into_path().to_str().unwrap() |
| 197 | ); |
| 198 | } |
| 199 | r |
| 200 | } |
| 201 | |
| 202 | #[derive(serde_derive::Deserialize)] |
| 203 | struct ReproCase { |
| 204 | config: String, |
| 205 | header: String, |
| 206 | } |
| 207 | |
| 208 | fn do_run(matches: ArgMatches, tmp_dir: &TempDir) -> Result<(), std::io::Error> { |
| 209 | let rs_path = tmp_dir.path().join("input.rs"); |
| 210 | let concat_path = tmp_dir.path().join("concat.h"); |
| 211 | match matches.subcommand_matches("repro") { |
| 212 | None => { |
| 213 | let submatches = matches.subcommand_matches("file").unwrap(); |
| 214 | let incs: Vec<_> = submatches |
| 215 | .values_of("inc") |
| 216 | .unwrap_or_default() |
| 217 | .map(PathBuf::from) |
| 218 | .collect(); |
| 219 | let defs: Vec<_> = submatches.values_of("define").unwrap_or_default().collect(); |
| 220 | let headers: Vec<_> = submatches.values_of("header").unwrap_or_default().collect(); |
| 221 | assert!(!headers.is_empty()); |
| 222 | let listing_path = tmp_dir.path().join("listing.h"); |
| 223 | create_concatenated_header(&headers, &listing_path)?; |
| 224 | announce_progress(&format!( |
| 225 | "Preprocessing {:?} to {:?}", |
| 226 | listing_path, concat_path |
| 227 | )); |
| 228 | preprocess(&listing_path, &concat_path, &incs, &defs)?; |
| 229 | let directives: Vec<_> = std::iter::once("#include \"concat.h\"\n".to_string()) |
| 230 | .chain( |
| 231 | submatches |
| 232 | .values_of("directive") |
| 233 | .unwrap_or_default() |
| 234 | .map(|s| format!("{}\n", s)), |
| 235 | ) |
| 236 | .collect(); |
| 237 | create_rs_file(&rs_path, &directives)?; |
| 238 | } |
| 239 | Some(submatches) => { |
| 240 | let case: ReproCase = serde_json::from_reader(File::open(PathBuf::from( |
| 241 | submatches.value_of("repro").unwrap(), |
| 242 | ))?) |
| 243 | .unwrap(); |
| 244 | // Replace the headers in the config |
| 245 | let mut config: IncludeCppConfig = syn::parse_str(&case.config).unwrap(); |
| 246 | config.replace_included_headers("concat.h"); |
| 247 | create_file( |
| 248 | &rs_path, |
| 249 | &format!("autocxx::include_cpp!({});", config.to_token_stream()), |
| 250 | )?; |
| 251 | if let Some(header) = submatches.value_of("header") { |
| 252 | std::fs::copy(PathBuf::from(header), &concat_path)?; |
| 253 | } else { |
| 254 | create_file(&concat_path, &case.header)? |
| 255 | } |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | let suppress_cxx_classes = match matches.value_of("suppress-cxx-inclusions").unwrap() { |
| 260 | "yes" => true, |
| 261 | "no" => false, |
| 262 | "auto" => detect_cxx_h(&concat_path)?, |
| 263 | _ => panic!("unexpected value"), |
| 264 | }; |
| 265 | |
| 266 | let cxx_suppressions = if suppress_cxx_classes { |
| 267 | get_cxx_suppressions() |
| 268 | } else { |
| 269 | Vec::new() |
| 270 | }; |
| 271 | |
| 272 | let extra_clang_args: Vec<_> = matches |
| 273 | .values_of("clang-args") |
| 274 | .unwrap_or_default() |
| 275 | .map(Cow::Borrowed) |
| 276 | .chain(cxx_suppressions.into_iter().map(Cow::Owned)) |
| 277 | .collect(); |
| 278 | let extra_clang_args: Vec<&str> = extra_clang_args.iter().map(|s| s.as_ref()).collect_vec(); |
| 279 | |
| 280 | let default_gen_cmd = std::env::current_exe()? |
| 281 | .parent() |
| 282 | .unwrap() |
| 283 | .join("autocxx-gen") |
| 284 | .to_str() |
| 285 | .unwrap() |
| 286 | .to_string(); |
| 287 | let gen_cmd = matches.value_of("gen-cmd").unwrap_or(&default_gen_cmd); |
| 288 | if !Path::new(gen_cmd).exists() { |
| 289 | panic!( |
| 290 | "autocxx-gen not found in {}. hint: autocxx-reduce --gen-cmd /path/to/autocxx-gen", |
| 291 | gen_cmd |
| 292 | ); |
| 293 | } |
| 294 | run_sample_gen_cmd(gen_cmd, &rs_path, tmp_dir.path(), &extra_clang_args)?; |
| 295 | // Create and run an interestingness test which does not filter its output through grep. |
| 296 | let demo_interestingness_test_dir = tmp_dir.path().join("demo-interestingness-test"); |
| 297 | std::fs::create_dir(&demo_interestingness_test_dir).unwrap(); |
| 298 | let interestingness_test = demo_interestingness_test_dir.join("test-demo.sh"); |
| 299 | create_interestingness_test( |
| 300 | gen_cmd, |
| 301 | &interestingness_test, |
| 302 | None, |
| 303 | &rs_path, |
| 304 | &extra_clang_args, |
| 305 | !matches.is_present("no-precompile"), |
| 306 | !matches.is_present("no-postcompile"), |
| 307 | )?; |
| 308 | let demo_dir_concat_path = demo_interestingness_test_dir.join("concat.h"); |
| 309 | std::fs::copy(&concat_path, demo_dir_concat_path).unwrap(); |
| 310 | run_demo_interestingness_test(&demo_interestingness_test_dir, &interestingness_test).unwrap(); |
| 311 | |
| 312 | // Now the main interestingness test |
| 313 | let interestingness_test = tmp_dir.path().join("test.sh"); |
| 314 | create_interestingness_test( |
| 315 | gen_cmd, |
| 316 | &interestingness_test, |
| 317 | Some(matches.value_of("problem").unwrap()), |
| 318 | &rs_path, |
| 319 | &extra_clang_args, |
| 320 | !matches.is_present("no-precompile"), |
| 321 | !matches.is_present("no-postcompile"), |
| 322 | )?; |
| 323 | run_creduce( |
| 324 | matches.value_of("creduce").unwrap(), |
| 325 | &interestingness_test, |
| 326 | &concat_path, |
| 327 | matches.values_of("creduce-args").unwrap_or_default(), |
| 328 | ); |
| 329 | announce_progress("creduce completed"); |
| 330 | let output_path = matches.value_of("output"); |
| 331 | match output_path { |
| 332 | None => print_minimized_case(&concat_path)?, |
| 333 | Some(output_path) => { |
| 334 | std::fs::copy(&concat_path, &PathBuf::from(output_path))?; |
| 335 | } |
| 336 | }; |
| 337 | Ok(()) |
| 338 | } |
| 339 | |
| 340 | /// Try to detect whether the preprocessed source code already contains |
| 341 | /// a preprocessed version of cxx.h. This is hard because all the comments |
| 342 | /// and preprocessor symbols may have been removed, and in fact if we're |
| 343 | /// part way through reduction, parts of the code may have been removed too. |
| 344 | fn detect_cxx_h(concat_path: &Path) -> Result<bool, std::io::Error> { |
| 345 | let haystack = std::fs::read_to_string(concat_path)?; |
| 346 | Ok(["class Box", "class Vec", "class Slice"] |
| 347 | .iter() |
| 348 | .all(|needle| haystack.contains(needle))) |
| 349 | } |
| 350 | |
| 351 | fn announce_progress(msg: &str) { |
| 352 | println!("=== {} ===", msg); |
| 353 | } |
| 354 | |
| 355 | fn print_minimized_case(concat_path: &Path) -> Result<(), std::io::Error> { |
| 356 | announce_progress("Completed. Minimized test case:"); |
| 357 | let contents = std::fs::read_to_string(concat_path)?; |
| 358 | println!("{}", contents); |
| 359 | Ok(()) |
| 360 | } |
| 361 | |
| 362 | /// Arguments we pass to creduce if supported. This pass always seems to cause a crash |
| 363 | /// as far as I can tell, so always exclude it. It may be environment-dependent, |
| 364 | /// of course, but as I'm the primary user of this tool I am ruthlessly removing it. |
| 365 | const REMOVE_PASS_LINE_MARKERS: &[&str] = &["--remove-pass", "pass_line_markers", "*"]; |
| 366 | const SKIP_INITIAL_PASSES: &[&str] = &["--skip-initial-passes"]; |
| 367 | |
| 368 | fn creduce_supports_remove_pass(creduce_cmd: &str) -> bool { |
| 369 | let cmd = std::process::Command::new(creduce_cmd) |
| 370 | .arg("--help") |
| 371 | .output(); |
| 372 | let msg = match cmd { |
| 373 | Err(error) => panic!("failed to run creduce. creduce_cmd = {}. hint: autocxx-reduce --creduce /path/to/creduce. error = {}", creduce_cmd, error), |
| 374 | Ok(result) => result.stdout |
| 375 | }; |
| 376 | let msg = std::str::from_utf8(&msg).unwrap(); |
| 377 | msg.contains("--remove-pass") |
| 378 | } |
| 379 | |
| 380 | fn run_creduce<'a>( |
| 381 | creduce_cmd: &str, |
| 382 | interestingness_test: &'a Path, |
| 383 | concat_path: &'a Path, |
| 384 | creduce_args: impl Iterator<Item = &'a str>, |
| 385 | ) { |
| 386 | announce_progress("creduce"); |
| 387 | let args = std::iter::once(interestingness_test.to_str().unwrap()) |
| 388 | .chain(std::iter::once(concat_path.to_str().unwrap())) |
| 389 | .chain(creduce_args) |
| 390 | .chain( |
| 391 | if creduce_supports_remove_pass(creduce_cmd) { |
| 392 | REMOVE_PASS_LINE_MARKERS |
| 393 | } else { |
| 394 | SKIP_INITIAL_PASSES |
| 395 | } |
| 396 | .iter() |
| 397 | .copied(), |
| 398 | ) |
| 399 | .collect::<Vec<_>>(); |
| 400 | println!("Command: {} {}", creduce_cmd, args.join(" ")); |
| 401 | std::process::Command::new(creduce_cmd) |
| 402 | .args(args) |
| 403 | .status() |
| 404 | .expect("failed to creduce"); |
| 405 | } |
| 406 | |
| 407 | fn run_sample_gen_cmd( |
| 408 | gen_cmd: &str, |
| 409 | rs_file: &Path, |
| 410 | tmp_dir: &Path, |
| 411 | extra_clang_args: &[&str], |
| 412 | ) -> Result<(), std::io::Error> { |
| 413 | let args = format_gen_cmd(rs_file, tmp_dir.to_str().unwrap(), extra_clang_args); |
| 414 | let args = args.collect::<Vec<_>>(); |
| 415 | let args_str = args.join(" "); |
| 416 | announce_progress(&format!("Running sample gen cmd: {} {}", gen_cmd, args_str)); |
| 417 | std::process::Command::new(gen_cmd).args(args).status()?; |
| 418 | Ok(()) |
| 419 | } |
| 420 | |
| 421 | fn run_demo_interestingness_test(demo_dir: &Path, test: &Path) -> Result<(), std::io::Error> { |
| 422 | announce_progress(&format!( |
| 423 | "Running demo interestingness test in {}", |
| 424 | demo_dir.to_string_lossy() |
| 425 | )); |
| 426 | std::process::Command::new(test) |
| 427 | .current_dir(demo_dir) |
| 428 | .status()?; |
| 429 | Ok(()) |
| 430 | } |
| 431 | |
| 432 | fn format_gen_cmd<'a>( |
| 433 | rs_file: &Path, |
| 434 | dir: &str, |
| 435 | extra_clang_args: &'a [&str], |
| 436 | ) -> impl Iterator<Item = String> + 'a { |
| 437 | let args = [ |
| 438 | "-o".to_string(), |
| 439 | dir.to_string(), |
| 440 | "-I".to_string(), |
| 441 | dir.to_string(), |
| 442 | rs_file.to_str().unwrap().to_string(), |
| 443 | "--gen-rs-include".to_string(), |
| 444 | "--gen-cpp".to_string(), |
| 445 | "--suppress-system-headers".to_string(), |
| 446 | "--".to_string(), |
| 447 | ] |
| 448 | .to_vec(); |
| 449 | args.into_iter() |
| 450 | .chain(extra_clang_args.iter().map(|s| s.to_string())) |
| 451 | } |
| 452 | |
| 453 | fn create_interestingness_test( |
| 454 | gen_cmd: &str, |
| 455 | test_path: &Path, |
| 456 | problem: Option<&str>, |
| 457 | rs_file: &Path, |
| 458 | extra_clang_args: &[&str], |
| 459 | precompile: bool, |
| 460 | postcompile: bool, |
| 461 | ) -> Result<(), std::io::Error> { |
| 462 | announce_progress("Creating interestingness test"); |
| 463 | // Ensure we refer to the input header by relative path |
| 464 | // because creduce will invoke us in some other directory with |
| 465 | // a copy thereof. |
| 466 | let mut args = format_gen_cmd(rs_file, "$(pwd)", extra_clang_args); |
| 467 | let args = args.join(" "); |
| 468 | let precompile_step = make_compile_step(precompile, "concat.h", extra_clang_args); |
| 469 | // For the compile afterwards, we have to avoid including any system headers. |
| 470 | // We rely on equivalent content being hermetically inside concat.h. |
| 471 | let postcompile_step = make_compile_step(postcompile, "gen0.cc", extra_clang_args); |
| 472 | let problem_grep = problem |
| 473 | .map(|problem| format!("| grep \"{}\" >/dev/null 2>&1", problem)) |
| 474 | .unwrap_or_default(); |
| 475 | let content = format!( |
| 476 | indoc! {" |
| 477 | #!/bin/sh |
| 478 | set -e |
| 479 | echo Precompile |
| 480 | {} |
| 481 | echo Move |
| 482 | mv concat.h concat-body.h |
| 483 | echo Codegen |
| 484 | (echo \"#ifndef __CONCAT_H__\"; echo \"#define __CONCAT_H__\"; echo '#include \"concat-body.h\"'; echo \"#endif\") > concat.h |
| 485 | ({} {} 2>&1 && cat autocxx-ffi-default-gen.rs && cat autocxxgen*.h && {} 2>&1 ) {} |
| 486 | echo Remove |
| 487 | rm concat.h |
| 488 | echo Swap back |
| 489 | mv concat-body.h concat.h |
| 490 | echo Done |
| 491 | "}, |
| 492 | precompile_step, gen_cmd, args, postcompile_step, problem_grep |
| 493 | ); |
| 494 | println!("Interestingness test:\n{}", content); |
| 495 | { |
| 496 | let mut file = File::create(test_path)?; |
| 497 | file.write_all(content.as_bytes())?; |
| 498 | } |
| 499 | |
| 500 | let mut perms = std::fs::metadata(&test_path)?.permissions(); |
| 501 | perms.set_mode(0o700); |
| 502 | std::fs::set_permissions(&test_path, perms)?; |
| 503 | Ok(()) |
| 504 | } |
| 505 | |
| 506 | fn make_compile_step(enabled: bool, file: &str, extra_clang_args: &[&str]) -> String { |
| 507 | if enabled { |
| 508 | format!( |
| 509 | "{} {} -c {}", |
| 510 | get_clang_path(), |
| 511 | make_clang_args(&[PathBuf::from(".")], extra_clang_args).join(" "), |
| 512 | file, |
| 513 | ) |
| 514 | } else { |
| 515 | "echo 'Skipping compilation'".into() |
| 516 | } |
| 517 | } |
| 518 | |
| 519 | fn create_rs_file(rs_path: &Path, directives: &[String]) -> Result<(), std::io::Error> { |
| 520 | announce_progress("Creating Rust input file"); |
| 521 | let mut file = File::create(rs_path)?; |
| 522 | file.write_all("use autocxx::include_cpp;\ninclude_cpp! (\n".as_bytes())?; |
| 523 | for directive in directives { |
| 524 | file.write_all(directive.as_bytes())?; |
| 525 | } |
| 526 | file.write_all(");\n".as_bytes())?; |
| 527 | Ok(()) |
| 528 | } |
| 529 | |
| 530 | fn create_concatenated_header(headers: &[&str], listing_path: &Path) -> Result<(), std::io::Error> { |
| 531 | announce_progress("Creating preprocessed header"); |
| 532 | let mut file = File::create(listing_path)?; |
| 533 | for header in headers { |
| 534 | file.write_all(format!("#include \"{}\"\n", header).as_bytes())?; |
| 535 | } |
| 536 | Ok(()) |
| 537 | } |
| 538 | |
| 539 | fn create_file(path: &Path, content: &str) -> Result<(), std::io::Error> { |
| 540 | let mut file = File::create(path)?; |
| 541 | write!(file, "{}", content)?; |
| 542 | Ok(()) |
| 543 | } |
| 544 | |
| 545 | fn get_cxx_suppressions() -> Vec<String> { |
| 546 | let defines: IndexSet<_> = Regex::new(r"\bCXXBRIDGE1_\w+\b") |
| 547 | .unwrap() |
| 548 | .find_iter(cxx_gen::HEADER) |
| 549 | .map(|m| m.as_str()) |
| 550 | .collect(); // for uniqueness |
| 551 | defines |
| 552 | .into_iter() |
| 553 | .map(|def| format!("-D{}", def)) |
| 554 | .collect() |
| 555 | } |
| 556 | |
| 557 | #[test] |
| 558 | fn test_get_cxx_suppressions() { |
| 559 | let defines = get_cxx_suppressions(); |
| 560 | assert!(defines.contains(&"-DCXXBRIDGE1_RUST_BITCOPY_T".to_string())); |
| 561 | assert!(defines.contains(&"-DCXXBRIDGE1_RUST_STR".to_string())); |
| 562 | } |