Skip to content

Commit ce66520

Browse files
committed
Copy lockfile when building build scripts
1 parent 7950da3 commit ce66520

File tree

4 files changed

+525
-73
lines changed

4 files changed

+525
-73
lines changed

crates/project-model/src/build_dependencies.rs

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ use la_arena::ArenaMap;
1616
use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
1717
use rustc_hash::{FxHashMap, FxHashSet};
1818
use serde::Deserialize as _;
19-
use stdx::{always, never};
19+
use stdx::never;
2020
use toolchain::Tool;
2121

2222
use crate::{
2323
CargoConfig, CargoFeatures, CargoWorkspace, InvocationStrategy, ManifestPath, Package, Sysroot,
24-
TargetKind, utf8_stdout,
24+
TargetKind, cargo_config_file::make_lockfile_copy,
25+
cargo_workspace::MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH, utf8_stdout,
2526
};
2627

2728
/// Output of the build script and proc-macro building steps for a workspace.
@@ -77,7 +78,7 @@ impl WorkspaceBuildScripts {
7778
let current_dir = workspace.workspace_root();
7879

7980
let allowed_features = workspace.workspace_features();
80-
let cmd = Self::build_command(
81+
let (_guard, cmd) = Self::build_command(
8182
config,
8283
&allowed_features,
8384
workspace.manifest_path(),
@@ -98,7 +99,7 @@ impl WorkspaceBuildScripts {
9899
) -> io::Result<Vec<WorkspaceBuildScripts>> {
99100
assert_eq!(config.invocation_strategy, InvocationStrategy::Once);
100101

101-
let cmd = Self::build_command(
102+
let (_guard, cmd) = Self::build_command(
102103
config,
103104
&Default::default(),
104105
// This is not gonna be used anyways, so just construct a dummy here
@@ -379,10 +380,6 @@ impl WorkspaceBuildScripts {
379380
progress(format!(
380381
"building compile-time-deps: proc-macro {name} built"
381382
));
382-
always!(
383-
data.proc_macro_dylib_path == ProcMacroDylibPath::NotBuilt,
384-
"received multiple compiler artifacts for the same package: {message:?}"
385-
);
386383
if data.proc_macro_dylib_path == ProcMacroDylibPath::NotBuilt {
387384
data.proc_macro_dylib_path = ProcMacroDylibPath::NotProcMacro;
388385
}
@@ -434,14 +431,15 @@ impl WorkspaceBuildScripts {
434431
current_dir: &AbsPath,
435432
sysroot: &Sysroot,
436433
toolchain: Option<&semver::Version>,
437-
) -> io::Result<Command> {
434+
) -> io::Result<(Option<temp_dir::TempDir>, Command)> {
438435
match config.run_build_script_command.as_deref() {
439436
Some([program, args @ ..]) => {
440437
let mut cmd = toolchain::command(program, current_dir, &config.extra_env);
441438
cmd.args(args);
442-
Ok(cmd)
439+
Ok((None, cmd))
443440
}
444441
_ => {
442+
let mut requires_unstable_options = false;
445443
let mut cmd = sysroot.tool(Tool::Cargo, current_dir, &config.extra_env);
446444

447445
cmd.args(["check", "--quiet", "--workspace", "--message-format=json"]);
@@ -457,7 +455,19 @@ impl WorkspaceBuildScripts {
457455
if let Some(target) = &config.target {
458456
cmd.args(["--target", target]);
459457
}
460-
458+
let mut temp_dir_guard = None;
459+
if toolchain
460+
.is_some_and(|v| *v >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH)
461+
{
462+
let lockfile_path =
463+
<_ as AsRef<Utf8Path>>::as_ref(manifest_path).with_extension("lock");
464+
if let Some((temp_dir, target_lockfile)) = make_lockfile_copy(&lockfile_path) {
465+
temp_dir_guard = Some(temp_dir);
466+
cmd.arg("--lockfile-path");
467+
cmd.arg(target_lockfile.as_str());
468+
requires_unstable_options = true;
469+
}
470+
}
461471
match &config.features {
462472
CargoFeatures::All => {
463473
cmd.arg("--all-features");
@@ -479,6 +489,7 @@ impl WorkspaceBuildScripts {
479489
}
480490

481491
if manifest_path.is_rust_manifest() {
492+
requires_unstable_options = true;
482493
cmd.arg("-Zscript");
483494
}
484495

@@ -488,7 +499,7 @@ impl WorkspaceBuildScripts {
488499
// available in current toolchain's cargo, use it to build compile time deps only.
489500
const COMP_TIME_DEPS_MIN_TOOLCHAIN_VERSION: semver::Version = semver::Version {
490501
major: 1,
491-
minor: 89,
502+
minor: 189,
492503
patch: 0,
493504
pre: semver::Prerelease::EMPTY,
494505
build: semver::BuildMetadata::EMPTY,
@@ -498,8 +509,7 @@ impl WorkspaceBuildScripts {
498509
toolchain.is_some_and(|v| *v >= COMP_TIME_DEPS_MIN_TOOLCHAIN_VERSION);
499510

500511
if cargo_comp_time_deps_available {
501-
cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
502-
cmd.arg("-Zunstable-options");
512+
requires_unstable_options = true;
503513
cmd.arg("--compile-time-deps");
504514
// we can pass this unconditionally, because we won't actually build the
505515
// binaries, and as such, this will succeed even on targets without libtest
@@ -522,7 +532,11 @@ impl WorkspaceBuildScripts {
522532
cmd.env("RA_RUSTC_WRAPPER", "1");
523533
}
524534
}
525-
Ok(cmd)
535+
if requires_unstable_options {
536+
cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
537+
cmd.arg("-Zunstable-options");
538+
}
539+
Ok((temp_dir_guard, cmd))
526540
}
527541
}
528542
}

crates/project-model/src/cargo_config_file.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//! Read `.cargo/config.toml` as a JSON object
2+
use paths::{Utf8Path, Utf8PathBuf};
23
use rustc_hash::FxHashMap;
34
use toolchain::Tool;
45

@@ -32,3 +33,24 @@ pub(crate) fn read(
3233

3334
Some(json)
3435
}
36+
37+
pub(crate) fn make_lockfile_copy(
38+
lockfile_path: &Utf8Path,
39+
) -> Option<(temp_dir::TempDir, Utf8PathBuf)> {
40+
let temp_dir = temp_dir::TempDir::with_prefix("rust-analyzer").ok()?;
41+
let target_lockfile = temp_dir.path().join("Cargo.lock").try_into().ok()?;
42+
match std::fs::copy(lockfile_path, &target_lockfile) {
43+
Ok(_) => {
44+
tracing::debug!("Copied lock file from `{}` to `{}`", lockfile_path, target_lockfile);
45+
Some((temp_dir, target_lockfile))
46+
}
47+
// lockfile does not yet exist, so we can just create a new one in the temp dir
48+
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Some((temp_dir, target_lockfile)),
49+
Err(e) => {
50+
tracing::warn!(
51+
"Failed to copy lock file from `{lockfile_path}` to `{target_lockfile}`: {e}",
52+
);
53+
None
54+
}
55+
}
56+
}

crates/project-model/src/cargo_workspace.rs

Lines changed: 25 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@ use span::Edition;
1515
use stdx::process::spawn_with_streaming_output;
1616
use toolchain::Tool;
1717

18+
use crate::cargo_config_file::make_lockfile_copy;
1819
use crate::{CfgOverrides, InvocationStrategy};
1920
use crate::{ManifestPath, Sysroot};
2021

21-
const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH: semver::Version = semver::Version {
22-
major: 1,
23-
minor: 82,
24-
patch: 0,
25-
pre: semver::Prerelease::EMPTY,
26-
build: semver::BuildMetadata::EMPTY,
27-
};
22+
pub(crate) const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH: semver::Version =
23+
semver::Version {
24+
major: 1,
25+
minor: 82,
26+
patch: 0,
27+
pre: semver::Prerelease::EMPTY,
28+
build: semver::BuildMetadata::EMPTY,
29+
};
2830

2931
/// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
3032
/// workspace. It pretty closely mirrors `cargo metadata` output.
@@ -552,8 +554,10 @@ impl CargoWorkspace {
552554

553555
pub(crate) struct FetchMetadata {
554556
command: cargo_metadata::MetadataCommand,
557+
#[expect(dead_code)]
555558
manifest_path: ManifestPath,
556559
lockfile_path: Option<Utf8PathBuf>,
560+
#[expect(dead_code)]
557561
kind: &'static str,
558562
no_deps: bool,
559563
no_deps_result: anyhow::Result<cargo_metadata::Metadata>,
@@ -634,7 +638,7 @@ impl FetchMetadata {
634638
command.other_options(other_options.clone());
635639

636640
if needs_nightly {
637-
command.env("RUSTC_BOOTSTRAP", "1");
641+
command.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
638642
}
639643

640644
// Pre-fetch basic metadata using `--no-deps`, which:
@@ -681,11 +685,12 @@ impl FetchMetadata {
681685
locked: bool,
682686
progress: &dyn Fn(String),
683687
) -> anyhow::Result<(cargo_metadata::Metadata, Option<anyhow::Error>)> {
688+
_ = target_dir;
684689
let Self {
685690
mut command,
686-
manifest_path,
691+
manifest_path: _,
687692
lockfile_path,
688-
kind,
693+
kind: _,
689694
no_deps,
690695
no_deps_result,
691696
mut other_options,
@@ -696,54 +701,18 @@ impl FetchMetadata {
696701
}
697702

698703
let mut using_lockfile_copy = false;
699-
let mut _temp_dir_guard = None;
700-
// The manifest is a rust file, so this means its a script manifest
701-
if let Some(lockfile) = lockfile_path {
702-
_temp_dir_guard = temp_dir::TempDir::with_prefix("rust-analyzer").ok();
703-
let target_lockfile = _temp_dir_guard
704-
.and_then(|tmp| tmp.path().join("Cargo.lock").try_into().ok())
705-
.unwrap_or_else(|| {
706-
// When multiple workspaces share the same target dir, they might overwrite into a
707-
// single lockfile path.
708-
// See https://github.com/rust-lang/rust-analyzer/issues/20189#issuecomment-3073520255
709-
let manifest_path_hash = std::hash::BuildHasher::hash_one(
710-
&std::hash::BuildHasherDefault::<rustc_hash::FxHasher>::default(),
711-
&manifest_path,
712-
);
713-
let disambiguator = format!(
714-
"{}_{manifest_path_hash}",
715-
manifest_path.components().nth_back(1).map_or("", |c| c.as_str())
716-
);
717-
718-
target_dir
719-
.join("rust-analyzer")
720-
.join("metadata")
721-
.join(kind)
722-
.join(disambiguator)
723-
.join("Cargo.lock")
724-
});
725-
match std::fs::copy(&lockfile, &target_lockfile) {
726-
Ok(_) => {
727-
using_lockfile_copy = true;
728-
other_options.push("--lockfile-path".to_owned());
729-
other_options.push(target_lockfile.to_string());
730-
}
731-
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
732-
// There exists no lockfile yet
733-
using_lockfile_copy = true;
734-
other_options.push("--lockfile-path".to_owned());
735-
other_options.push(target_lockfile.to_string());
736-
}
737-
Err(e) => {
738-
tracing::warn!(
739-
"Failed to copy lock file from `{lockfile}` to `{target_lockfile}`: {e}",
740-
);
741-
}
742-
}
704+
let mut _temp_dir_guard;
705+
if let Some(lockfile) = lockfile_path
706+
&& let Some((temp_dir, target_lockfile)) = make_lockfile_copy(&lockfile)
707+
{
708+
_temp_dir_guard = temp_dir;
709+
other_options.push("--lockfile-path".to_owned());
710+
other_options.push(target_lockfile.to_string());
711+
using_lockfile_copy = true;
743712
}
744713
if using_lockfile_copy {
714+
command.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
745715
other_options.push("-Zunstable-options".to_owned());
746-
command.env("RUSTC_BOOTSTRAP", "1");
747716
}
748717
// No need to lock it if we copied the lockfile, we won't modify the original after all/
749718
// This way cargo cannot error out on us if the lockfile requires updating.
@@ -752,13 +721,11 @@ impl FetchMetadata {
752721
}
753722
command.other_options(other_options);
754723

755-
// FIXME: Fetching metadata is a slow process, as it might require
756-
// calling crates.io. We should be reporting progress here, but it's
757-
// unclear whether cargo itself supports it.
758724
progress("cargo metadata: started".to_owned());
759725

760726
let res = (|| -> anyhow::Result<(_, _)> {
761727
let mut errored = false;
728+
tracing::debug!("Running `{:?}`", command.cargo_command());
762729
let output =
763730
spawn_with_streaming_output(command.cargo_command(), &mut |_| (), &mut |line| {
764731
errored = errored || line.starts_with("error") || line.starts_with("warning");

0 commit comments

Comments
 (0)