How to prepend env var "PATH" in Rust cargo config.toml

  • This is a bump of stack overflow question.

I am using compile time inline-python and would like to use python v-env for building and testing.

Referring that a python v-env is activated by the following shell script, I believe it should be done by prepending the location of v-env/bin to the existing PATH for compile time and reverting on the competition.

...
VIRTUAL_ENV="/Users/<some-path>/<cargo-project>/venv"
export VIRTUAL_ENV

_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/bin:$PATH"
export PATH
...

Would it be a good way to go for the purpose? If so, how am I supposed to do this?

--- EDIT 23.02.13-1. This is necessary because when I print out the path and import the path like below with a test, they direct system root python.

#[cfg(test)]
mod tests {

    use std::env;

    use ct_python::ct_python;

    static python_bin: &'static str = ct_python! {
    import sys
    print('"' + "{}".format(sys.executable) + '"')
    };

    static python_path: &'static str = ct_python! {
    import sys
    print('"' + "{}".format(sys.path) + '"')
    };

    #[test]
    fn venv_test() {
        match env::var("VIRTUAL_ENV") {
            Ok(v) => println!("{}", v),
            Err(e) => println!("{}", e)
            
        }
    }

    #[test]
    fn path_print_test() {        
        println!("bin: {}", python_bin);
        println!("path: {}", python_path);
    }
}

output:

running 1 test
bin: /Users/<username>/.rustup/toolchains/nightly-aarch64-apple-darwin/bin/rustc
path: ['/opt/homebrew/Cellar/python@3.10/3.10.9/Frameworks/Python.framework/Versions/3.10/lib/python310.zip', '/opt/homebrew/Cellar/python@3.10/3.10.9/Frameworks/Python.framework/Versions/3.10/lib/python3.10', '/opt/homebrew/Cellar/python@3.10/3.10.9/Frameworks/Python.framework/Versions/3.10/lib/python3.10/lib-dynload', '/opt/homebrew/Cellar/python@3.10/3.10.9/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages', '/opt/homebrew/opt/python-tk@3.10/libexec']
test signal_proc::tests::path_print_test ... ok

I believe the output of the sys.path should be ./venv/bin in order to use arbitrary python libraries and it has to be "prepending" of PATH only for the cargo.

Try defining [env] table in config.toml: Configuration - The Cargo Book

[env]
# Set ENV_VAR_NAME=value for any process run by Cargo
ENV_VAR_NAME = "value"
# Set even if already present in environment
ENV_VAR_NAME_2 = { value = "value", force = true }
# Value is relative to .cargo directory containing `config.toml`, make absolute
ENV_VAR_NAME_3 = { value = "relative/path", relative = true }

Isn't it override the whole path if I do the below?

[env]
PATH = "<path of venv>"

It has to be "prepend" of PATH only for the cargo.

No. If you click the link, you'll see

By default, the variables specified will not override values that already exist in the environment. This behavior can be changed by setting the force flag.


Instead, you can use build.rs to prepend. Build Scripts - The Cargo Book

// build.rs
use std::{env, ffi::OsString, iter, path::Path};
fn main() {
    let debug_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("target/debug/");
    println!("cargo:rustc-env=PATH={:?}", prepend_path(debug_dir).unwrap());
}

fn prepend_path<P: AsRef<Path>>(p: P) -> Result<OsString, env::JoinPathsError> {
    let new_path = p.as_ref();
    if let Some(path) = env::var_os("PATH") {
        let old = env::split_paths(&path);
        Ok(env::join_paths(iter::once(new_path.to_owned()).chain(old))?)
    } else {
        Ok(new_path.into())
    }
}

// src/main.rs: test it!
fn main() {
    let dir = env!("CARGO_MANIFEST_DIR");
    let path = env!("PATH");
    eprintln!("PATH={path}\n\nCARGO_MANIFEST_DIR={dir}");
    assert!(path.contains(dir));
}

Update: thanks to @bjorn3 for referring to join_paths in std::env - Rust

You should use std::env::join_paths to join paths. This handles platform differences like windows using ; instead of :.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.