Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CARGO is not Cargo when build script is invoked through cargo used as a library #10119

Closed
jonhoo opened this issue Nov 24, 2021 · 6 comments · Fixed by #11285
Closed

CARGO is not Cargo when build script is invoked through cargo used as a library #10119

jonhoo opened this issue Nov 24, 2021 · 6 comments · Fixed by #11285
Labels
A-cargo-api Area: cargo-the-library API and internal code issues C-bug Category: bug

Comments

@jonhoo
Copy link
Contributor

jonhoo commented Nov 24, 2021

Problem

When a program using cargo as a library initiates a build, the CARGO environment variable to build scripts is set to be the path to the current executable, even though that command may look nothing like Cargo. This, in turn, means that build scripts invoked as part as such builds may fail if they try to use CARGO as though it were Cargo, like for example cbindgen does:
https://github.com/eqrion/cbindgen/blob/93c06c5c9d319f481788c9670700097b4e46d270/src/bindgen/cargo/cargo_metadata.rs#L233-L235

Steps

#!/bin/bash

set -euo pipefail

rm -rf cargo-build-rs
mkdir cargo-build-rs
cd cargo-build-rs

cargo new cargo-something
pushd cargo-something
echo 'cargo = "0.57"' >> Cargo.toml
cat >src/main.rs <<EOF
use cargo::core::compiler::{BuildConfig, CompileMode};
use cargo::core::resolver::{CliFeatures};
use cargo::core::Workspace;
use cargo::ops::{CompileFilter, CompileOptions, Packages};
use cargo::Config;

fn main() {
  // This is the smallest working emulation of `cargo build` I could come up with.
  let mut config = Config::default().unwrap();
  config
    .configure(0, false, None, false, false, false, &None, &[], &[])
    .unwrap();
  let root = cargo::util::important_paths::find_root_manifest_for_wd(config.cwd()).unwrap();
  let ws = Workspace::new(&root, &config).unwrap();
  let build_config = BuildConfig::new(&config, None, &[], CompileMode::Build).unwrap();
  let cli_features = CliFeatures::from_command_line(&[], false, true).unwrap();
  let compile_opts = CompileOptions {
      build_config,
      cli_features,
      spec: Packages::from_flags(false, Vec::new(), Vec::new()).unwrap(),
      filter: CompileFilter::from_raw_arguments(
          false,
	  Vec::new(),
          false,
          Vec::new(),
          false,
          Vec::new(),
          false,
          Vec::new(),
          false,
          false,
      ),
      target_rustdoc_args: None,
      target_rustc_args: None,
      local_rustdoc_args: None,
      rustdoc_document_private_items: false,
      honor_rust_version: true,
  };
  cargo::ops::compile(&ws, &compile_opts).unwrap();
}
EOF
cargo build
popd

cargo new --lib has-buildrs
cd has-buildrs
cat >build.rs <<EOF
fn main() {
    println!("{}", std::env::var("CARGO").unwrap());
    assert!(false);
}
EOF
env PATH="$PWD/../cargo-something/target/debug:$PATH" cargo something

Yields

~/cargo-build-rs/cargo-something/target/debug/cargo-something

Note that a command like $CARGO metadata (like cbindgen executes) would fail in this case, since cargo-something != cargo .

Possible Solution(s)

This is a tough one. In the particular case where the binary invoking a build through cargo-as-a-library is a cargo external subcommand, perhaps the initiating cargo invocation could set CARGO (by some other name) that then gets picked up transparently by the build logic. But in the more general case, it seems like this needs to be settable through cargo::Config somewhere so that library users at least have a way to correct CARGO if they do invoke a build.

Notes

No response

Version

cargo 1.56.0 (4ed5d137b 2021-10-04)
release: 1.56.0
commit-hash: 4ed5d137baff5eccf1bae5a7b2ae4b57efad4a7d
commit-date: 2021-10-04
@jonhoo jonhoo added the C-bug Category: bug label Nov 24, 2021
@ehuss ehuss added the A-cargo-api Area: cargo-the-library API and internal code issues label Dec 6, 2021
@jonhoo
Copy link
Contributor Author

jonhoo commented Dec 7, 2021

It could be that one significant improvement here is to make the logic that sets CARGO should propagate an existing CARGO if it's already set. That way, if cargo-something is invoked as cargo something, the CARGO that the "outer" Cargo sets (which is truly Cargo) will carry through to the inner build script. It won't fix cases where Cargo isn't involved in the outer execution, but I think that's certainly the common case.

@jonhoo
Copy link
Contributor Author

jonhoo commented Dec 15, 2021

With #10115 closed (which I think is reasonable), I think it's even more important to figure out a solution to this, ideally using a single mechanism to address both cases.

@jonhoo
Copy link
Contributor Author

jonhoo commented Jun 17, 2022

To leave breadcrumbs to related issues, a very specific (but also slightly different) instantiation of this problem is #10113. Ideally a solution addresses both use-cases.

@jonhoo
Copy link
Contributor Author

jonhoo commented Oct 18, 2022

Also, just for convenience for readers of this issue, the place where Cargo discovers the current Cargo executable is

pub fn cargo_exe(&self) -> CargoResult<&Path> {

It's used here for external authentication commands:

.env("CARGO", config.cargo_exe()?)

Here for build scripts:

cmd.env(crate::CARGO_ENV, cargo_exe);

And here for fingerprinting:

let current = if key == CARGO_ENV {

@jonhoo
Copy link
Contributor Author

jonhoo commented Oct 18, 2022

I also just realized that Cargo already sets CARGO when it invokes subcommands:

cargo/src/bin/cargo/main.rs

Lines 193 to 195 in c71f344

let cargo_exe = config.cargo_exe()?;
let mut cmd = ProcessBuilder::new(&command);
cmd.env(cargo::CARGO_ENV, cargo_exe).args(args);

So I think there's a reasonable argument for saying that Cargo should prefer re-using an existing value for CARGO over overwriting it with what current_exe returns.

jonhoo pushed a commit to jonhoo/cargo that referenced this issue Oct 25, 2022
Currently, Cargo will always set `$CARGO` to point to what it detects
its own path to be (using `std::env::current_exe`). Unfortunately, this
runs into trouble when Cargo is used as a library, or when `current_exe`
is not actually the binary itself (e.g., when invoked through Valgrind
or `ld.so`), since `$CARGO` will not point at something that can be used
as `cargo`. This, in turn, means that users can't currently rely on
`$CARGO` to do the right thing, and will sometimes have to invoke
`cargo` directly from `$PATH` instead, which may not reflect the `cargo`
that's currently in use.

This patch makes Cargo re-use the existing value of `$CARGO` if it's
already set in the environment. For Cargo subcommands, this will mean
that the initial invocation of `cargo` in `cargo foo` will set `$CARGO`,
and then Cargo-as-a-library inside of `cargo-foo` will inherit that
(correct) value instead of overwriting it with the incorrect value
`cargo-foo`. For other execution environments that do not have `cargo`
in their call stack, it gives them the opportunity to set a working
value for `$CARGO`.

One note about the implementation of this is that the test suite now
needs to override `$CARGO` explicitly so that the _user's_ `$CARGO` does
not interfere with the contents of the tests. It _could_ remove `$CARGO`
instead, but overriding it seemed less error-prone.

Fixes rust-lang#10119.
Fixes rust-lang#10113.
@jonhoo
Copy link
Contributor Author

jonhoo commented Oct 25, 2022

I filed #11285 to fix this by forwarding CARGO if already set

bors added a commit that referenced this issue Nov 2, 2022
Make cargo forward pre-existing CARGO if set

Currently, Cargo will always set `$CARGO` to point to what it detects its own path to be (using `std::env::current_exe`). Unfortunately, this runs into trouble when Cargo is used as a library, or when `current_exe` is not actually the binary itself (e.g., when invoked through Valgrind or `ld.so`), since `$CARGO` will not point at something that can be used as `cargo`. This, in turn, means that users can't currently rely on `$CARGO` to do the right thing, and will sometimes have to invoke `cargo` directly from `$PATH` instead, which may not reflect the `cargo` that's currently in use.

This patch makes Cargo re-use the existing value of `$CARGO` if it's already set in the environment. For Cargo subcommands, this will mean that the initial invocation of `cargo` in `cargo foo` will set `$CARGO`, and then Cargo-as-a-library inside of `cargo-foo` will inherit that (correct) value instead of overwriting it with the incorrect value `cargo-foo`. For other execution environments that do not have `cargo` in their call stack, it gives them the opportunity to set a working value for `$CARGO`.

One note about the implementation of this is that the test suite now needs to override `$CARGO` explicitly so that the _user's_ `$CARGO` does not interfere with the contents of the tests. It _could_ remove `$CARGO` instead, but overriding it seemed less error-prone.

Fixes #10119.
Fixes #10113.
@bors bors closed this as completed in 724a197 Nov 2, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-cargo-api Area: cargo-the-library API and internal code issues C-bug Category: bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants