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 | use proc_macro2::TokenStream; |
| 10 | use quote::quote; |
| 11 | use std::{fs::File, path::PathBuf}; |
| 12 | |
| 13 | use crate::{multi_bindings::MultiBindings, IncludeCppConfig}; |
| 14 | |
| 15 | /// The strategy used to generate, and to find generated, files. |
| 16 | /// As standard, these are based off the OUT_DIR set by Cargo, |
| 17 | /// but our code can't assume it can read OUT_DIR as it may |
| 18 | /// be running in the rust-analyzer proc macro server where it's |
| 19 | /// not available. We need to give provision for custom locations |
| 20 | /// and we need our code generator build script to be able to pass |
| 21 | /// locations through to the proc macro by env vars. |
| 22 | /// |
| 23 | /// On the whole this class concerns itself with directory names |
| 24 | /// and allows the actual file name to be determined elsewhere |
| 25 | /// (based on a hash of the contents of `include_cpp!`.) But |
| 26 | /// some types of build system need to know the precise file _name_ |
| 27 | /// produced by the codegen phase and passed into the macro phase, |
| 28 | /// so we have some options for that. See `gen --help` for details. |
| 29 | pub enum FileLocationStrategy { |
| 30 | Custom(PathBuf), |
| 31 | FromAutocxxRsFile(PathBuf), |
| 32 | FromAutocxxRs(PathBuf), |
| 33 | FromOutDir(PathBuf), |
| 34 | FromAutocxxRsJsonArchive(PathBuf), |
| 35 | UnknownMaybeFromOutdir, |
| 36 | } |
| 37 | |
| 38 | static BUILD_DIR_NAME: &str = "autocxx-build-dir"; |
| 39 | static RS_DIR_NAME: &str = "rs"; |
| 40 | static AUTOCXX_RS: &str = "AUTOCXX_RS"; |
| 41 | static AUTOCXX_RS_FILE: &str = "AUTOCXX_RS_FILE"; |
| 42 | static AUTOCXX_RS_JSON_ARCHIVE: &str = "AUTOCXX_RS_JSON_ARCHIVE"; |
| 43 | |
| 44 | impl FileLocationStrategy { |
| 45 | pub fn new() -> Self { |
| 46 | match std::env::var_os(AUTOCXX_RS_JSON_ARCHIVE) { |
| 47 | Some(of) => FileLocationStrategy::FromAutocxxRsJsonArchive(PathBuf::from(of)), |
| 48 | None => match std::env::var_os(AUTOCXX_RS_FILE) { |
| 49 | Some(of) => FileLocationStrategy::FromAutocxxRsFile(PathBuf::from(of)), |
| 50 | None => match std::env::var_os(AUTOCXX_RS) { |
| 51 | None => match std::env::var_os("OUT_DIR") { |
| 52 | None => FileLocationStrategy::UnknownMaybeFromOutdir, |
| 53 | Some(od) => FileLocationStrategy::FromOutDir(PathBuf::from(od)), |
| 54 | }, |
| 55 | Some(acrs) => FileLocationStrategy::FromAutocxxRs(PathBuf::from(acrs)), |
| 56 | }, |
| 57 | }, |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | pub fn new_custom(gen_dir: PathBuf) -> Self { |
| 62 | FileLocationStrategy::Custom(gen_dir) |
| 63 | } |
| 64 | |
| 65 | /// Make a macro to include a given generated Rust file name. |
| 66 | /// This can't simply be calculated from `get_rs_dir` because |
| 67 | /// of limitations in rust-analyzer. |
| 68 | pub fn make_include(&self, config: &IncludeCppConfig) -> TokenStream { |
| 69 | match self { |
| 70 | FileLocationStrategy::FromAutocxxRs(custom_dir) => { |
| 71 | let fname = config.get_rs_filename(); |
| 72 | let fname = custom_dir.join(fname).to_str().unwrap().to_string(); |
| 73 | quote! { |
| 74 | include!( #fname ); |
| 75 | } |
| 76 | } |
| 77 | FileLocationStrategy::Custom(_) => panic!("Should never happen in the macro"), |
| 78 | FileLocationStrategy::UnknownMaybeFromOutdir | FileLocationStrategy::FromOutDir(_) => { |
| 79 | let fname = config.get_rs_filename(); |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 80 | let fname = format!("/{BUILD_DIR_NAME}/{RS_DIR_NAME}/{fname}"); |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 81 | // rust-analyzer works better if we ask Rust to do the path |
| 82 | // concatenation rather than doing it in proc-macro code. |
| 83 | // proc-macro code does not itself have access to the value of |
| 84 | // OUT_DIR, but if we put it into a macro like the below, |
| 85 | // rust-analyzer can cope. |
| 86 | quote! { |
| 87 | include!(concat!(env!("OUT_DIR"), #fname)); |
| 88 | } |
| 89 | } |
| 90 | FileLocationStrategy::FromAutocxxRsFile(fname) => { |
| 91 | let fname = fname |
| 92 | .to_str() |
| 93 | .expect("AUTOCXX_RS_FILE environment variable contained non-UTF8 characters"); |
| 94 | quote! { |
| 95 | include!( #fname ); |
| 96 | } |
| 97 | } |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 98 | FileLocationStrategy::FromAutocxxRsJsonArchive(fnames) => { |
| 99 | let archive = std::env::split_paths(fnames).flat_map(File::open).next().unwrap_or_else(|| panic!("Unable to open any of the paths listed in {}. This may mean you didn't run the codegen tool (autocxx_gen) before building the Rust code.", fnames.to_string_lossy())); |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 100 | let multi_bindings: MultiBindings = serde_json::from_reader(archive) |
| 101 | .unwrap_or_else(|_| { |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 102 | panic!("Unable to interpret {} as JSON", fnames.to_string_lossy()) |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 103 | }); |
Austin Schuh | 6ea9bfa | 2023-08-06 19:05:10 -0700 | [diff] [blame^] | 104 | multi_bindings.get(config).unwrap_or_else(|err| panic!("Unable to find a suitable set of bindings within the JSON archive {} ({}). This likely means that the codegen tool hasn't been rerun since some changes in your include_cpp! macro.", fnames.to_string_lossy(), err)) |
Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame] | 105 | } |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | fn get_gen_dir(&self, suffix: &str) -> PathBuf { |
| 110 | let root = match self { |
| 111 | FileLocationStrategy::Custom(gen_dir) |
| 112 | | FileLocationStrategy::FromAutocxxRs(gen_dir) => gen_dir.clone(), |
| 113 | FileLocationStrategy::FromOutDir(out_dir) => out_dir.join(BUILD_DIR_NAME), |
| 114 | FileLocationStrategy::UnknownMaybeFromOutdir => { |
| 115 | panic!("Could not determine OUT_DIR or AUTOCXX_RS dir") |
| 116 | } |
| 117 | FileLocationStrategy::FromAutocxxRsFile(_) => { |
| 118 | panic!("It's invalid to set AUTOCXX_RS_FILE during the codegen phase.") |
| 119 | } |
| 120 | FileLocationStrategy::FromAutocxxRsJsonArchive(_) => { |
| 121 | panic!("It's invalid to set AUTOCXX_RS_JSON_ARCHIVE during the codegen phase.") |
| 122 | } |
| 123 | }; |
| 124 | root.join(suffix) |
| 125 | } |
| 126 | |
| 127 | /// Location to generate Rust files. |
| 128 | pub fn get_rs_dir(&self) -> PathBuf { |
| 129 | self.get_gen_dir(RS_DIR_NAME) |
| 130 | } |
| 131 | |
| 132 | /// Location to generate C++ header files. |
| 133 | pub fn get_include_dir(&self) -> PathBuf { |
| 134 | self.get_gen_dir("include") |
| 135 | } |
| 136 | |
| 137 | /// Location to generate C++ code. |
| 138 | pub fn get_cxx_dir(&self) -> PathBuf { |
| 139 | self.get_gen_dir("cxx") |
| 140 | } |
| 141 | |
| 142 | /// From a build script, inform cargo how to set environment variables |
| 143 | /// to make them available to the procedural macro. |
| 144 | pub fn set_cargo_env_vars_for_build(&self) { |
| 145 | if let FileLocationStrategy::Custom(_) = self { |
| 146 | println!( |
| 147 | "cargo:rustc-env={}={}", |
| 148 | AUTOCXX_RS, |
| 149 | self.get_rs_dir().to_str().unwrap() |
| 150 | ); |
| 151 | } |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | impl Default for FileLocationStrategy { |
| 156 | fn default() -> Self { |
| 157 | Self::new() |
| 158 | } |
| 159 | } |