rustenvironment-variablesbuild-scriptrust-proc-macros

Why does a procedural macro not see environment variables set by dotenv in my build script?


I have a procedural macro crate Proc and binary crate Bin. Bin has a dependency on Proc. Proc needs a filled environment variable to function properly.

This is some code inside my build.rs in Bin. Proc can successfully find the env value when using the following code:

fn main() {
    println!("cargo:rustc-env=SOME_ENV_VALUE=somevalue");
}

However, Proc fails to find the environment variable when using this code inside my build.rs in Bin (note: when checking the existence right after the dotenv call, I can verify the key is actually present):

fn main() {
    dotenv::dotenv().unwrap();
}

This is my Proc crate:

use proc_macro::TokenStream;

#[proc_macro_derive(MyProcMacro)]
pub fn my_proc_macro(input: TokenStream) -> TokenStream {
    if std::env::var("SOME_ENV_VALUE").is_err() {
        panic!("Failed to retrieve env value")
    }

    TokenStream::new()
}

Why won't it fail with the println! command? Can it work with dotenv? Else I need to write some code that copies the keys from my env file to the println! command...

All the code is in my minimal reproduction project.


Solution

  • I encourage you to re-read the Build Scripts chapter of the Cargo docs. A build script is a separate executable that Cargo builds and executes before starting to build your source code.

    1. Cargo starts
    2. Cargo executes rustc to build the build script
    3. Cargo executes the build script
    4. Cargo executes rustc to build your code
    5. Cargo exits

    [dotenv] loads environment variables from a .env file, if available, and mashes those with the actual environment variables provided by the operating system.

    Variables that it loads are placed into the current processes environment variables. In your example, that's the build script executable.

    [cargo:rustc-env] tells Cargo to set the given environment variable when compiling the package

    The stdout of the build script interacts with Cargo, which then modifies how the code is compiled, including what environment variables to set.


    You need to load the dotenv file and set the environment variables for the subsequent compilation. Something like this compiling-but-untested example:

    fn main() {
        if let Ok(env) = dotenv::dotenv_iter() {
            for (k,v) in env.flatten() {
                println!("cargo:rustc-env={}={}", k, v);
            }
        }
    }
    

    Don't worry that this method is marked as deprecated. The maintainers changed their minds.