Skip to content

Commit 6881029

Browse files
authored
Merge pull request #20315 from Veykril/push-pvmslwwouzzx
internal: Fix lockfile temp dir usage and use it for build scripts as well
2 parents 971c393 + df85aac commit 6881029

File tree

7 files changed

+246
-182
lines changed

7 files changed

+246
-182
lines changed

crates/base-db/src/input.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ pub type ProcMacroPaths =
3030
pub enum ProcMacroLoadingError {
3131
Disabled,
3232
FailedToBuild,
33-
MissingDylibPath,
33+
ExpectedProcMacroArtifact,
34+
MissingDylibPath(Box<[String]>),
3435
NotYetBuilt,
3536
NoProcMacros,
3637
ProcMacroSrvError(Box<str>),
@@ -39,8 +40,9 @@ impl ProcMacroLoadingError {
3940
pub fn is_hard_error(&self) -> bool {
4041
match self {
4142
ProcMacroLoadingError::Disabled | ProcMacroLoadingError::NotYetBuilt => false,
42-
ProcMacroLoadingError::FailedToBuild
43-
| ProcMacroLoadingError::MissingDylibPath
43+
ProcMacroLoadingError::ExpectedProcMacroArtifact
44+
| ProcMacroLoadingError::FailedToBuild
45+
| ProcMacroLoadingError::MissingDylibPath(_)
4446
| ProcMacroLoadingError::NoProcMacros
4547
| ProcMacroLoadingError::ProcMacroSrvError(_) => true,
4648
}
@@ -51,10 +53,23 @@ impl Error for ProcMacroLoadingError {}
5153
impl fmt::Display for ProcMacroLoadingError {
5254
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5355
match self {
56+
ProcMacroLoadingError::ExpectedProcMacroArtifact => {
57+
write!(f, "proc-macro crate did not build proc-macro artifact")
58+
}
5459
ProcMacroLoadingError::Disabled => write!(f, "proc-macro expansion is disabled"),
5560
ProcMacroLoadingError::FailedToBuild => write!(f, "proc-macro failed to build"),
56-
ProcMacroLoadingError::MissingDylibPath => {
57-
write!(f, "proc-macro crate build data is missing a dylib path")
61+
ProcMacroLoadingError::MissingDylibPath(candidates) if candidates.is_empty() => {
62+
write!(
63+
f,
64+
"proc-macro crate built but the dylib path is missing, this indicates a problem with your build system."
65+
)
66+
}
67+
ProcMacroLoadingError::MissingDylibPath(candidates) => {
68+
write!(
69+
f,
70+
"proc-macro crate built but the dylib path is missing, this indicates a problem with your build system. Candidates not considered due to not having a dynamic library extension: {}",
71+
candidates.join(", ")
72+
)
5873
}
5974
ProcMacroLoadingError::NotYetBuilt => write!(f, "proc-macro not yet built"),
6075
ProcMacroLoadingError::NoProcMacros => {

crates/project-model/src/build_dependencies.rs

Lines changed: 102 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +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::never;
1920
use toolchain::Tool;
2021

2122
use crate::{
2223
CargoConfig, CargoFeatures, CargoWorkspace, InvocationStrategy, ManifestPath, Package, Sysroot,
23-
TargetKind, utf8_stdout,
24+
TargetKind, cargo_config_file::make_lockfile_copy,
25+
cargo_workspace::MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH, utf8_stdout,
2426
};
2527

2628
/// Output of the build script and proc-macro building steps for a workspace.
@@ -30,6 +32,15 @@ pub struct WorkspaceBuildScripts {
3032
error: Option<String>,
3133
}
3234

35+
#[derive(Debug, Clone, Default, PartialEq, Eq)]
36+
pub enum ProcMacroDylibPath {
37+
Path(AbsPathBuf),
38+
DylibNotFound(Box<[Utf8PathBuf]>),
39+
NotProcMacro,
40+
#[default]
41+
NotBuilt,
42+
}
43+
3344
/// Output of the build script and proc-macro building step for a concrete package.
3445
#[derive(Debug, Clone, Default, PartialEq, Eq)]
3546
pub(crate) struct BuildScriptOutput {
@@ -43,15 +54,15 @@ pub(crate) struct BuildScriptOutput {
4354
/// Directory where a build script might place its output.
4455
pub(crate) out_dir: Option<AbsPathBuf>,
4556
/// Path to the proc-macro library file if this package exposes proc-macros.
46-
pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
57+
pub(crate) proc_macro_dylib_path: ProcMacroDylibPath,
4758
}
4859

4960
impl BuildScriptOutput {
5061
fn is_empty(&self) -> bool {
5162
self.cfgs.is_empty()
5263
&& self.envs.is_empty()
5364
&& self.out_dir.is_none()
54-
&& self.proc_macro_dylib_path.is_none()
65+
&& self.proc_macro_dylib_path == ProcMacroDylibPath::NotBuilt
5566
}
5667
}
5768

@@ -67,7 +78,7 @@ impl WorkspaceBuildScripts {
6778
let current_dir = workspace.workspace_root();
6879

6980
let allowed_features = workspace.workspace_features();
70-
let cmd = Self::build_command(
81+
let (_guard, cmd) = Self::build_command(
7182
config,
7283
&allowed_features,
7384
workspace.manifest_path(),
@@ -88,7 +99,7 @@ impl WorkspaceBuildScripts {
8899
) -> io::Result<Vec<WorkspaceBuildScripts>> {
89100
assert_eq!(config.invocation_strategy, InvocationStrategy::Once);
90101

91-
let cmd = Self::build_command(
102+
let (_guard, cmd) = Self::build_command(
92103
config,
93104
&Default::default(),
94105
// This is not gonna be used anyways, so just construct a dummy here
@@ -126,6 +137,8 @@ impl WorkspaceBuildScripts {
126137
|package, cb| {
127138
if let Some(&(package, workspace)) = by_id.get(package) {
128139
cb(&workspaces[workspace][package].name, &mut res[workspace].outputs[package]);
140+
} else {
141+
never!("Received compiler message for unknown package: {}", package);
129142
}
130143
},
131144
progress,
@@ -140,12 +153,9 @@ impl WorkspaceBuildScripts {
140153
if tracing::enabled!(tracing::Level::INFO) {
141154
for (idx, workspace) in workspaces.iter().enumerate() {
142155
for package in workspace.packages() {
143-
let package_build_data = &mut res[idx].outputs[package];
156+
let package_build_data: &mut BuildScriptOutput = &mut res[idx].outputs[package];
144157
if !package_build_data.is_empty() {
145-
tracing::info!(
146-
"{}: {package_build_data:?}",
147-
workspace[package].manifest.parent(),
148-
);
158+
tracing::info!("{}: {package_build_data:?}", workspace[package].manifest,);
149159
}
150160
}
151161
}
@@ -198,39 +208,58 @@ impl WorkspaceBuildScripts {
198208
let path = dir_entry.path();
199209
let extension = path.extension()?;
200210
if extension == std::env::consts::DLL_EXTENSION {
201-
let name = path.file_stem()?.to_str()?.split_once('-')?.0.to_owned();
202-
let path = AbsPathBuf::try_from(Utf8PathBuf::from_path_buf(path).ok()?)
203-
.ok()?;
204-
return Some((name, path));
211+
let name = path
212+
.file_stem()?
213+
.to_str()?
214+
.split_once('-')?
215+
.0
216+
.trim_start_matches("lib")
217+
.to_owned();
218+
let path = match Utf8PathBuf::from_path_buf(path) {
219+
Ok(path) => path,
220+
Err(path) => {
221+
tracing::warn!(
222+
"Proc-macro dylib path contains non-UTF8 characters: {:?}",
223+
path.display()
224+
);
225+
return None;
226+
}
227+
};
228+
return match AbsPathBuf::try_from(path) {
229+
Ok(path) => Some((name, path)),
230+
Err(path) => {
231+
tracing::error!(
232+
"proc-macro dylib path is not absolute: {:?}",
233+
path
234+
);
235+
None
236+
}
237+
};
205238
}
206239
}
207240
None
208241
})
209242
.collect();
210243
for p in rustc.packages() {
211244
let package = &rustc[p];
212-
if package
213-
.targets
214-
.iter()
215-
.any(|&it| matches!(rustc[it].kind, TargetKind::Lib { is_proc_macro: true }))
216-
{
217-
if let Some((_, path)) = proc_macro_dylibs
218-
.iter()
219-
.find(|(name, _)| *name.trim_start_matches("lib") == package.name)
220-
{
221-
bs.outputs[p].proc_macro_dylib_path = Some(path.clone());
245+
bs.outputs[p].proc_macro_dylib_path =
246+
if package.targets.iter().any(|&it| {
247+
matches!(rustc[it].kind, TargetKind::Lib { is_proc_macro: true })
248+
}) {
249+
match proc_macro_dylibs.iter().find(|(name, _)| *name == package.name) {
250+
Some((_, path)) => ProcMacroDylibPath::Path(path.clone()),
251+
_ => ProcMacroDylibPath::DylibNotFound(Box::default()),
252+
}
253+
} else {
254+
ProcMacroDylibPath::NotProcMacro
222255
}
223-
}
224256
}
225257

226258
if tracing::enabled!(tracing::Level::INFO) {
227259
for package in rustc.packages() {
228260
let package_build_data = &bs.outputs[package];
229261
if !package_build_data.is_empty() {
230-
tracing::info!(
231-
"{}: {package_build_data:?}",
232-
rustc[package].manifest.parent(),
233-
);
262+
tracing::info!("{}: {package_build_data:?}", rustc[package].manifest,);
234263
}
235264
}
236265
}
@@ -263,6 +292,12 @@ impl WorkspaceBuildScripts {
263292
|package, cb| {
264293
if let Some(&package) = by_id.get(package) {
265294
cb(&workspace[package].name, &mut outputs[package]);
295+
} else {
296+
never!(
297+
"Received compiler message for unknown package: {}\n {}",
298+
package,
299+
by_id.keys().join(", ")
300+
);
266301
}
267302
},
268303
progress,
@@ -272,10 +307,7 @@ impl WorkspaceBuildScripts {
272307
for package in workspace.packages() {
273308
let package_build_data = &outputs[package];
274309
if !package_build_data.is_empty() {
275-
tracing::info!(
276-
"{}: {package_build_data:?}",
277-
workspace[package].manifest.parent(),
278-
);
310+
tracing::info!("{}: {package_build_data:?}", workspace[package].manifest,);
279311
}
280312
}
281313
}
@@ -348,15 +380,21 @@ impl WorkspaceBuildScripts {
348380
progress(format!(
349381
"building compile-time-deps: proc-macro {name} built"
350382
));
383+
if data.proc_macro_dylib_path == ProcMacroDylibPath::NotBuilt {
384+
data.proc_macro_dylib_path = ProcMacroDylibPath::NotProcMacro;
385+
}
351386
if message.target.kind.contains(&cargo_metadata::TargetKind::ProcMacro)
352387
{
353-
// Skip rmeta file
354-
if let Some(filename) =
355-
message.filenames.iter().find(|file| is_dylib(file))
356-
{
357-
let filename = AbsPath::assert(filename);
358-
data.proc_macro_dylib_path = Some(filename.to_owned());
359-
}
388+
data.proc_macro_dylib_path =
389+
match message.filenames.iter().find(|file| is_dylib(file)) {
390+
Some(filename) => {
391+
let filename = AbsPath::assert(filename);
392+
ProcMacroDylibPath::Path(filename.to_owned())
393+
}
394+
None => ProcMacroDylibPath::DylibNotFound(
395+
message.filenames.clone().into_boxed_slice(),
396+
),
397+
};
360398
}
361399
});
362400
}
@@ -393,14 +431,15 @@ impl WorkspaceBuildScripts {
393431
current_dir: &AbsPath,
394432
sysroot: &Sysroot,
395433
toolchain: Option<&semver::Version>,
396-
) -> io::Result<Command> {
434+
) -> io::Result<(Option<temp_dir::TempDir>, Command)> {
397435
match config.run_build_script_command.as_deref() {
398436
Some([program, args @ ..]) => {
399437
let mut cmd = toolchain::command(program, current_dir, &config.extra_env);
400438
cmd.args(args);
401-
Ok(cmd)
439+
Ok((None, cmd))
402440
}
403441
_ => {
442+
let mut requires_unstable_options = false;
404443
let mut cmd = sysroot.tool(Tool::Cargo, current_dir, &config.extra_env);
405444

406445
cmd.args(["check", "--quiet", "--workspace", "--message-format=json"]);
@@ -416,7 +455,19 @@ impl WorkspaceBuildScripts {
416455
if let Some(target) = &config.target {
417456
cmd.args(["--target", target]);
418457
}
419-
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+
}
420471
match &config.features {
421472
CargoFeatures::All => {
422473
cmd.arg("--all-features");
@@ -438,6 +489,7 @@ impl WorkspaceBuildScripts {
438489
}
439490

440491
if manifest_path.is_rust_manifest() {
492+
requires_unstable_options = true;
441493
cmd.arg("-Zscript");
442494
}
443495

@@ -447,7 +499,7 @@ impl WorkspaceBuildScripts {
447499
// available in current toolchain's cargo, use it to build compile time deps only.
448500
const COMP_TIME_DEPS_MIN_TOOLCHAIN_VERSION: semver::Version = semver::Version {
449501
major: 1,
450-
minor: 89,
502+
minor: 189,
451503
patch: 0,
452504
pre: semver::Prerelease::EMPTY,
453505
build: semver::BuildMetadata::EMPTY,
@@ -457,8 +509,7 @@ impl WorkspaceBuildScripts {
457509
toolchain.is_some_and(|v| *v >= COMP_TIME_DEPS_MIN_TOOLCHAIN_VERSION);
458510

459511
if cargo_comp_time_deps_available {
460-
cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
461-
cmd.arg("-Zunstable-options");
512+
requires_unstable_options = true;
462513
cmd.arg("--compile-time-deps");
463514
// we can pass this unconditionally, because we won't actually build the
464515
// binaries, and as such, this will succeed even on targets without libtest
@@ -481,7 +532,11 @@ impl WorkspaceBuildScripts {
481532
cmd.env("RA_RUSTC_WRAPPER", "1");
482533
}
483534
}
484-
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))
485540
}
486541
}
487542
}

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+
}

0 commit comments

Comments
 (0)