Skip to content

Commit e07088b

Browse files
committed
Auto merge of #6308 - ehuss:output-collision, r=alexcrichton
Check for duplicate output filenames. This generates an error if it detects that different units would output the same file name. This can happen in a few situations: - Multiple targets in a workspace use the same name. - `--out-dir` in some cases (like `examples` because it does not include the directory). - Bugs in cargo (like #5524, #5444) This includes a few cleanups/consolidations: - `export_path` added to `OutputFile` so there is one place where the `--out-dir` filename logic is located. - `TargetKind::description()` added for a simple way to get a human-readable name of a target kind. - The `PartialOrd` changes are a slight performance improvement (overall things are now slightly faster even though it is doing more work). Closes #5524, closes #6293.
2 parents 438c55d + 69c6363 commit e07088b

File tree

11 files changed

+249
-45
lines changed

11 files changed

+249
-45
lines changed

src/cargo/core/compiler/build_config.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ pub enum MessageFormat {
111111
/// `compile_ws` to tell it the general execution strategy. This influences
112112
/// the default targets selected. The other use is in the `Unit` struct
113113
/// to indicate what is being done with a specific target.
114-
#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash)]
114+
#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash, PartialOrd, Ord)]
115115
pub enum CompileMode {
116116
/// A target being built for a test.
117117
Test,

src/cargo/core/compiler/context/compilation_files.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,13 @@ pub struct CompilationFiles<'a, 'cfg: 'a> {
3838

3939
#[derive(Debug)]
4040
pub struct OutputFile {
41-
/// File name that will be produced by the build process (in `deps`).
41+
/// Absolute path to the file that will be produced by the build process.
4242
pub path: PathBuf,
4343
/// If it should be linked into `target`, and what it should be called
4444
/// (e.g. without metadata).
4545
pub hardlink: Option<PathBuf>,
46+
/// If `--out-dir` is specified, the absolute path to the exported file.
47+
pub export_path: Option<PathBuf>,
4648
/// Type of the file (library / debug symbol / else).
4749
pub flavor: FileFlavor,
4850
}
@@ -251,6 +253,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
251253
ret.push(OutputFile {
252254
path,
253255
hardlink: None,
256+
export_path: None,
254257
flavor: FileFlavor::Linkable,
255258
});
256259
} else {
@@ -273,9 +276,19 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
273276
let hardlink = link_stem
274277
.as_ref()
275278
.map(|&(ref ld, ref ls)| ld.join(file_type.filename(ls)));
279+
let export_path = if unit.target.is_custom_build() {
280+
None
281+
} else {
282+
self.export_dir.as_ref().and_then(|export_dir| {
283+
hardlink.as_ref().and_then(|hardlink| {
284+
Some(export_dir.join(hardlink.file_name().unwrap()))
285+
})
286+
})
287+
};
276288
ret.push(OutputFile {
277289
path,
278290
hardlink,
291+
export_path,
279292
flavor: file_type.flavor,
280293
});
281294
},

src/cargo/core/compiler/context/mod.rs

+88-21
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use std::ffi::OsStr;
44
use std::fmt::Write;
55
use std::path::PathBuf;
66
use std::sync::Arc;
7-
use std::cmp::Ordering;
87

98
use jobserver::Client;
109

@@ -42,7 +41,7 @@ use self::compilation_files::CompilationFiles;
4241
/// example, it needs to know the target architecture (OS, chip arch etc.) and it needs to know
4342
/// whether you want a debug or release build. There is enough information in this struct to figure
4443
/// all that out.
45-
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
44+
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
4645
pub struct Unit<'a> {
4746
/// Information about available targets, which files to include/exclude, etc. Basically stuff in
4847
/// `Cargo.toml`.
@@ -72,18 +71,6 @@ impl<'a> Unit<'a> {
7271
}
7372
}
7473

75-
impl<'a> Ord for Unit<'a> {
76-
fn cmp(&self, other: &Unit) -> Ordering {
77-
self.buildkey().cmp(&other.buildkey())
78-
}
79-
}
80-
81-
impl<'a> PartialOrd for Unit<'a> {
82-
fn partial_cmp(&self, other: &Unit) -> Option<Ordering> {
83-
Some(self.cmp(other))
84-
}
85-
}
86-
8774
pub struct Context<'a, 'cfg: 'a> {
8875
pub bcx: &'a BuildContext<'a, 'cfg>,
8976
pub compilation: Compilation<'cfg>,
@@ -150,6 +137,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
150137
self.prepare_units(export_dir, units)?;
151138
self.prepare()?;
152139
custom_build::build_map(&mut self, units)?;
140+
self.check_collistions()?;
153141

154142
for unit in units.iter() {
155143
// Build up a list of pending jobs, each of which represent
@@ -353,13 +341,8 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
353341
self.files.as_mut().unwrap()
354342
}
355343

356-
/// Return the filenames that the given target for the given profile will
357-
/// generate as a list of 3-tuples (filename, link_dst, linkable)
358-
///
359-
/// - filename: filename rustc compiles to. (Often has metadata suffix).
360-
/// - link_dst: Optional file to link/copy the result to (without metadata suffix)
361-
/// - linkable: Whether possible to link against file (eg it's a library)
362-
pub fn outputs(&mut self, unit: &Unit<'a>) -> CargoResult<Arc<Vec<OutputFile>>> {
344+
/// Return the filenames that the given unit will generate.
345+
pub fn outputs(&self, unit: &Unit<'a>) -> CargoResult<Arc<Vec<OutputFile>>> {
363346
self.files.as_ref().unwrap().outputs(unit, self.bcx)
364347
}
365348

@@ -468,6 +451,90 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
468451
inputs.sort();
469452
Ok(inputs)
470453
}
454+
455+
fn check_collistions(&self) -> CargoResult<()> {
456+
let mut output_collisions = HashMap::new();
457+
let describe_collision = |unit: &Unit, other_unit: &Unit, path: &PathBuf| -> String {
458+
format!(
459+
"The {} target `{}` in package `{}` has the same output \
460+
filename as the {} target `{}` in package `{}`.\n\
461+
Colliding filename is: {}\n",
462+
unit.target.kind().description(),
463+
unit.target.name(),
464+
unit.pkg.package_id(),
465+
other_unit.target.kind().description(),
466+
other_unit.target.name(),
467+
other_unit.pkg.package_id(),
468+
path.display()
469+
)
470+
};
471+
let suggestion = "Consider changing their names to be unique or compiling them separately.\n\
472+
This may become a hard error in the future, see https://github.com/rust-lang/cargo/issues/6313";
473+
let report_collision = |unit: &Unit, other_unit: &Unit, path: &PathBuf| -> CargoResult<()> {
474+
if unit.target.name() == other_unit.target.name() {
475+
self.bcx.config.shell().warn(format!(
476+
"output filename collision.\n\
477+
{}\
478+
The targets should have unique names.\n\
479+
{}",
480+
describe_collision(unit, other_unit, path),
481+
suggestion
482+
))
483+
} else {
484+
self.bcx.config.shell().warn(format!(
485+
"output filename collision.\n\
486+
{}\
487+
The output filenames should be unique.\n\
488+
{}\n\
489+
If this looks unexpected, it may be a bug in Cargo. Please file a bug report at\n\
490+
https://github.com/rust-lang/cargo/issues/ with as much information as you\n\
491+
can provide.\n\
492+
{} running on `{}` target `{}`\n\
493+
First unit: {:?}\n\
494+
Second unit: {:?}",
495+
describe_collision(unit, other_unit, path),
496+
suggestion,
497+
::version(), self.bcx.host_triple(), self.bcx.target_triple(),
498+
unit, other_unit))
499+
}
500+
};
501+
let mut keys = self
502+
.unit_dependencies
503+
.keys()
504+
.filter(|unit| !unit.mode.is_run_custom_build())
505+
.collect::<Vec<_>>();
506+
// Sort for consistent error messages.
507+
keys.sort_unstable();
508+
for unit in keys {
509+
for output in self.outputs(unit)?.iter() {
510+
if let Some(other_unit) =
511+
output_collisions.insert(output.path.clone(), unit)
512+
{
513+
report_collision(unit, &other_unit, &output.path)?;
514+
}
515+
if let Some(hardlink) = output.hardlink.as_ref() {
516+
if let Some(other_unit) = output_collisions.insert(hardlink.clone(), unit)
517+
{
518+
report_collision(unit, &other_unit, hardlink)?;
519+
}
520+
}
521+
if let Some(ref export_path) = output.export_path {
522+
if let Some(other_unit) =
523+
output_collisions.insert(export_path.clone(), unit)
524+
{
525+
self.bcx.config.shell().warn(format!("`--out-dir` filename collision.\n\
526+
{}\
527+
The exported filenames should be unique.\n\
528+
{}",
529+
describe_collision(unit, &other_unit, &export_path),
530+
suggestion
531+
))?;
532+
}
533+
}
534+
}
535+
}
536+
Ok(())
537+
}
471538
}
472539

473540
#[derive(Default)]

src/cargo/core/compiler/fingerprint.rs

+2-9
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use serde::de::{self, Deserialize};
99
use serde::ser;
1010
use serde_json;
1111

12-
use core::{Edition, Package, TargetKind};
12+
use core::{Edition, Package};
1313
use util;
1414
use util::errors::{CargoResult, CargoResultExt};
1515
use util::paths;
@@ -780,14 +780,7 @@ fn filename<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> String {
780780
// fingerprint for every metadata hash version. This works because
781781
// even if the package is fresh, we'll still link the fresh target
782782
let file_stem = cx.files().file_stem(unit);
783-
let kind = match *unit.target.kind() {
784-
TargetKind::Lib(..) => "lib",
785-
TargetKind::Bin => "bin",
786-
TargetKind::Test => "integration-test",
787-
TargetKind::ExampleBin | TargetKind::ExampleLib(..) => "example",
788-
TargetKind::Bench => "bench",
789-
TargetKind::CustomBuild => "build-script",
790-
};
783+
let kind = unit.target.kind().description();
791784
let flavor = if unit.mode.is_any_test() {
792785
"test-"
793786
} else if unit.mode.is_doc() {

src/cargo/core/compiler/mod.rs

+6-7
Original file line numberDiff line numberDiff line change
@@ -436,14 +436,13 @@ fn link_targets<'a, 'cfg>(
436436
};
437437
destinations.push(dst.display().to_string());
438438
hardlink_or_copy(src, dst)?;
439-
if let Some(ref path) = export_dir {
440-
if !target.is_custom_build() {
441-
if !path.exists() {
442-
fs::create_dir_all(path)?;
443-
}
444-
445-
hardlink_or_copy(src, &path.join(dst.file_name().unwrap()))?;
439+
if let Some(ref path) = output.export_path {
440+
let export_dir = export_dir.as_ref().unwrap();
441+
if !export_dir.exists() {
442+
fs::create_dir_all(export_dir)?;
446443
}
444+
445+
hardlink_or_copy(src, path)?;
447446
}
448447
}
449448

src/cargo/core/manifest.rs

+15-2
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,22 @@ impl fmt::Debug for TargetKind {
181181
}
182182
}
183183

184+
impl TargetKind {
185+
pub fn description(&self) -> &'static str {
186+
match self {
187+
TargetKind::Lib(..) => "lib",
188+
TargetKind::Bin => "bin",
189+
TargetKind::Test => "integration-test",
190+
TargetKind::ExampleBin | TargetKind::ExampleLib(..) => "example",
191+
TargetKind::Bench => "bench",
192+
TargetKind::CustomBuild => "build-script",
193+
}
194+
}
195+
}
196+
184197
/// Information about a binary, a library, an example, etc. that is part of the
185198
/// package.
186-
#[derive(Clone, Hash, PartialEq, Eq)]
199+
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
187200
pub struct Target {
188201
kind: TargetKind,
189202
name: String,
@@ -202,7 +215,7 @@ pub struct Target {
202215
edition: Edition,
203216
}
204217

205-
#[derive(Clone, PartialEq, Eq)]
218+
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
206219
pub enum TargetSourcePath {
207220
Path(PathBuf),
208221
Metabuild,

src/cargo/core/package.rs

+13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::cell::{Ref, RefCell, Cell};
2+
use std::cmp::Ordering;
23
use std::collections::{HashMap, HashSet};
34
use std::fmt;
45
use std::hash;
@@ -38,6 +39,18 @@ pub struct Package {
3839
manifest_path: PathBuf,
3940
}
4041

42+
impl Ord for Package {
43+
fn cmp(&self, other: &Package) -> Ordering {
44+
self.package_id().cmp(other.package_id())
45+
}
46+
}
47+
48+
impl PartialOrd for Package {
49+
fn partial_cmp(&self, other: &Package) -> Option<Ordering> {
50+
Some(self.cmp(other))
51+
}
52+
}
53+
4154
/// A Package in a form where `Serialize` can be derived.
4255
#[derive(Serialize)]
4356
struct SerializedPackage<'a> {

src/cargo/core/profiles.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ fn merge_profile(profile: &mut Profile, toml: &TomlProfile) {
371371

372372
/// Profile settings used to determine which compiler flags to use for a
373373
/// target.
374-
#[derive(Clone, Copy, Eq)]
374+
#[derive(Clone, Copy, Eq, PartialOrd, Ord)]
375375
pub struct Profile {
376376
pub name: &'static str,
377377
pub opt_level: InternedString,
@@ -523,7 +523,7 @@ impl Profile {
523523
}
524524

525525
/// The link-time-optimization setting.
526-
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
526+
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
527527
pub enum Lto {
528528
/// False = no LTO
529529
/// True = "Fat" LTO

tests/testsuite/build.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -4327,8 +4327,8 @@ fn target_filters_workspace() {
43274327
.file("a/src/lib.rs", "")
43284328
.file("a/examples/ex1.rs", "fn main() {}")
43294329
.file("b/Cargo.toml", &basic_bin_manifest("b"))
4330+
.file("b/src/lib.rs", "")
43304331
.file("b/src/main.rs", "fn main() {}")
4331-
.file("b/examples/ex1.rs", "fn main() {}")
43324332
.build();
43334333

43344334
ws.cargo("build -v --example ex")
@@ -4343,12 +4343,12 @@ Did you mean `ex1`?",
43434343
ws.cargo("build -v --lib")
43444344
.with_status(0)
43454345
.with_stderr_contains("[RUNNING] `rustc [..]a/src/lib.rs[..]")
4346+
.with_stderr_contains("[RUNNING] `rustc [..]b/src/lib.rs[..]")
43464347
.run();
43474348

43484349
ws.cargo("build -v --example ex1")
43494350
.with_status(0)
43504351
.with_stderr_contains("[RUNNING] `rustc [..]a/examples/ex1.rs[..]")
4351-
.with_stderr_contains("[RUNNING] `rustc [..]b/examples/ex1.rs[..]")
43524352
.run();
43534353
}
43544354

0 commit comments

Comments
 (0)