autocxx_parser/
file_locations.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use proc_macro2::TokenStream;
use quote::quote;
use std::{fs::File, path::PathBuf};

use crate::{multi_bindings::MultiBindings, IncludeCppConfig};

/// The strategy used to generate, and to find generated, files.
/// As standard, these are based off the OUT_DIR set by Cargo,
/// but our code can't assume it can read OUT_DIR as it may
/// be running in the rust-analyzer proc macro server where it's
/// not available. We need to give provision for custom locations
/// and we need our code generator build script to be able to pass
/// locations through to the proc macro by env vars.
///
/// On the whole this class concerns itself with directory names
/// and allows the actual file name to be determined elsewhere
/// (based on a hash of the contents of `include_cpp!`.) But
/// some types of build system need to know the precise file _name_
/// produced by the codegen phase and passed into the macro phase,
/// so we have some options for that. See `gen --help` for details.
pub enum FileLocationStrategy {
    Custom(PathBuf),
    FromAutocxxRsFile(PathBuf),
    FromAutocxxRs(PathBuf),
    FromOutDir(PathBuf),
    FromAutocxxRsJsonArchive(PathBuf),
    UnknownMaybeFromOutdir,
}

static BUILD_DIR_NAME: &str = "autocxx-build-dir";
static RS_DIR_NAME: &str = "rs";
static AUTOCXX_RS: &str = "AUTOCXX_RS";
static AUTOCXX_RS_FILE: &str = "AUTOCXX_RS_FILE";
static AUTOCXX_RS_JSON_ARCHIVE: &str = "AUTOCXX_RS_JSON_ARCHIVE";

impl FileLocationStrategy {
    pub fn new() -> Self {
        match std::env::var_os(AUTOCXX_RS_JSON_ARCHIVE) {
            Some(of) => FileLocationStrategy::FromAutocxxRsJsonArchive(PathBuf::from(of)),
            None => match std::env::var_os(AUTOCXX_RS_FILE) {
                Some(of) => FileLocationStrategy::FromAutocxxRsFile(PathBuf::from(of)),
                None => match std::env::var_os(AUTOCXX_RS) {
                    None => match std::env::var_os("OUT_DIR") {
                        None => FileLocationStrategy::UnknownMaybeFromOutdir,
                        Some(od) => FileLocationStrategy::FromOutDir(PathBuf::from(od)),
                    },
                    Some(acrs) => FileLocationStrategy::FromAutocxxRs(PathBuf::from(acrs)),
                },
            },
        }
    }

    pub fn new_custom(gen_dir: PathBuf) -> Self {
        FileLocationStrategy::Custom(gen_dir)
    }

    /// Make a macro to include a given generated Rust file name.
    /// This can't simply be calculated from `get_rs_dir` because
    /// of limitations in rust-analyzer.
    pub fn make_include(&self, config: &IncludeCppConfig) -> TokenStream {
        match self {
            FileLocationStrategy::FromAutocxxRs(custom_dir) => {
                let fname = config.get_rs_filename();
                let fname = custom_dir.join(fname).to_str().unwrap().to_string();
                quote! {
                    include!( #fname );
                }
            }
            FileLocationStrategy::Custom(_) => panic!("Should never happen in the macro"),
            FileLocationStrategy::UnknownMaybeFromOutdir | FileLocationStrategy::FromOutDir(_) => {
                let fname = config.get_rs_filename();
                let fname = format!("/{BUILD_DIR_NAME}/{RS_DIR_NAME}/{fname}");
                // rust-analyzer works better if we ask Rust to do the path
                // concatenation rather than doing it in proc-macro code.
                // proc-macro code does not itself have access to the value of
                // OUT_DIR, but if we put it into a macro like the below,
                // rust-analyzer can cope.
                quote! {
                    include!(concat!(env!("OUT_DIR"), #fname));
                }
            }
            FileLocationStrategy::FromAutocxxRsFile(fname) => {
                let fname = fname
                    .to_str()
                    .expect("AUTOCXX_RS_FILE environment variable contained non-UTF8 characters");
                quote! {
                    include!( #fname );
                }
            }
            FileLocationStrategy::FromAutocxxRsJsonArchive(fnames) => {
                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()));
                let multi_bindings: MultiBindings = serde_json::from_reader(archive)
                    .unwrap_or_else(|_| {
                        panic!("Unable to interpret {} as JSON", fnames.to_string_lossy())
                    });
                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))
            }
        }
    }

    fn get_gen_dir(&self, suffix: &str) -> PathBuf {
        let root = match self {
            FileLocationStrategy::Custom(gen_dir)
            | FileLocationStrategy::FromAutocxxRs(gen_dir) => gen_dir.clone(),
            FileLocationStrategy::FromOutDir(out_dir) => out_dir.join(BUILD_DIR_NAME),
            FileLocationStrategy::UnknownMaybeFromOutdir => {
                panic!("Could not determine OUT_DIR or AUTOCXX_RS dir")
            }
            FileLocationStrategy::FromAutocxxRsFile(_) => {
                panic!("It's invalid to set AUTOCXX_RS_FILE during the codegen phase.")
            }
            FileLocationStrategy::FromAutocxxRsJsonArchive(_) => {
                panic!("It's invalid to set AUTOCXX_RS_JSON_ARCHIVE during the codegen phase.")
            }
        };
        root.join(suffix)
    }

    /// Location to generate Rust files.
    pub fn get_rs_dir(&self) -> PathBuf {
        self.get_gen_dir(RS_DIR_NAME)
    }

    /// Location to generate C++ header files.
    pub fn get_include_dir(&self) -> PathBuf {
        self.get_gen_dir("include")
    }

    /// Location to generate C++ code.
    pub fn get_cxx_dir(&self) -> PathBuf {
        self.get_gen_dir("cxx")
    }

    /// From a build script, inform cargo how to set environment variables
    /// to make them available to the procedural macro.
    pub fn set_cargo_env_vars_for_build(&self) {
        if let FileLocationStrategy::Custom(_) = self {
            println!(
                "cargo:rustc-env={}={}",
                AUTOCXX_RS,
                self.get_rs_dir().to_str().unwrap()
            );
        }
    }
}

impl Default for FileLocationStrategy {
    fn default() -> Self {
        Self::new()
    }
}