4

Procedural macros live in their own crates, which are compiled for the development machine (so that they can be executed when crates that use them are compiled). Any conditional compilation directives within the procedural macro crates will accordingly be based on their compilation environment, rather than that of the invoking crate.

Of course, such macros could expand to tokens that include conditional compilation directives that will then be evaluated in the context of the invoking crate's compilation—however, this is not always possible or desirable.

Where one wishes for the expanded tokens themselves to be some function of the invoking crate's compilation environment, there is a need for the macro to determine that environment during its run-time (which is of course the invoking crate's compilation-time). Clearly a perfect use-case for the std::env module.

However, rustc doesn't set any environment variables; and Cargo sets only a limited few. In particular, some key information (like target architecture/operating system etc) is not present at all.

I appreciate that a build script in the invoking crate could set environment variables for the macro then to read, but this places an unsatisfactory burden on the invoking crate author.

Is there any way that a proc macro author can obtain runtime information about the invoking crate's compilation environment (target architecture and operating system being of most interest to me)?

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
eggyal
  • 122,705
  • 18
  • 212
  • 237

2 Answers2

5

I've somewhat inelegantly solved this by recursing into a second proc macro invocation, where the first invocation adds #[cfg_attr] attributes with literal boolean parameters that can then be accessed within the second invocation:

#[cfg_attr(all(target_os = "linux"), my_macro(linux = true ))]
#[cfg_attr(not(target_os = "linux"), my_macro(linux = false))]
// ...

A hack, but it works.

eggyal
  • 122,705
  • 18
  • 212
  • 237
2

I found another solution:

Instead of generating the code depending on a flag like that, you can generate the code for all the OS and use #[cfg(...)] inside the quoted code.

quote! {
    #[cfg(linux)]
    {
        // linux specific stuff
    }

    #[cfg(not(linux))]
    {
        // not linux specific stuff
    }
}

This is probably cleaner.

Cecile
  • 1,553
  • 1
  • 15
  • 26
  • 2
    This solution is described in the second paragraph of the question: "*Of course, such macros could expand to tokens that include conditional compilation directives that will then be evaluated in the context of the invoking crate's compilation—however, this is not always possible or desirable.*" though I do not now recall why it was either impossible or undesirable in my particular use-case at that time. – eggyal Mar 21 '22 at 06:58
  • Oh wow sorry I barely read the question. I jumped too fast to the answers. Well you could have issues generating the very code meant for a different architecture I guess... but I don't have an example in mind. I guess the most KISS solution is conditional compilation directive – Cecile Mar 22 '22 at 07:39