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 | mod depfile; |
| 12 | |
| 13 | use autocxx_engine::{ |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 14 | generate_rs_archive, generate_rs_single, get_cxx_header_bytes, parse_file, |
| 15 | AutocxxgenHeaderNamer, CxxgenHeaderNamer, RebuildDependencyRecorder, |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 16 | }; |
| 17 | use clap::{crate_authors, crate_version, Arg, ArgGroup, Command}; |
| 18 | use depfile::Depfile; |
| 19 | use indexmap::IndexSet; |
| 20 | use miette::IntoDiagnostic; |
| 21 | use std::cell::RefCell; |
| 22 | use std::io::{Read, Write}; |
| 23 | use std::path::PathBuf; |
| 24 | use std::rc::Rc; |
| 25 | use std::{cell::Cell, fs::File, path::Path}; |
| 26 | |
| 27 | pub(crate) static BLANK: &str = "// Blank autocxx placeholder"; |
| 28 | |
| 29 | static LONG_HELP: &str = " |
| 30 | Command line utility to expand the Rust 'autocxx' include_cpp! directive. |
| 31 | |
| 32 | This tool can generate both the C++ and Rust side binding code for |
| 33 | a Rust file containing an include_cpp! directive. |
| 34 | |
| 35 | If you're using cargo, don't use this: use autocxx_build instead, |
| 36 | which is much easier to include in build.rs build scripts. You'd likely |
| 37 | use this tool only if you're using some non-Cargo build system. If |
| 38 | that's you, read on. |
| 39 | |
| 40 | This tool has three modes: generate the C++; or generate |
| 41 | a Rust file which can be included by the autocxx_macro; or generate an archive |
| 42 | containing multiple Rust files to be expanded by different autocxx macros. |
| 43 | You may specify multiple modes, or of course, invoke the tool multiple times. |
| 44 | |
| 45 | In any mode, you'll need to pass the source Rust file name and the C++ |
| 46 | include path. You may pass multiple Rust files, each of which may contain |
| 47 | multiple include_cpp! or cxx::bridge macros. |
| 48 | |
| 49 | There are three basic ways to use this tool, depending on the flexibility |
| 50 | of your build system. |
| 51 | |
| 52 | Does your build system require fixed output filenames, or can it enumerate |
| 53 | whatever files are generated? |
| 54 | |
| 55 | If it's flexible, then use |
| 56 | --gen-rs-include --gen-cpp |
| 57 | An arbitrary number of .h, .cc and .rs files will be generated, depending |
| 58 | on how many cxx::bridge and include_cpp macros are encountered and their contents. |
| 59 | When building the rust code, simply ensure that AUTOCXX_RS or OUT_DIR is set to |
| 60 | teach rustc where to find these .rs files. |
| 61 | |
| 62 | If your build system needs to be told exactly what C++ files are generated, |
| 63 | additionally use --generate-exact <N> You are then guaranteed to get |
| 64 | exactly 'n' files as follows: |
| 65 | gen<n>.h |
| 66 | autocxxgen<n>.h |
| 67 | gen<n>.cc |
| 68 | Some of them may be blank. If the tool finds too many include_cpp or cxx::bridge |
| 69 | macros to fit within that allowance, the build will fail. |
| 70 | |
| 71 | If your build system additionally requires that Rust files have fixed |
| 72 | filenames, then you should use |
| 73 | --gen-rs-archive |
| 74 | instead of |
| 75 | --gen-rs-include |
| 76 | and you will need to give AUTOCXX_RS_JSON_ARCHIVE when building the Rust code. |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 77 | The output filename is named gen.rs.json. AUTOCXX_RS_JSON_ARCHIVE should be set |
| 78 | to the path to gen.rs.json. It may optionally have multiple paths separated the |
| 79 | way as the PATH environment variable for the current platform, see |
| 80 | [`std::env::split_paths`] for details. The first path which is successfully |
| 81 | opened will be used. |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 82 | |
| 83 | This teaches rustc (and the autocxx macro) that all the different Rust bindings |
| 84 | for multiple different autocxx macros have been archived into this single file. |
| 85 | "; |
| 86 | |
| 87 | fn main() -> miette::Result<()> { |
| 88 | let matches = Command::new("autocxx-gen") |
| 89 | .version(crate_version!()) |
| 90 | .author(crate_authors!()) |
| 91 | .about("Generates bindings files from Rust files that contain include_cpp! macros") |
| 92 | .long_about(LONG_HELP) |
| 93 | .arg( |
| 94 | Arg::new("INPUT") |
| 95 | .help("Sets the input .rs files to use") |
| 96 | .required(true) |
| 97 | .multiple_occurrences(true) |
| 98 | ) |
| 99 | .arg( |
| 100 | Arg::new("outdir") |
| 101 | .short('o') |
| 102 | .long("outdir") |
| 103 | .allow_invalid_utf8(true) |
| 104 | .value_name("PATH") |
| 105 | .help("output directory path") |
| 106 | .takes_value(true) |
| 107 | .required(true), |
| 108 | ) |
| 109 | .arg( |
| 110 | Arg::new("inc") |
| 111 | .short('I') |
| 112 | .long("inc") |
| 113 | .multiple_occurrences(true) |
| 114 | .number_of_values(1) |
| 115 | .value_name("INCLUDE DIRS") |
| 116 | .help("include path") |
| 117 | .takes_value(true), |
| 118 | ) |
| 119 | .arg( |
| 120 | Arg::new("cpp-extension") |
| 121 | .long("cpp-extension") |
| 122 | .value_name("EXTENSION") |
| 123 | .default_value("cc") |
| 124 | .help("C++ filename extension") |
| 125 | .takes_value(true), |
| 126 | ) |
| 127 | .arg( |
| 128 | Arg::new("gen-cpp") |
| 129 | .long("gen-cpp") |
| 130 | .help("whether to generate C++ implementation and header files") |
| 131 | ) |
| 132 | .arg( |
| 133 | Arg::new("gen-rs-include") |
| 134 | .long("gen-rs-include") |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 135 | .help("whether to generate Rust files for inclusion using autocxx_macro") |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 136 | ) |
| 137 | .arg( |
| 138 | Arg::new("gen-rs-archive") |
| 139 | .long("gen-rs-archive") |
| 140 | .help("whether to generate an archive of multiple sets of Rust bindings for use by autocxx_macro (suffix will be .rs.json)") |
| 141 | ) |
| 142 | .group(ArgGroup::new("mode") |
| 143 | .required(true) |
| 144 | .multiple(true) |
| 145 | .arg("gen-cpp") |
| 146 | .arg("gen-rs-include") |
| 147 | .arg("gen-rs-archive") |
| 148 | ) |
| 149 | .arg( |
| 150 | Arg::new("generate-exact") |
| 151 | .long("generate-exact") |
| 152 | .value_name("NUM") |
| 153 | .help("assume and ensure there are exactly NUM bridge blocks in the file. Only applies for --gen-cpp or --gen-rs-include") |
| 154 | .takes_value(true), |
| 155 | ) |
| 156 | .arg( |
| 157 | Arg::new("fix-rs-include-name") |
| 158 | .long("fix-rs-include-name") |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 159 | .help("Make the name of the .rs file predictable (suffix will be .include.rs). You must set AUTOCXX_RS_FILE during Rust build time to educate autocxx_macro about your choice.") |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 160 | .requires("gen-rs-include") |
| 161 | ) |
| 162 | .arg( |
| 163 | Arg::new("auto-allowlist") |
| 164 | .long("auto-allowlist") |
| 165 | .help("Dynamically construct allowlist from real uses of APIs.") |
| 166 | ) |
| 167 | .arg( |
| 168 | Arg::new("suppress-system-headers") |
| 169 | .long("suppress-system-headers") |
| 170 | .help("Do not refer to any system headers from generated code. May be useful for minimization.") |
| 171 | ) |
| 172 | .arg( |
| 173 | Arg::new("cxx-impl-annotations") |
| 174 | .long("cxx-impl-annotations") |
| 175 | .value_name("ANNOTATION") |
| 176 | .help("prefix for symbols to be exported from C++ bindings, e.g. __attribute__ ((visibility (\"default\")))") |
| 177 | .takes_value(true), |
| 178 | ) |
| 179 | .arg( |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 180 | Arg::new("generate-cxx-h") |
| 181 | .long("generate-cxx-h") |
| 182 | .help("whether to generate cxx.h header file. If you already knew where to find cxx.h, consider using --cxx-h-path") |
| 183 | ) |
| 184 | .arg( |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 185 | Arg::new("cxx-h-path") |
| 186 | .long("cxx-h-path") |
| 187 | .value_name("PREFIX") |
| 188 | .help("prefix for path to cxx.h (from the cxx crate) within #include statements. Must end in /") |
| 189 | .takes_value(true), |
| 190 | ) |
| 191 | .arg( |
| 192 | Arg::new("cxxgen-h-path") |
| 193 | .long("cxxgen-h-path") |
| 194 | .value_name("PREFIX") |
| 195 | .help("prefix for path to cxxgen.h (which we generate into the output directory) within #include statements. Must end in /") |
| 196 | .takes_value(true), |
| 197 | ) |
| 198 | .arg( |
| 199 | Arg::new("depfile") |
| 200 | .long("depfile") |
| 201 | .value_name("DEPFILE") |
| 202 | .help("A .d file to write") |
| 203 | .takes_value(true), |
| 204 | ) |
| 205 | .arg( |
| 206 | Arg::new("clang-args") |
| 207 | .last(true) |
| 208 | .multiple_occurrences(true) |
| 209 | .help("Extra arguments to pass to Clang"), |
| 210 | ) |
| 211 | .get_matches(); |
| 212 | |
| 213 | env_logger::builder().init(); |
| 214 | let incs = matches |
| 215 | .values_of("inc") |
| 216 | .unwrap_or_default() |
| 217 | .map(PathBuf::from) |
| 218 | .collect::<Vec<_>>(); |
| 219 | let extra_clang_args: Vec<_> = matches |
| 220 | .values_of("clang-args") |
| 221 | .unwrap_or_default() |
| 222 | .collect(); |
| 223 | let suppress_system_headers = matches.is_present("suppress-system-headers"); |
| 224 | let desired_number = matches |
| 225 | .value_of("generate-exact") |
| 226 | .map(|s| s.parse::<usize>().unwrap()); |
| 227 | let autocxxgen_header_counter = Cell::new(0); |
| 228 | let autocxxgen_header_namer = if desired_number.is_some() { |
| 229 | AutocxxgenHeaderNamer(Box::new(|_| { |
| 230 | let r = name_autocxxgen_h(autocxxgen_header_counter.get()); |
| 231 | autocxxgen_header_counter.set(autocxxgen_header_counter.get() + 1); |
| 232 | r |
| 233 | })) |
| 234 | } else { |
| 235 | Default::default() |
| 236 | }; |
| 237 | let cxxgen_header_counter = Cell::new(0); |
| 238 | let cxxgen_header_namer = if desired_number.is_some() { |
| 239 | CxxgenHeaderNamer(Box::new(|| { |
| 240 | let r = name_cxxgen_h(cxxgen_header_counter.get()); |
| 241 | cxxgen_header_counter.set(cxxgen_header_counter.get() + 1); |
| 242 | r |
| 243 | })) |
| 244 | } else { |
| 245 | Default::default() |
| 246 | }; |
| 247 | let cpp_codegen_options = autocxx_engine::CppCodegenOptions { |
| 248 | suppress_system_headers, |
| 249 | cxx_impl_annotations: get_option_string("cxx-impl-annotations", &matches), |
| 250 | path_to_cxx_h: get_option_string("cxx-h-path", &matches), |
| 251 | path_to_cxxgen_h: get_option_string("cxxgen-h-path", &matches), |
| 252 | autocxxgen_header_namer, |
| 253 | cxxgen_header_namer, |
| 254 | }; |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 255 | let codegen_options = autocxx_engine::CodegenOptions { |
| 256 | cpp_codegen_options, |
| 257 | ..Default::default() |
| 258 | }; |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 259 | let depfile = match matches.value_of("depfile") { |
| 260 | None => None, |
| 261 | Some(depfile_path) => { |
| 262 | let depfile_path = PathBuf::from(depfile_path); |
| 263 | Some(Rc::new(RefCell::new( |
| 264 | Depfile::new(&depfile_path).into_diagnostic()?, |
| 265 | ))) |
| 266 | } |
| 267 | }; |
| 268 | let auto_allowlist = matches.is_present("auto-allowlist"); |
| 269 | |
| 270 | let mut parsed_files = Vec::new(); |
| 271 | for input in matches.values_of("INPUT").expect("No INPUT was provided") { |
| 272 | // Parse all the .rs files we're asked to process, first. |
| 273 | // Spot any fundamental parsing or command line problems before we start |
| 274 | // to do the complex processing. |
| 275 | let parsed_file = parse_file(input, auto_allowlist)?; |
| 276 | parsed_files.push(parsed_file); |
| 277 | } |
| 278 | |
| 279 | for parsed_file in parsed_files.iter_mut() { |
| 280 | // Now actually handle all the include_cpp directives we found, |
| 281 | // which is the complex bit where we interpret all the C+. |
| 282 | let dep_recorder: Option<Box<dyn RebuildDependencyRecorder>> = depfile |
| 283 | .as_ref() |
| 284 | .map(|rc| get_dependency_recorder(rc.clone())); |
| 285 | parsed_file.resolve_all( |
| 286 | incs.clone(), |
| 287 | &extra_clang_args, |
| 288 | dep_recorder, |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 289 | &codegen_options, |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 290 | )?; |
| 291 | } |
| 292 | |
| 293 | // Finally start to write the C++ and Rust out. |
| 294 | let outdir: PathBuf = matches.value_of_os("outdir").unwrap().into(); |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 295 | |
| 296 | if !outdir.exists() { |
| 297 | use miette::WrapErr as _; |
| 298 | std::fs::create_dir_all(&outdir) |
| 299 | .into_diagnostic() |
| 300 | .wrap_err_with(|| format!("Failed to create `outdir` '{}'", outdir.display()))?; |
| 301 | } |
| 302 | |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 303 | let mut writer = FileWriter { |
| 304 | depfile: &depfile, |
| 305 | outdir: &outdir, |
| 306 | written: IndexSet::new(), |
| 307 | }; |
| 308 | if matches.is_present("gen-cpp") { |
| 309 | let cpp = matches.value_of("cpp-extension").unwrap(); |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 310 | let name_cc_file = |counter| format!("gen{counter}.{cpp}"); |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 311 | let mut counter = 0usize; |
| 312 | for include_cxx in parsed_files |
| 313 | .iter() |
| 314 | .flat_map(|file| file.get_cpp_buildables()) |
| 315 | { |
| 316 | let generations = include_cxx |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 317 | .generate_h_and_cxx(&codegen_options.cpp_codegen_options) |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 318 | .expect("Unable to generate header and C++ code"); |
| 319 | for pair in generations.0 { |
| 320 | let cppname = name_cc_file(counter); |
| 321 | writer.write_to_file(cppname, &pair.implementation.unwrap_or_default())?; |
| 322 | writer.write_to_file(pair.header_name, &pair.header)?; |
| 323 | counter += 1; |
| 324 | } |
| 325 | } |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 326 | drop(codegen_options); |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 327 | // Write placeholders to ensure we always make exactly 'n' of each file type. |
| 328 | writer.write_placeholders(counter, desired_number, name_cc_file)?; |
| 329 | writer.write_placeholders( |
| 330 | cxxgen_header_counter.into_inner(), |
| 331 | desired_number, |
| 332 | name_cxxgen_h, |
| 333 | )?; |
| 334 | writer.write_placeholders( |
| 335 | autocxxgen_header_counter.into_inner(), |
| 336 | desired_number, |
| 337 | name_autocxxgen_h, |
| 338 | )?; |
| 339 | } |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 340 | |
| 341 | if matches.is_present("generate-cxx-h") { |
| 342 | writer.write_to_file( |
| 343 | "cxx.h".to_string(), |
| 344 | &get_cxx_header_bytes(suppress_system_headers), |
| 345 | )?; |
| 346 | } |
| 347 | |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 348 | if matches.is_present("gen-rs-include") { |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 349 | if !matches.is_present("fix-rs-include-name") && desired_number.is_some() { |
| 350 | return Err(miette::Report::msg( |
| 351 | "gen-rs-include and generate-exact requires fix-rs-include-name.", |
| 352 | )); |
| 353 | } |
| 354 | let mut counter = 0usize; |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 355 | let rust_buildables = parsed_files |
| 356 | .iter() |
| 357 | .flat_map(|parsed_file| parsed_file.get_rs_outputs()); |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 358 | for include_cxx in rust_buildables { |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 359 | let rs_code = generate_rs_single(include_cxx); |
| 360 | let fname = if matches.is_present("fix-rs-include-name") { |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 361 | name_include_rs(counter) |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 362 | } else { |
| 363 | rs_code.filename |
| 364 | }; |
| 365 | writer.write_to_file(fname, rs_code.code.as_bytes())?; |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 366 | counter += 1; |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 367 | } |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 368 | writer.write_placeholders(counter, desired_number, name_include_rs)?; |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 369 | } |
| 370 | if matches.is_present("gen-rs-archive") { |
| 371 | let rust_buildables = parsed_files |
| 372 | .iter() |
| 373 | .flat_map(|parsed_file| parsed_file.get_rs_outputs()); |
| 374 | let json = generate_rs_archive(rust_buildables); |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 375 | writer.write_to_file("gen.rs.json".into(), json.as_bytes())?; |
| 376 | } |
| 377 | if let Some(depfile) = depfile { |
| 378 | depfile.borrow_mut().write().into_diagnostic()?; |
| 379 | } |
| 380 | Ok(()) |
| 381 | } |
| 382 | |
| 383 | fn name_autocxxgen_h(counter: usize) -> String { |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 384 | format!("autocxxgen{counter}.h") |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 385 | } |
| 386 | |
| 387 | fn name_cxxgen_h(counter: usize) -> String { |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 388 | format!("gen{counter}.h") |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 389 | } |
| 390 | |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 391 | fn name_include_rs(counter: usize) -> String { |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 392 | format!("gen{counter}.include.rs") |
Brian Silverman | f3ec38b | 2022-07-06 20:43:36 -0700 | [diff] [blame] | 393 | } |
| 394 | |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 395 | fn get_dependency_recorder(depfile: Rc<RefCell<Depfile>>) -> Box<dyn RebuildDependencyRecorder> { |
| 396 | Box::new(RecordIntoDepfile(depfile)) |
| 397 | } |
| 398 | |
| 399 | fn get_option_string(option: &str, matches: &clap::ArgMatches) -> Option<String> { |
| 400 | let cxx_impl_annotations = matches.value_of(option).map(|s| s.to_string()); |
| 401 | cxx_impl_annotations |
| 402 | } |
| 403 | |
| 404 | struct FileWriter<'a> { |
| 405 | depfile: &'a Option<Rc<RefCell<Depfile>>>, |
| 406 | outdir: &'a Path, |
| 407 | written: IndexSet<String>, |
| 408 | } |
| 409 | |
| 410 | impl<'a> FileWriter<'a> { |
| 411 | fn write_placeholders<F: FnOnce(usize) -> String + Copy>( |
| 412 | &mut self, |
| 413 | mut counter: usize, |
| 414 | desired_number: Option<usize>, |
| 415 | filename: F, |
| 416 | ) -> miette::Result<()> { |
| 417 | if let Some(desired_number) = desired_number { |
| 418 | if counter > desired_number { |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 419 | return Err(miette::Report::msg(format!("{counter} files were generated. Increase the value passed to --generate-exact or reduce the number of include_cpp! sections."))); |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 420 | } |
| 421 | while counter < desired_number { |
| 422 | let fname = filename(counter); |
| 423 | self.write_to_file(fname, BLANK.as_bytes())?; |
| 424 | counter += 1; |
| 425 | } |
| 426 | } |
| 427 | Ok(()) |
| 428 | } |
| 429 | |
| 430 | fn write_to_file(&mut self, filename: String, content: &[u8]) -> miette::Result<()> { |
| 431 | let path = self.outdir.join(&filename); |
| 432 | if let Some(depfile) = self.depfile { |
| 433 | depfile.borrow_mut().add_output(&path); |
| 434 | } |
| 435 | { |
| 436 | let f = File::open(&path); |
| 437 | if let Ok(mut f) = f { |
| 438 | let mut existing_content = Vec::new(); |
| 439 | let r = f.read_to_end(&mut existing_content); |
| 440 | if r.is_ok() && existing_content == content { |
| 441 | return Ok(()); // don't change timestamp on existing file unnecessarily |
| 442 | } |
| 443 | } |
| 444 | } |
| 445 | let mut f = File::create(&path).into_diagnostic()?; |
| 446 | f.write_all(content).into_diagnostic()?; |
| 447 | if self.written.contains(&filename) { |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 448 | return Err(miette::Report::msg(format!("autocxx_gen would write two files entitled '{filename}' which would have conflicting contents. Consider using --generate-exact."))); |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 449 | } |
| 450 | self.written.insert(filename); |
| 451 | Ok(()) |
| 452 | } |
| 453 | } |
| 454 | |
| 455 | struct RecordIntoDepfile(Rc<RefCell<Depfile>>); |
| 456 | |
| 457 | impl RebuildDependencyRecorder for RecordIntoDepfile { |
| 458 | fn record_header_file_dependency(&self, filename: &str) { |
| 459 | self.0.borrow_mut().add_dependency(&PathBuf::from(filename)) |
| 460 | } |
| 461 | } |
| 462 | |
| 463 | impl std::fmt::Debug for RecordIntoDepfile { |
| 464 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 465 | write!(f, "<depfile>") |
| 466 | } |
| 467 | } |