Skip to content

Commit a31e10a

Browse files
authored
Merge pull request #20018 from Veykril/push-pkowrtoturkr
fix: Copy lockfiles into target directory before invoking `cargo metadata`
2 parents 8661c59 + c0f428d commit a31e10a

File tree

6 files changed

+176
-84
lines changed

6 files changed

+176
-84
lines changed

crates/project-model/src/cargo_workspace.rs

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use anyhow::Context;
77
use base_db::Env;
88
use cargo_metadata::{CargoOpt, MetadataCommand};
99
use la_arena::{Arena, Idx};
10-
use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
10+
use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf};
1111
use rustc_hash::{FxHashMap, FxHashSet};
1212
use serde_derive::Deserialize;
1313
use serde_json::from_value;
@@ -18,6 +18,14 @@ use toolchain::Tool;
1818
use crate::{CfgOverrides, InvocationStrategy};
1919
use crate::{ManifestPath, Sysroot};
2020

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+
};
28+
2129
/// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
2230
/// workspace. It pretty closely mirrors `cargo metadata` output.
2331
///
@@ -291,6 +299,13 @@ pub struct CargoMetadataConfig {
291299
pub extra_args: Vec<String>,
292300
/// Extra env vars to set when invoking the cargo command
293301
pub extra_env: FxHashMap<String, Option<String>>,
302+
/// The target dir for this workspace load.
303+
pub target_dir: Utf8PathBuf,
304+
/// What kind of metadata are we fetching: workspace, rustc, or sysroot.
305+
pub kind: &'static str,
306+
/// The toolchain version, if known.
307+
/// Used to conditionally enable unstable cargo features.
308+
pub toolchain_version: Option<semver::Version>,
294309
}
295310

296311
// Deserialize helper for the cargo metadata
@@ -383,17 +398,53 @@ impl CargoWorkspace {
383398
config.targets.iter().flat_map(|it| ["--filter-platform".to_owned(), it.clone()]),
384399
);
385400
}
401+
if no_deps {
402+
other_options.push("--no-deps".to_owned());
403+
}
404+
405+
let mut using_lockfile_copy = false;
386406
// The manifest is a rust file, so this means its a script manifest
387407
if cargo_toml.is_rust_manifest() {
388-
// Deliberately don't set up RUSTC_BOOTSTRAP or a nightly override here, the user should
389-
// opt into it themselves.
390408
other_options.push("-Zscript".to_owned());
409+
} else if config
410+
.toolchain_version
411+
.as_ref()
412+
.is_some_and(|v| *v >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH)
413+
{
414+
let lockfile = <_ as AsRef<Utf8Path>>::as_ref(cargo_toml).with_extension("lock");
415+
let target_lockfile = config
416+
.target_dir
417+
.join("rust-analyzer")
418+
.join("metadata")
419+
.join(config.kind)
420+
.join("Cargo.lock");
421+
match std::fs::copy(&lockfile, &target_lockfile) {
422+
Ok(_) => {
423+
using_lockfile_copy = true;
424+
other_options.push("--lockfile-path".to_owned());
425+
other_options.push(target_lockfile.to_string());
426+
}
427+
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
428+
// There exists no lockfile yet
429+
using_lockfile_copy = true;
430+
other_options.push("--lockfile-path".to_owned());
431+
other_options.push(target_lockfile.to_string());
432+
}
433+
Err(e) => {
434+
tracing::warn!(
435+
"Failed to copy lock file from `{lockfile}` to `{target_lockfile}`: {e}",
436+
);
437+
}
438+
}
391439
}
392-
if locked {
393-
other_options.push("--locked".to_owned());
440+
if using_lockfile_copy {
441+
other_options.push("-Zunstable-options".to_owned());
442+
meta.env("RUSTC_BOOTSTRAP", "1");
394443
}
395-
if no_deps {
396-
other_options.push("--no-deps".to_owned());
444+
// No need to lock it if we copied the lockfile, we won't modify the original after all/
445+
// This way cargo cannot error out on us if the lockfile requires updating.
446+
if !using_lockfile_copy && locked {
447+
other_options.push("--locked".to_owned());
397448
}
398449
meta.other_options(other_options);
399450

@@ -427,8 +478,8 @@ impl CargoWorkspace {
427478
current_dir,
428479
config,
429480
sysroot,
430-
locked,
431481
true,
482+
locked,
432483
progress,
433484
) {
434485
return Ok((metadata, Some(error)));

crates/project-model/src/manifest_path.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! See [`ManifestPath`].
22
use std::{borrow::Borrow, fmt, ops};
33

4-
use paths::{AbsPath, AbsPathBuf};
4+
use paths::{AbsPath, AbsPathBuf, Utf8Path};
55

66
/// More or less [`AbsPathBuf`] with non-None parent.
77
///
@@ -78,6 +78,12 @@ impl AsRef<std::ffi::OsStr> for ManifestPath {
7878
}
7979
}
8080

81+
impl AsRef<Utf8Path> for ManifestPath {
82+
fn as_ref(&self) -> &Utf8Path {
83+
self.file.as_ref()
84+
}
85+
}
86+
8187
impl Borrow<AbsPath> for ManifestPath {
8288
fn borrow(&self) -> &AbsPath {
8389
self.file.borrow()

crates/project-model/src/sysroot.rs

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//! but we can't process `.rlib` and need source code instead. The source code
55
//! is typically installed with `rustup component add rust-src` command.
66
7+
use core::fmt;
78
use std::{env, fs, ops::Not, path::Path, process::Command};
89

910
use anyhow::{Result, format_err};
@@ -34,6 +35,19 @@ pub enum RustLibSrcWorkspace {
3435
Empty,
3536
}
3637

38+
impl fmt::Display for RustLibSrcWorkspace {
39+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40+
match self {
41+
RustLibSrcWorkspace::Workspace(ws) => write!(f, "workspace {}", ws.workspace_root()),
42+
RustLibSrcWorkspace::Json(json) => write!(f, "json {}", json.manifest_or_root()),
43+
RustLibSrcWorkspace::Stitched(stitched) => {
44+
write!(f, "stitched with {} crates", stitched.crates.len())
45+
}
46+
RustLibSrcWorkspace::Empty => write!(f, "empty"),
47+
}
48+
}
49+
}
50+
3751
impl Sysroot {
3852
pub const fn empty() -> Sysroot {
3953
Sysroot {
@@ -195,6 +209,7 @@ impl Sysroot {
195209
pub fn load_workspace(
196210
&self,
197211
sysroot_source_config: &RustSourceWorkspaceConfig,
212+
current_dir: &AbsPath,
198213
progress: &dyn Fn(String),
199214
) -> Option<RustLibSrcWorkspace> {
200215
assert!(matches!(self.workspace, RustLibSrcWorkspace::Empty), "workspace already loaded");
@@ -205,10 +220,16 @@ impl Sysroot {
205220
if let RustSourceWorkspaceConfig::CargoMetadata(cargo_config) = sysroot_source_config {
206221
let library_manifest = ManifestPath::try_from(src_root.join("Cargo.toml")).unwrap();
207222
if fs::metadata(&library_manifest).is_ok() {
208-
if let Some(loaded) =
209-
self.load_library_via_cargo(library_manifest, src_root, cargo_config, progress)
210-
{
211-
return Some(loaded);
223+
match self.load_library_via_cargo(
224+
&library_manifest,
225+
current_dir,
226+
cargo_config,
227+
progress,
228+
) {
229+
Ok(loaded) => return Some(loaded),
230+
Err(e) => {
231+
tracing::error!("`cargo metadata` failed on `{library_manifest}` : {e}")
232+
}
212233
}
213234
}
214235
tracing::debug!("Stitching sysroot library: {src_root}");
@@ -294,11 +315,11 @@ impl Sysroot {
294315

295316
fn load_library_via_cargo(
296317
&self,
297-
library_manifest: ManifestPath,
298-
rust_lib_src_dir: &AbsPathBuf,
318+
library_manifest: &ManifestPath,
319+
current_dir: &AbsPath,
299320
cargo_config: &CargoMetadataConfig,
300321
progress: &dyn Fn(String),
301-
) -> Option<RustLibSrcWorkspace> {
322+
) -> Result<RustLibSrcWorkspace> {
302323
tracing::debug!("Loading library metadata: {library_manifest}");
303324
let mut cargo_config = cargo_config.clone();
304325
// the sysroot uses `public-dependency`, so we make cargo think it's a nightly
@@ -307,22 +328,16 @@ impl Sysroot {
307328
Some("nightly".to_owned()),
308329
);
309330

310-
let (mut res, _) = match CargoWorkspace::fetch_metadata(
311-
&library_manifest,
312-
rust_lib_src_dir,
331+
let (mut res, _) = CargoWorkspace::fetch_metadata(
332+
library_manifest,
333+
current_dir,
313334
&cargo_config,
314335
self,
315336
false,
316337
// Make sure we never attempt to write to the sysroot
317338
true,
318339
progress,
319-
) {
320-
Ok(it) => it,
321-
Err(e) => {
322-
tracing::error!("`cargo metadata` failed on `{library_manifest}` : {e}");
323-
return None;
324-
}
325-
};
340+
)?;
326341

327342
// Patch out `rustc-std-workspace-*` crates to point to the real crates.
328343
// This is done prior to `CrateGraph` construction to prevent de-duplication logic from failing.
@@ -373,8 +388,9 @@ impl Sysroot {
373388
res.packages.remove(idx);
374389
});
375390

376-
let cargo_workspace = CargoWorkspace::new(res, library_manifest, Default::default(), true);
377-
Some(RustLibSrcWorkspace::Workspace(cargo_workspace))
391+
let cargo_workspace =
392+
CargoWorkspace::new(res, library_manifest.clone(), Default::default(), true);
393+
Ok(RustLibSrcWorkspace::Workspace(cargo_workspace))
378394
}
379395
}
380396

crates/project-model/src/tests.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::env::temp_dir;
2+
13
use base_db::{CrateGraphBuilder, ProcMacroPaths};
24
use cargo_metadata::Metadata;
35
use cfg::{CfgAtom, CfgDiff};
@@ -235,12 +237,18 @@ fn smoke_test_real_sysroot_cargo() {
235237
AbsPath::assert(Utf8Path::new(env!("CARGO_MANIFEST_DIR"))),
236238
&Default::default(),
237239
);
240+
let cwd = AbsPathBuf::assert_utf8(temp_dir().join("smoke_test_real_sysroot_cargo"));
241+
std::fs::create_dir_all(&cwd).unwrap();
238242
let loaded_sysroot =
239-
sysroot.load_workspace(&RustSourceWorkspaceConfig::default_cargo(), &|_| ());
243+
sysroot.load_workspace(&RustSourceWorkspaceConfig::default_cargo(), &cwd, &|_| ());
240244
if let Some(loaded_sysroot) = loaded_sysroot {
241245
sysroot.set_workspace(loaded_sysroot);
242246
}
243-
assert!(matches!(sysroot.workspace(), RustLibSrcWorkspace::Workspace(_)));
247+
assert!(
248+
matches!(sysroot.workspace(), RustLibSrcWorkspace::Workspace(_)),
249+
"got {}",
250+
sysroot.workspace()
251+
);
244252
let project_workspace = ProjectWorkspace {
245253
kind: ProjectWorkspaceKind::Cargo {
246254
cargo: cargo_workspace,

0 commit comments

Comments
 (0)