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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
#![feature(proc_macro_span)]

//! Getting the module path of the caller within a [`#[proc_macro_attribute]`](https://doc.rust-lang.org/nightly/book/ch19-06-macros.html#procedural-macros-for-generating-code-from-attributes).
//!
//! This crate provides two items:
//! - [`#[expose_caller_modpath]`](macro@expose_caller_modpath)
//! - [`CallerModpath::caller_modpath`](CallerModpath::caller_modpath)
//!
//! The first makes the second available; see the documentation on each for more information.
//!
//! ## Example
//!
//! ```
//! #[caller_modpath::expose_caller_modpath]
//! #[proc_macro_attribute]
//! pub fn test(_attr: TokenStream, _input: TokenStream) -> TokenStream {
//!     let modpath: String = proc_macro::Span::caller_modpath();
//!     // now do something with it. For example, just panic to have the compiler display the result:
//!     panic!(
//!         "module path of call site: {}",
//!         modpath
//!     );
//! }
//! ```
//!
//! ## Nightly Requirement
//! This crate internally uses the `proc_macro_span` feature.
//! This is only used to test the equality of two Spans.
//! If you know how to do this without nightly, file an issue or PR on the
//! [upstream GitHub repo](https://github.com/Shizcow/caller_modpath).
//! Help is greatly appreciated!
//!
//! ## Backend
//! This crate causes the macro invoker to call rustc on itself.
//! [`#[expose_caller_modpath]`](macro@expose_caller_modpath) performans some setup such that on the meta-call
//! the target macro essentially expands to
//! [`module_path!()`](https://doc.rust-lang.org/std/macro.module_path.html)
//! with some additional identifiers. These identifiers are read within
//! [`CallerModpath::caller_modpath`](CallerModpath::caller_modpath), which does the rustc meta-call,
//! parsing and returning the value.
//!
//! Because this crate does some terribly non-standard stuff, issues are bound to occur.
//! If you run into something, file an issue at the
//! [upstream GitHub repo](https://github.com/Shizcow/caller_modpath).

// yeah
extern crate proc_macro;

pub use caller_modpath_macros::*;
#[doc(hidden)]
pub use quote::quote;

use std::path::PathBuf;
use std::sync::RwLock;
use uuid::Uuid;

// use when we call rustc on ourself (this lib gets wild)
#[doc(hidden)]
pub static UUID_ENV_VAR_NAME: &str =
    concat!("CARGO_INJECT_", env!("CARGO_PKG_NAME"), "_SECOND_PASS_UUID");

// so Span is a really special type
// It is very dumb and implements no useful traits (Eq, Hash, Send, Sync, etc)
// A lot of this stuff is crazy because of that
// If this was better I'd stick it in a lazy_static HashMap and call it a day but sometype needs attention
thread_local! {
    static MODCACHE: RwLock<Vec<(proc_macro2::Span, ResolveStatus)>> = RwLock::new(vec![]);
}

enum ResolveStatus {
    Unresolved(&'static str), // crate name
    Resolved(String),         // module path name
}

// This trait is the main interface for this crate
/// Provides a way to fetch the module path of the caller.
///
/// ## Note:
/// [`#[expose_caller_modpath]`](macro@expose_caller_modpath) must be placed on the parent
/// [`#[proc_macro_attribute]`](https://doc.rust-lang.org/nightly/book/ch19-06-macros.html#procedural-macros-for-generating-code-from-attributes)
/// to enable. [`caller_modpath`](CallerModpath::caller_modpath) may be called from children functions, so long as
/// [`#[expose_caller_modpath]`](macro@expose_caller_modpath) is applied to the parent.
pub trait CallerModpath {
    /// Get the caller modpath.
    ///
    /// Fetching is done lazily and cahced into a static. So, the first call will be slow and subsequent
    /// calls will be much faster.
    fn caller_modpath() -> String;
}

impl CallerModpath for proc_macro::Span {
    fn caller_modpath() -> String {
        let call_site = proc_macro2::Span::call_site().unwrap();
        // First, try to find any mention of it (it's initialized by the macro)
        MODCACHE.with(move |m| {
	    // overwritten and used only when required
	    let mut need_to_write_index = None;
	    let mut newly_resolved = None;
	    { // this weird scope is so the mutex can be reused mutably later
		let locked = m.read().unwrap();
		for i in 0..locked.len() {
                    if locked[i].0.unwrap().eq(&call_site) {
			match locked[i].1 {
			    ResolveStatus::Resolved(ref modpath) => {
				// If we have calculated everything already, just return it
				return modpath.clone();
			    },
			    ResolveStatus::Unresolved(cratename) => {
				// Otherwise, calculate and continue
				let modpath = resolve_modpath(cratename);
				need_to_write_index = Some(i);
				newly_resolved = Some(modpath.to_owned());
			    },
			};
                    }
		};
	    }
	    // If we found no mention, the user forgot to set up
	    if need_to_write_index.is_none() {
		panic!("Attempt to call Span::caller_modpath() without first putting #[expose_caller_modpath] on the parent #[proc_macro_attribute]!");
	    }
	    // Otherise, do the calculation and cache+return the result
	    let mut write_lock = m.write().unwrap();
	    let modpath = newly_resolved.unwrap();
	    write_lock[need_to_write_index.unwrap()].1 = ResolveStatus::Resolved(modpath.clone());
	    modpath
        })
    }
}

// I just want this available for both types
/// This impl is for [`proc_macro2::Span`](https://docs.rs/proc-macro2/1.0.24/proc_macro2/struct.Span.html).
/// The backend is the exact same; this is just provided for convienience.
impl CallerModpath for proc_macro2::Span {
    fn caller_modpath() -> String {
        proc_macro::Span::caller_modpath()
    }
}

#[doc(hidden)]
pub fn gen_second_pass() -> proc_macro::TokenStream {
    let i = proc_macro2::Ident::new(
        &format!(
            "{}_UUID_{}",
            env!("CARGO_PKG_NAME"),
            std::env::var(UUID_ENV_VAR_NAME).unwrap()
        ),
        proc_macro2::Span::call_site(),
    );
    (quote! {
        static #i: &'static str = module_path!();
    })
    .into()
}

#[doc(hidden)]
pub fn gen_first_pass(client_proc_macro_crate_name: &'static str) {
    // Make sure we aren't logging the call site twice
    let call_site = proc_macro2::Span::call_site().unwrap();
    let already_calculated = MODCACHE.with(|m| {
        let locked = m.read().unwrap();
        for i in 0..locked.len() {
            if locked[i].0.unwrap().eq(&call_site) {
                return true;
            }
        }
        false
    });
    if already_calculated {
        return;
    }
    // Then just push an empty to be resolved when we actually ask for it
    MODCACHE.with(move |m| {
        m.write().unwrap().push((
            proc_macro2::Span::call_site(),
            ResolveStatus::Unresolved(client_proc_macro_crate_name),
        ))
    });
}

fn resolve_modpath(client_proc_macro_crate_name: &str) -> String {
    let entry_p = get_entrypoint();

    let uuid_string = Uuid::new_v4().to_string().replace("-", "_");

    let chosen_dir = find_lib_so(&client_proc_macro_crate_name);

    let liblink_path = format!("{}={}", client_proc_macro_crate_name, chosen_dir);

    let rustc_args = vec![
        "-Z",
        "unstable-options",
        "--pretty=expanded",
        "--color=never",
        "--extern",
        &liblink_path,
        entry_p.to_str().unwrap(),
    ];

    let proc = std::process::Command::new("rustc")
        .current_dir(std::env::var("CARGO_MANIFEST_DIR").unwrap())
        .args(&rustc_args)
        .env(UUID_ENV_VAR_NAME, &uuid_string)
        .output()
        .expect("failed to execute a second pass of rustc");

    String::from_utf8_lossy(&proc.stdout).split(&uuid_string)
        .nth(1)
        .unwrap_or_else(|| panic!("Failed to find internal UUID; rustc metacall probably faliled. Called as `rustc {}`. Stderr:\n{}", rustc_args.join(" "), String::from_utf8_lossy(&proc.stderr)))
        .chars()
        .skip_while(|c| c != &'"')
        .skip(1)
        .take_while(|c| c != &'"')
        .collect()
}

fn get_entrypoint() -> PathBuf {
    let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());

    if let Ok(bin_name) = std::env::var("CARGO_BIN_NAME") {
        // binary: need to parse targets in Cargo.toml to find the correct path

        let manifest = cargo_manifest::Manifest::from_path(manifest_dir.join("Cargo.toml"))
            .expect("Could not parse Cargo.toml of caller");

        let rustc_entry = manifest.bin.unwrap().into_iter().find(|target| target.name.as_ref() == Some(&bin_name)).expect("Could not get binary target path from Cargo.toml. If you are manually specifying targets, make sure the path is included as well.").path.unwrap();

        manifest_dir.join(rustc_entry)
    } else {
        // just a library: can assume it's just src/lib.rs
        manifest_dir.join("src").join("lib.rs")
    }
}

fn find_lib_so(libname: &str) -> String {
    let target_path = std::env::current_dir()
        .expect("Could not get current dir from env")
        .join("target")
        .join(if cfg!(debug_assertions) {
            "debug"
        } else {
            "release"
        });

    // need to look in two places:
    // target/{}/deps/ for crate dependencies
    let dep_p = target_path
        .join("deps")
        .join(format!("lib{}-*.so", libname))
        .into_os_string();

    let dep_str = dep_p.to_string_lossy();

    // and target/{}/ for workspace targets
    let t_p = target_path.join(format!("lib{}.so", libname));

    let mut file_candidates: Vec<_> = glob::glob(&dep_str)
        .expect("Failed to read library glob pattern")
        .into_iter()
        .filter_map(|entry| entry.ok())
        .collect();

    file_candidates.push(t_p);

    let fstr = file_candidates
        .iter()
        .map(|p| p.to_string_lossy())
        .collect::<Vec<_>>()
        .join(" ");

    file_candidates
        .into_iter()
        .filter_map(|entry| {
            std::fs::metadata(&entry)
                .and_then(|f| f.accessed())
                .ok()
                .map(|t| (entry, t))
        })
        .max()
        .map(|(f, _)| f)
        .unwrap_or_else(|| {
            panic!(
                "Could not find suitable backend library paths from file list {}",
                fstr
            )
        })
        .into_os_string()
        .to_string_lossy()
        .to_string()
}