autocxx_parser/
file_locations.rs

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
9use proc_macro2::TokenStream;
10use quote::quote;
11use std::{fs::File, path::PathBuf};
12
13use 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.
29pub enum FileLocationStrategy {
30    Custom(PathBuf),
31    FromAutocxxRsFile(PathBuf),
32    FromAutocxxRs(PathBuf),
33    FromOutDir(PathBuf),
34    FromAutocxxRsJsonArchive(PathBuf),
35    UnknownMaybeFromOutdir,
36}
37
38static BUILD_DIR_NAME: &str = "autocxx-build-dir";
39static RS_DIR_NAME: &str = "rs";
40static AUTOCXX_RS: &str = "AUTOCXX_RS";
41static AUTOCXX_RS_FILE: &str = "AUTOCXX_RS_FILE";
42static AUTOCXX_RS_JSON_ARCHIVE: &str = "AUTOCXX_RS_JSON_ARCHIVE";
43
44impl 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();
80                let fname = format!("/{BUILD_DIR_NAME}/{RS_DIR_NAME}/{fname}");
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            }
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()));
100                let multi_bindings: MultiBindings = serde_json::from_reader(archive)
101                    .unwrap_or_else(|_| {
102                        panic!("Unable to interpret {} as JSON", fnames.to_string_lossy())
103                    });
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))
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
155impl Default for FileLocationStrategy {
156    fn default() -> Self {
157        Self::new()
158    }
159}