How to get the output file path in env?

I am using cargo to build something,

Now I want to get the output file path by code.

for example

cargo run --example call

and I can get the call.exe dir path target/debug/examples

or
cargo run --example call --release
and I can get the call.exe dir path target/release/examples/

or cargo run # application
dir path is target/debug

or cargo run --release # application
dir path is in target/release

so how can i get the dir path?

This information is available through the --message-format=json Cargo flag:

cargo b $RELEASE --example example_name -q --message-format=json
  • assuming $RELEASE either expands to '' or to '--release'

outputs:
{
  "reason": "compiler-artifact",
  "package_id": "crate_name 0.1.0 (path+file:///private/var/folders/m_/347zzk7j6nz08tt6z3tnd3z00000gn/T/tmp.7Cn3Cxiy)",
  "target": {
    "kind": [
      "lib"
    ],
    "crate_types": [
      "lib"
    ],
    "name": "crate_name",
    "src_path": "/private/var/folders/m_/347zzk7j6nz08tt6z3tnd3z00000gn/T/tmp.7Cn3Cxiy/src/lib.rs",
    "edition": "2018",
    "doctest": true
  },
  "profile": {
    "opt_level": "0",
    "debuginfo": 2,
    "debug_assertions": true,
    "overflow_checks": true,
    "test": false
  },
  "features": [],
  "filenames": [
    "/private/var/folders/m_/347zzk7j6nz08tt6z3tnd3z00000gn/T/tmp.7Cn3Cxiy/target/debug/deps/libcrate_name-67735c22f4f0185b.rlib",
    "/private/var/folders/m_/347zzk7j6nz08tt6z3tnd3z00000gn/T/tmp.7Cn3Cxiy/target/debug/deps/libcrate_name-67735c22f4f0185b.rmeta"
  ],
  "executable": null,
  "fresh": false
}
{
  "reason": "compiler-artifact",
  "package_id": "crate_name 0.1.0 (path+file:///private/var/folders/m_/347zzk7j6nz08tt6z3tnd3z00000gn/T/tmp.7Cn3Cxiy)",
  "target": {
    "kind": [
      "example"
    ],
    "crate_types": [
      "bin"
    ],
    "name": "example_name",
    "src_path": "/private/var/folders/m_/347zzk7j6nz08tt6z3tnd3z00000gn/T/tmp.7Cn3Cxiy/examples/example_name.rs",
    "edition": "2018",
    "doctest": false
  },
  "profile": {
    "opt_level": "0",
    "debuginfo": 2,
    "debug_assertions": true,
    "overflow_checks": true,
    "test": false
  },
  "features": [],
  "filenames": [
    "/private/var/folders/m_/347zzk7j6nz08tt6z3tnd3z00000gn/T/tmp.7Cn3Cxiy/target/debug/examples/example_name",
    "/private/var/folders/m_/347zzk7j6nz08tt6z3tnd3z00000gn/T/tmp.7Cn3Cxiy/target/debug/examples/example_name.dSYM"
  ],
  "executable": "/private/var/folders/m_/347zzk7j6nz08tt6z3tnd3z00000gn/T/tmp.7Cn3Cxiy/target/debug/examples/example_name",
  "fresh": false
}
{
  "reason": "build-finished",
  "success": true
}

I'll let you stare at the output, but you can notice that you can filter based on .target.kind being ["example"] (if you want to know about --example executables), and then .target.name being example_name.

Then, the field executable is the one that tells you where cargo has created the binary.

Proof-of-Concept

Hence, the following jq-based one-liner:

cargo build $RELEASE --example example_name -q --message-format=json \
    | jq -r -c '
        select(
            (.target.kind == ["example"])
            and
            (.target.name == "example_name")
        )
        | .executable
    '

which yields

/path/to/…/crate/target/release/examples/example_name

which, granted, seems to be an easy to guess file name not requiring all this:


Location of executables

  • examples/example_name.rs

    yields an executable at:

    target/{debug,release}/examples/example_name.ext
    
    Remarks regarding target/
    • Note that target/ is the default CARGO_TARGET_DIR, which can be overridden through env vars or .cargo/config keys, or simply be located in a parent directory if such directory is a workspace having the current crate as one of its members.

    • Note also that when cross-compiling, there will be an intermediate directory in between the CARGO_TARGET_DIR / target/ and the {debug,release} duet, named as the target triplet:

      <cargo-target-dir>{,/<target-triple>}/{debug,release}/…
      
  • src/main.rs or src/bin/crate_name/main.rs

    yields an executable at:

    target/{debug,release}/crate_name.ext
    
  • tests/test_name.rs

    yields an executable at:

    target/debug/deps/test_name-SOME_HASH.ext
    

    which is the one featuring a hard-to-predict file name because of that hash.

That's where parsing the JSON output of the --message-format=json output of Cargo comes in very handy :slightly_smiling_face:

Again, a jq proof-of-concept

Getting the exact path of a specific integration test binary (jq PoC)

Assuming a tests/test_name.rs file layout, we do:

cargo test $RELEASE --test test_name --no-run -q --message-format=json \
    | jq -r -c '
        select(
            (.target.kind == ["test"])
            and
            (.target.name == "test_name")
        )
        | .executable
    '

to obtain

/abs/path/to/…/target/…/{debug,release}/deps/test_name-<hash>{,.ext}

Getting the paths of all the integration tests under tests/

cargo test $RELEASE --no-run -q --message-format=json \
    | jq -r -c 'select(.target.kind == ["test"]) | .executable'

to obtain one path per line :slightly_smiling_face:

  • if you are worried about weird whitespace shenanigans, you can remove the -r from jq to get json-stringified versions of these paths, allowing to disambiguate the paths at the cost of having to perform the json-unstringification ourselves afterwards.
1 Like

Is it possible to get the path in the build.rs by env::var ?

The short answer is no :slightly_frowning_face: You can look at what env vars are available to a build.rs script here: Environment Variables - The Cargo Book

That being said, in practice,

  • if you are only interested in non-test binary paths, such as examples/example_name.rs or src{,/bin/crate_name}/main.rs

  • and if the target_dir has not been overridden (and you are not a subdirectory member of a workspace, although an heuristic can be written to handle this case),

  • and there is no cross-compilation going on (no --target … flag passed to Cargo),

then you can guess the location of the target/{debug,release} directory as:

let mut target_dir: ::std::path::PathBuf = 
    ::std::env::var_os("CARGO_MANIFEST_DIR")
        .unwrap()
        .into()
;
target_dir.push("target");
target_dir.push(::std::env::var_os("PROFILE").unwrap());
let target_dir = &*target_dir; // Done mutating

From there, you can have the location of an example named example_name with:

#[cfg(windows)]
const EXEC_EXT: &str = ".exe";

target_dir
    .join("examples")
    .join(&["example_name", EXEC_EXT].concat())

and for the binary:

target_dir
    .join(&["crate_name", EXEC_EXT].concat())
  • :thinking: there is no CARGO_PKG_NAME available to a build.rs script

all right. Thanks for your answer. maybe i should try to another way to solve question.

have a good day !

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.