Is there a Cargo environment variable for the workspace directory?
Asked Answered
S

4

6

I have the following projects in a workspace:

Workspacefolder
 |
 +-- Project A
 |    |
 |    +-- build.rs
 |
 +-- Dep
 |    |
 |    +-- test.json  
 |
 +-Cargo.toml

In Project A, there is build.rs that wants to open test.json in a way that doesn't rely on platform and that works well with CI.

I'm looking for a CARGO_WORKSPACE environment variable, because then I can say Path::new(&workspace_dir).join("/Dep/test.json").

Strike answered 24/4, 2017 at 0:8 Comment(0)
D
3

No, not for the version of Cargo bundled with Rust 1.16.0. You can verify this yourself by printing out all of the environment variables in the build script:

use std::fs::File;
use std::io::Write;

fn main() {
    let mut dump = File::create("/tmp/dump").expect("unable to open");
    for (k, v) in std::env::vars() {
        writeln!(&mut dump, "{} -> {}", k, v).expect("unable to write")
    }
}

On my machine, this produces:

$ sort /tmp/dump | grep CARGO
CARGO_CFG_DEBUG_ASSERTIONS ->
CARGO_CFG_TARGET_ARCH -> x86_64
CARGO_CFG_TARGET_ENDIAN -> little
CARGO_CFG_TARGET_ENV ->
CARGO_CFG_TARGET_FAMILY -> unix
CARGO_CFG_TARGET_OS -> macos
CARGO_CFG_TARGET_POINTER_WIDTH -> 64
CARGO_CFG_UNIX ->
CARGO_HOME -> /Users/shep/.cargo
CARGO_MANIFEST_DIR -> /private/tmp/the-workspace/project-a
CARGO_PKG_AUTHORS -> An Devloper <[email protected]>
CARGO_PKG_DESCRIPTION ->
CARGO_PKG_HOMEPAGE ->
CARGO_PKG_NAME -> project-a
CARGO_PKG_VERSION -> 0.1.0
CARGO_PKG_VERSION_MAJOR -> 0
CARGO_PKG_VERSION_MINOR -> 1
CARGO_PKG_VERSION_PATCH -> 0
CARGO_PKG_VERSION_PRE ->

I'm not sure why you can't just do

Path::new(&manifest_dir).join("..").join("Dep").join("test.json")

I've split each directory into a separate call — avoiding the need to specify the directory separator at all to be platform agnostic.

Deejay answered 24/4, 2017 at 1:9 Comment(4)
This is, what I eventually did. I just wanted to verify there is a cleaner, less hacky way of achieving this.Strike
@DanielFath if it's the kind of thing you find useful, perhaps others would too. You could submit a PR to Cargo to add it! ^_^Deejay
Already made an issue, 2hrs ago I'll think about that PR.Strike
This solution might not be great if there's a library in a workspace that should work for projects that might not be in subdirectories that are direct children of the workspace root.Topsoil
H
7

There is a simpler way to do it now:

fn workspace_dir() -> PathBuf {
    let output = std::process::Command::new(env!("CARGO"))
        .arg("locate-project")
        .arg("--workspace")
        .arg("--message-format=plain")
        .output()
        .unwrap()
        .stdout;
    let cargo_path = Path::new(std::str::from_utf8(&output).unwrap().trim());
    cargo_path.parent().unwrap().to_path_buf()
}
Horst answered 28/12, 2022 at 15:30 Comment(0)
D
3

No, not for the version of Cargo bundled with Rust 1.16.0. You can verify this yourself by printing out all of the environment variables in the build script:

use std::fs::File;
use std::io::Write;

fn main() {
    let mut dump = File::create("/tmp/dump").expect("unable to open");
    for (k, v) in std::env::vars() {
        writeln!(&mut dump, "{} -> {}", k, v).expect("unable to write")
    }
}

On my machine, this produces:

$ sort /tmp/dump | grep CARGO
CARGO_CFG_DEBUG_ASSERTIONS ->
CARGO_CFG_TARGET_ARCH -> x86_64
CARGO_CFG_TARGET_ENDIAN -> little
CARGO_CFG_TARGET_ENV ->
CARGO_CFG_TARGET_FAMILY -> unix
CARGO_CFG_TARGET_OS -> macos
CARGO_CFG_TARGET_POINTER_WIDTH -> 64
CARGO_CFG_UNIX ->
CARGO_HOME -> /Users/shep/.cargo
CARGO_MANIFEST_DIR -> /private/tmp/the-workspace/project-a
CARGO_PKG_AUTHORS -> An Devloper <[email protected]>
CARGO_PKG_DESCRIPTION ->
CARGO_PKG_HOMEPAGE ->
CARGO_PKG_NAME -> project-a
CARGO_PKG_VERSION -> 0.1.0
CARGO_PKG_VERSION_MAJOR -> 0
CARGO_PKG_VERSION_MINOR -> 1
CARGO_PKG_VERSION_PATCH -> 0
CARGO_PKG_VERSION_PRE ->

I'm not sure why you can't just do

Path::new(&manifest_dir).join("..").join("Dep").join("test.json")

I've split each directory into a separate call — avoiding the need to specify the directory separator at all to be platform agnostic.

Deejay answered 24/4, 2017 at 1:9 Comment(4)
This is, what I eventually did. I just wanted to verify there is a cleaner, less hacky way of achieving this.Strike
@DanielFath if it's the kind of thing you find useful, perhaps others would too. You could submit a PR to Cargo to add it! ^_^Deejay
Already made an issue, 2hrs ago I'll think about that PR.Strike
This solution might not be great if there's a library in a workspace that should work for projects that might not be in subdirectories that are direct children of the workspace root.Topsoil
M
2

For cargo version 1.63.0, i managed with:

use std::{env, path::PathBuf, process::Command};

pub fn get_workspace_root() -> anyhow::Result<PathBuf> {
    let current_dir = env::current_dir()?;
    let cmd_output = Command::new("cargo")
        .args(["metadata", "--format-version=1"])
        .output()?;

    if !cmd_output.status.success() {
        return Ok(current_dir);
    }

    let json =
        serde_json::from_str::<serde_json::Value>(String::from_utf8(cmd_output.stdout)?.as_str())?;
    let path = match json.get("workspace_root") {
        Some(val) => match val.as_str() {
            Some(val) => val,
            None => return Ok(current_dir),
        },
        None => return Ok(current_dir),
    };
    Ok(PathBuf::from(path))
}
Machicolation answered 30/8, 2022 at 18:58 Comment(0)
C
1

In future, maybe CARGO_RUSTC_CURRENT_DIR could be use to do that, doc.

A good trick from RandomInsano is to use .cargo/config.toml:

[env]
CARGO_WORKSPACE_DIR = { value = "", relative = true }

Source

Crompton answered 11/6, 2024 at 8:59 Comment(1)
This is the better answerWaldrup

© 2022 - 2025 — McMap. All rights reserved.