Skip to content

Commit 23424fd

Browse files
committed
Auto merge of #11067 - tedinski:install_workspace_root, r=weihanglo
Ignore `workspace.default-members` when running `cargo install` on root package of a non-virtual workspace ### What does this PR try to resolve? * Fixes #11058 Two observable behaviors are fixed: 1. When running `cargo install` with `--path` or `--git` and specifically requesting the root package of a non-virtual workspace, `cargo install` will accidentally build `workspace.default-members` instead of the requested root package. 2. Further, if that `default-members` does not include the root package, it will install binaries from those other packages (the `default-members`) and claim they were the binaries from the root package! There is no way, actually, to install the root package binaries. These two behaviors have the same root cause: * `cargo install` effectively does `cargo build --release` in the requested package directory, but when this is the root of a non-virtual workspace, that builds `default-members` instead of the requested package. ### How should we test and review this PR? I have included a test exhibiting this behavior. It currently fails in the manner indicated in the test, and passes with the changes included in this PR. I'm not sure the solution in the PR is the _best_ solution, but the alternative I am able to come up with involves much more extensive changes to how `cargo install` works, to produce a distinct `CompileOptions` for every package built. I tried to keep the new workspace "API" `ignore_default_members()` as narrowly-scoped in its effect as possible. ### Additional information The only way I could think this behavior change could impact someone is if they were somehow using `cargo install --path` (or `--git`) and wanting it to actually install the binaries from all of `default-members`. However, I don't believe that's possible, since if there are multiple packages with binaries, I believe cargo requires the packages to be specified. So someone would have to be additionally relying on specifying just the root package, but then wanting the binaries from more than just the root. I think this is probably an acceptable risk for merging!
2 parents fc2242a + 7dc8be5 commit 23424fd

File tree

6 files changed

+63
-15
lines changed

6 files changed

+63
-15
lines changed

src/cargo/core/compiler/build_config.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ use cargo_util::ProcessBuilder;
66
use serde::ser;
77
use std::cell::RefCell;
88
use std::path::PathBuf;
9+
use std::sync::Arc;
910
use std::thread::available_parallelism;
1011

1112
/// Configuration information for a rustc build.
12-
#[derive(Debug)]
13+
#[derive(Debug, Clone)]
1314
pub struct BuildConfig {
1415
/// The requested kind of compilation for this session
1516
pub requested_kinds: Vec<CompileKind>,
@@ -33,7 +34,7 @@ pub struct BuildConfig {
3334
pub primary_unit_rustc: Option<ProcessBuilder>,
3435
/// A thread used by `cargo fix` to receive messages on a socket regarding
3536
/// the success/failure of applying fixes.
36-
pub rustfix_diagnostic_server: RefCell<Option<RustfixDiagnosticServer>>,
37+
pub rustfix_diagnostic_server: Arc<RefCell<Option<RustfixDiagnosticServer>>>,
3738
/// The directory to copy final artifacts to. Note that even if `out_dir` is
3839
/// set, a copy of artifacts still could be found a `target/(debug\release)`
3940
/// as usual.
@@ -100,7 +101,7 @@ impl BuildConfig {
100101
build_plan: false,
101102
unit_graph: false,
102103
primary_unit_rustc: None,
103-
rustfix_diagnostic_server: RefCell::new(None),
104+
rustfix_diagnostic_server: Arc::new(RefCell::new(None)),
104105
export_dir: None,
105106
future_incompat_report: false,
106107
timing_outputs: Vec::new(),

src/cargo/ops/cargo_compile/compile_filter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub enum FilterRule {
3434
///
3535
/// [`generate_root_units`]: super::UnitGenerator::generate_root_units
3636
/// [`Packages`]: crate::ops::Packages
37-
#[derive(Debug)]
37+
#[derive(Debug, Clone)]
3838
pub enum CompileFilter {
3939
/// The default set of Cargo targets.
4040
Default {

src/cargo/ops/cargo_compile/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub use packages::Packages;
6868
/// of it as `CompileOptions` are high-level settings requested on the
6969
/// command-line, and `BuildConfig` are low-level settings for actually
7070
/// driving `rustc`.
71-
#[derive(Debug)]
71+
#[derive(Debug, Clone)]
7272
pub struct CompileOptions {
7373
/// Configuration information for a rustc build
7474
pub build_config: BuildConfig,

src/cargo/ops/cargo_compile/packages.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use anyhow::{bail, Context as _};
1313
///
1414
/// Generally, it represents the combination of all `-p` flag. When working within
1515
/// a workspace, `--exclude` and `--workspace` flags also contribute to it.
16-
#[derive(PartialEq, Eq, Debug)]
16+
#[derive(PartialEq, Eq, Debug, Clone)]
1717
pub enum Packages {
1818
/// Packages selected by default. Usually means no flag provided.
1919
Default,

src/cargo/ops/cargo_install.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use std::{env, fs};
55

66
use crate::core::compiler::{CompileKind, DefaultExecutor, Executor, UnitOutput};
77
use crate::core::{Dependency, Edition, Package, PackageId, Source, SourceId, Workspace};
8-
use crate::ops::CompileFilter;
98
use crate::ops::{common_for_install_and_uninstall::*, FilterRule};
9+
use crate::ops::{CompileFilter, Packages};
1010
use crate::sources::{GitSource, PathSource, SourceConfigMap};
1111
use crate::util::errors::CargoResult;
1212
use crate::util::{Config, Filesystem, Rustc, ToSemver, VersionReqExt};
@@ -37,7 +37,7 @@ impl Drop for Transaction {
3737

3838
struct InstallablePackage<'cfg, 'a> {
3939
config: &'cfg Config,
40-
opts: &'a ops::CompileOptions,
40+
opts: ops::CompileOptions,
4141
root: Filesystem,
4242
source_id: SourceId,
4343
vers: Option<&'a str>,
@@ -60,7 +60,7 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> {
6060
source_id: SourceId,
6161
from_cwd: bool,
6262
vers: Option<&'a str>,
63-
opts: &'a ops::CompileOptions,
63+
original_opts: &'a ops::CompileOptions,
6464
force: bool,
6565
no_track: bool,
6666
needs_update_if_source_is_index: bool,
@@ -145,7 +145,7 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> {
145145
dep.clone(),
146146
&mut source,
147147
config,
148-
opts,
148+
original_opts,
149149
&root,
150150
&dst,
151151
force,
@@ -167,7 +167,14 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> {
167167
}
168168
};
169169

170-
let (ws, rustc, target) = make_ws_rustc_target(config, opts, &source_id, pkg.clone())?;
170+
// When we build this package, we want to build the *specified* package only,
171+
// and avoid building e.g. workspace default-members instead. Do so by constructing
172+
// specialized compile options specific to the identified package.
173+
// See test `path_install_workspace_root_despite_default_members`.
174+
let mut opts = original_opts.clone();
175+
opts.spec = Packages::Packages(vec![pkg.name().to_string()]);
176+
177+
let (ws, rustc, target) = make_ws_rustc_target(config, &opts, &source_id, pkg.clone())?;
171178
// If we're installing in --locked mode and there's no `Cargo.lock` published
172179
// ie. the bin was published before https://github.com/rust-lang/cargo/pull/7026
173180
if config.locked() && !ws.root().join("Cargo.lock").exists() {
@@ -235,7 +242,7 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> {
235242
// Check for conflicts.
236243
ip.no_track_duplicates(&dst)?;
237244
} else if is_installed(
238-
&ip.pkg, config, opts, &ip.rustc, &ip.target, &ip.root, &dst, force,
245+
&ip.pkg, config, &ip.opts, &ip.rustc, &ip.target, &ip.root, &dst, force,
239246
)? {
240247
let msg = format!(
241248
"package `{}` is already installed, use --force to override",
@@ -297,7 +304,7 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> {
297304
self.check_yanked_install()?;
298305

299306
let exec: Arc<dyn Executor> = Arc::new(DefaultExecutor);
300-
let compile = ops::compile_ws(&self.ws, self.opts, &exec).with_context(|| {
307+
let compile = ops::compile_ws(&self.ws, &self.opts, &exec).with_context(|| {
301308
if let Some(td) = td_opt.take() {
302309
// preserve the temporary directory, so the user can inspect it
303310
td.into_path();
@@ -372,7 +379,7 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> {
372379
&dst,
373380
&self.pkg,
374381
self.force,
375-
self.opts,
382+
&self.opts,
376383
&self.target,
377384
&self.rustc.verbose_version,
378385
)?;
@@ -439,7 +446,7 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> {
439446
&self.pkg,
440447
&successful_bins,
441448
self.vers.map(|s| s.to_string()),
442-
self.opts,
449+
&self.opts,
443450
&self.target,
444451
&self.rustc.verbose_version,
445452
);

tests/testsuite/install.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,46 @@ fn use_path_workspace() {
12921292
assert_eq!(lock, lock2, "different lockfiles");
12931293
}
12941294

1295+
#[cargo_test]
1296+
fn path_install_workspace_root_despite_default_members() {
1297+
let p = project()
1298+
.file(
1299+
"Cargo.toml",
1300+
r#"
1301+
[package]
1302+
name = "ws-root"
1303+
version = "0.1.0"
1304+
authors = []
1305+
1306+
[workspace]
1307+
members = ["ws-member"]
1308+
default-members = ["ws-member"]
1309+
"#,
1310+
)
1311+
.file("src/main.rs", "fn main() {}")
1312+
.file(
1313+
"ws-member/Cargo.toml",
1314+
r#"
1315+
[package]
1316+
name = "ws-member"
1317+
version = "0.1.0"
1318+
authors = []
1319+
"#,
1320+
)
1321+
.file("ws-member/src/main.rs", "fn main() {}")
1322+
.build();
1323+
1324+
p.cargo("install --path")
1325+
.arg(p.root())
1326+
.arg("ws-root")
1327+
.with_stderr_contains(
1328+
"[INSTALLED] package `ws-root v0.1.0 ([..])` (executable `ws-root[EXE]`)",
1329+
)
1330+
// Particularly avoid "Installed package `ws-root v0.1.0 ([..]])` (executable `ws-member`)":
1331+
.with_stderr_does_not_contain("ws-member")
1332+
.run();
1333+
}
1334+
12951335
#[cargo_test]
12961336
fn dev_dependencies_no_check() {
12971337
Package::new("foo", "1.0.0").publish();

0 commit comments

Comments
 (0)