Skip to content

Commit 5aad9b3

Browse files
committed
Auto merge of #9992 - Byron:rfc-3028, r=ehuss
Implement "artifact dependencies" (RFC-3028) Tracking issue: #9096 #### Tasks * [x] -Z unstable option called `bindeps` * ✅ **(config)** allow parsing of newly introduced 'artifact' fields * [x] into `TomlManifest` * [x] into `Manifest` - [x] ~~abort~~ warn if artifacts are used on stable * ✅ **resolver** : assure artifact dependencies are part of the resolution process and unified into the dependency tree * 🔬**compiler**: make it understand 'artifact' dependencies and pass new environment variables to the crate being build * [x] `lib=false` should not be considered a rust library for the dependent, in unit and possibly resolve graph * [x] assure profile settings are applied correctly * [x] target overrides work * [x] `target = "target"` in build deps * [x] other targets on build deps * [x] other targets on non-build deps * [x] 'no-cross doc tests' seems like a constraint we should apply as well maybe * [x] more confidence with `resolver = "2"` * [x] assure artifact placement is correct (bin and various forms of lib) * ✅ **serialization**: rewriting manifests (i.e. for publishing) does not discard artifact information * [x] publishing keeps `artifact` and `lib` values * **Other cargo subcommands** * [x] `cargo metadata` * leave unchanged * [x] artifacts work with `cargo check` * [x] artifacts work with rustdoc, such that it doesn't document them unless `lib=true` * [x] `cargo tree` maybe? * [x] `cargo clean` should clean artifacts - even though it's more complex, ultimately it deletes the `target` directory. * [x] artifacts work with `cargo test` (and dev-dependencies) * [x] doctests * [x] try `reproducible` repository as well. * 🧪 **tests** for more subtle RFC constraints - [x] build scripts cannot access artifact environment variables at compile time, only at runtime) - [x] assure 'examples' which also support crate-type fields like [[lib]] won't become artifacts themselves. - [x] assure `--out-dir` does not leak artifacts - tested manually, it seemed to niche to add a test. - [x] try `target="foo"` in artifact and assure it sees a decent error message - [x] Assure RFC 3176 _doesn't_ work * 🧹cleanup and finalization - [x] assure no `TODO(ST)` markers are left in code - [x] assure no tests are ignored - [x] use `resolver = "1"` once to assert everything also works with the previous resolver, but leave it on "2". #### Implementation and review notes - artifacts and unstable options are only checked when transforming them from `TomlManifest` to `Manifest`, discarding artifact information if the unstable flag is not set. Nowhere else in code will the CLI options be checked again. - `If no binaries are specified, all the binaries in the package will be built and made available.` - this should only refer to `[[bin]]` targets, not examples for instance. - artifact binaries won't be uplifted, hence won't be present outside of their output directory - ❗️We don't know how [package links](https://github.com/rust-lang/cargo/blob/00e925f61fbd9f2d956046aea5af6b7636ab2931/src/cargo/core/compiler/unit_dependencies.rs#L380) will affect artifacts for build dependencies. Should probably be thought through. - ❗️The location of artifacts is only tested roughly to avoid having to deal with different output names on the four platforms that seem to matter (gnu, macos, windows msvc, windows gnu). - `cargo tree` doesn't handle artifacts specifically, and it might be interesting to make clear if an artifact is only an artifact, or both artifact and dependency. - Most error and warning messages can probably be more cargo-matic. #### Questions * Does `cargo` without the feature enabled have to complain about the "artifact" field in a dependency, like it does right now? It doesn't look like machinery for that exists in `do_read_manifest()`. - ✔️It warns now * Should parsing of artifact values, like "bin" be case sensitive? - ✔️ It's case sensitive now, which should help with serde roundtripping. #### Review Progress * [x] address Josh's review notes one by one * [x] introduce `IsArtifact` (see [this answer](#9992 (comment))) (76cd48a2d62d74e043a1a482199c5bb920f50311) * [x] prefer uplifting artifact deps that were written into the `deps` directory, but prefer to do that in [this PR instead](#9992) * [x] add extra-tests as described in Josh's comment: " features get unified between a Rust library and a binary, and one that confirms features don't get unified between a Rust library and a binary for a different target?" * [x] Make target-based artifact splitting work by porting parts of RFC-3176 * [x] test-support/cross-compile * [x] namespace separation * [x] re-create RFC-3176 what's not in RFC-3028, namely multidep support and all related tests * [x] Address Eh2406 's review comments * [x] Address Eric's comments * [x] Unstable features need to be documented in unstable.md. * [x] sort out [`target_data`](#9992 (comment)) * [x] figure out [cargo metadata](#9992 (comment)) * [x] See if target-data can work without an [index format update](#9992 (comment)). * [x] truncate comments at 80-90 lines and remove unused methods, remove -Z unstable-options, use `cfg!(target_env = "msvc")` * [x] add missing doc comments to newly added methods and funtions * [x] simplify method parameters and inline some functions * [x] add test and extend test to show additional issues * [x] assure current set of tests works consistently, also on windows Sponsored by [Profian](https://www.profian.com)
2 parents 46c9b51 + 7248f4b commit 5aad9b3

36 files changed

+4330
-411
lines changed

crates/cargo-test-support/src/cross_compile.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ pub fn native_arch() -> &'static str {
191191
.expect("Target triple has unexpected format")
192192
{
193193
"x86_64" => "x86_64",
194+
"aarch64" => "aarch64",
194195
"i686" => "x86",
195196
_ => panic!("This test should be gated on cross_compile::disabled."),
196197
}
@@ -200,7 +201,9 @@ pub fn native_arch() -> &'static str {
200201
///
201202
/// Only use this function on tests that check `cross_compile::disabled`.
202203
pub fn alternate() -> &'static str {
203-
if cfg!(target_os = "macos") {
204+
if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
205+
"x86_64-apple-darwin"
206+
} else if cfg!(target_os = "macos") {
204207
"x86_64-apple-ios"
205208
} else if cfg!(target_os = "linux") {
206209
"i686-unknown-linux-gnu"

crates/cargo-test-support/src/registry.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ pub struct Dependency {
331331
name: String,
332332
vers: String,
333333
kind: String,
334+
artifact: Option<(String, Option<String>)>,
334335
target: Option<String>,
335336
features: Vec<String>,
336337
registry: Option<String>,
@@ -591,6 +592,7 @@ impl Package {
591592
"features": dep.features,
592593
"default_features": true,
593594
"target": dep.target,
595+
"artifact": dep.artifact,
594596
"optional": dep.optional,
595597
"kind": dep.kind,
596598
"registry": registry_url,
@@ -744,6 +746,12 @@ impl Package {
744746
"#,
745747
target, kind, dep.name, dep.vers
746748
));
749+
if let Some((artifact, target)) = &dep.artifact {
750+
manifest.push_str(&format!("artifact = \"{}\"\n", artifact));
751+
if let Some(target) = &target {
752+
manifest.push_str(&format!("target = \"{}\"\n", target))
753+
}
754+
}
747755
if let Some(registry) = &dep.registry {
748756
assert_eq!(registry, "alternative");
749757
manifest.push_str(&format!("registry-index = \"{}\"", alt_registry_url()));
@@ -799,6 +807,7 @@ impl Dependency {
799807
name: name.to_string(),
800808
vers: vers.to_string(),
801809
kind: "normal".to_string(),
810+
artifact: None,
802811
target: None,
803812
features: Vec::new(),
804813
package: None,
@@ -825,6 +834,13 @@ impl Dependency {
825834
self
826835
}
827836

837+
/// Change the artifact to be of the given kind, like "bin", or "staticlib",
838+
/// along with a specific target triple if provided.
839+
pub fn artifact(&mut self, kind: &str, target: Option<String>) -> &mut Self {
840+
self.artifact = Some((kind.to_string(), target));
841+
self
842+
}
843+
828844
/// Adds `registry = $registry` to this dependency.
829845
pub fn registry(&mut self, registry: &str) -> &mut Self {
830846
self.registry = Some(registry.to_string());

src/cargo/core/compiler/artifact.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/// Generate artifact information from unit dependencies for configuring the compiler environment.
2+
use crate::core::compiler::unit_graph::UnitDep;
3+
use crate::core::compiler::{Context, CrateType, FileFlavor, Unit};
4+
use crate::core::TargetKind;
5+
use crate::CargoResult;
6+
use std::collections::HashMap;
7+
use std::ffi::OsString;
8+
9+
/// Return all environment variables for the given unit-dependencies
10+
/// if artifacts are present.
11+
pub fn get_env(
12+
cx: &Context<'_, '_>,
13+
dependencies: &[UnitDep],
14+
) -> CargoResult<HashMap<String, OsString>> {
15+
let mut env = HashMap::new();
16+
for unit_dep in dependencies.iter().filter(|d| d.unit.artifact.is_true()) {
17+
for artifact_path in cx
18+
.outputs(&unit_dep.unit)?
19+
.iter()
20+
.filter_map(|f| (f.flavor == FileFlavor::Normal).then(|| &f.path))
21+
{
22+
let artifact_type_upper = unit_artifact_type_name_upper(&unit_dep.unit);
23+
let dep_name = unit_dep.dep_name.unwrap_or(unit_dep.unit.pkg.name());
24+
let dep_name_upper = dep_name.to_uppercase().replace("-", "_");
25+
26+
let var = format!("CARGO_{}_DIR_{}", artifact_type_upper, dep_name_upper);
27+
let path = artifact_path.parent().expect("parent dir for artifacts");
28+
env.insert(var, path.to_owned().into());
29+
30+
let var = format!(
31+
"CARGO_{}_FILE_{}_{}",
32+
artifact_type_upper,
33+
dep_name_upper,
34+
unit_dep.unit.target.name()
35+
);
36+
env.insert(var, artifact_path.to_owned().into());
37+
38+
if unit_dep.unit.target.name() == dep_name.as_str() {
39+
let var = format!("CARGO_{}_FILE_{}", artifact_type_upper, dep_name_upper,);
40+
env.insert(var, artifact_path.to_owned().into());
41+
}
42+
}
43+
}
44+
Ok(env)
45+
}
46+
47+
fn unit_artifact_type_name_upper(unit: &Unit) -> &'static str {
48+
match unit.target.kind() {
49+
TargetKind::Lib(kinds) => match kinds.as_slice() {
50+
&[CrateType::Cdylib] => "CDYLIB",
51+
&[CrateType::Staticlib] => "STATICLIB",
52+
invalid => unreachable!("BUG: artifacts cannot be of type {:?}", invalid),
53+
},
54+
TargetKind::Bin => "BIN",
55+
invalid => unreachable!("BUG: artifacts cannot be of type {:?}", invalid),
56+
}
57+
}

src/cargo/core/compiler/build_context/target_info.rs

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::core::compiler::{
22
BuildOutput, CompileKind, CompileMode, CompileTarget, Context, CrateType,
33
};
4-
use crate::core::{Dependency, Target, TargetKind, Workspace};
4+
use crate::core::{Dependency, Package, Target, TargetKind, Workspace};
55
use crate::util::config::{Config, StringList, TargetConfig};
66
use crate::util::{CargoResult, Rustc};
77
use anyhow::Context as _;
@@ -748,11 +748,17 @@ impl<'cfg> RustcTargetData<'cfg> {
748748
// Get all kinds we currently know about.
749749
//
750750
// For now, targets can only ever come from the root workspace
751-
// units as artifact dependencies are not a thing yet, so this
752-
// correctly represents all the kinds that can happen. When we
753-
// have artifact dependencies or other ways for targets to
754-
// appear at places that are not the root units, we may have
755-
// to revisit this.
751+
// units and artifact dependencies, so this
752+
// correctly represents all the kinds that can happen. When we have
753+
// other ways for targets to appear at places that are not the root units,
754+
// we may have to revisit this.
755+
fn artifact_targets(package: &Package) -> impl Iterator<Item = CompileKind> + '_ {
756+
package
757+
.manifest()
758+
.dependencies()
759+
.iter()
760+
.filter_map(|d| d.artifact()?.target()?.to_compile_kind())
761+
}
756762
let all_kinds = requested_kinds
757763
.iter()
758764
.copied()
@@ -761,25 +767,32 @@ impl<'cfg> RustcTargetData<'cfg> {
761767
.default_kind()
762768
.into_iter()
763769
.chain(p.manifest().forced_kind())
770+
.chain(artifact_targets(p))
764771
}));
765772
for kind in all_kinds {
766-
if let CompileKind::Target(target) = kind {
767-
if !res.target_config.contains_key(&target) {
768-
res.target_config
769-
.insert(target, res.config.target_cfg_triple(target.short_name())?);
770-
}
771-
if !res.target_info.contains_key(&target) {
772-
res.target_info.insert(
773-
target,
774-
TargetInfo::new(res.config, &res.requested_kinds, &res.rustc, kind)?,
775-
);
776-
}
777-
}
773+
res.merge_compile_kind(kind)?;
778774
}
779775

780776
Ok(res)
781777
}
782778

779+
/// Insert `kind` into our `target_info` and `target_config` members if it isn't present yet.
780+
fn merge_compile_kind(&mut self, kind: CompileKind) -> CargoResult<()> {
781+
if let CompileKind::Target(target) = kind {
782+
if !self.target_config.contains_key(&target) {
783+
self.target_config
784+
.insert(target, self.config.target_cfg_triple(target.short_name())?);
785+
}
786+
if !self.target_info.contains_key(&target) {
787+
self.target_info.insert(
788+
target,
789+
TargetInfo::new(self.config, &self.requested_kinds, &self.rustc, kind)?,
790+
);
791+
}
792+
}
793+
Ok(())
794+
}
795+
783796
/// Returns a "short" name for the given kind, suitable for keying off
784797
/// configuration in Cargo or presenting to users.
785798
pub fn short_name<'a>(&'a self, kind: &'a CompileKind) -> &'a str {

src/cargo/core/compiler/compilation.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ pub struct Doctest {
2525
///
2626
/// This is used for indexing [`Compilation::extra_env`].
2727
pub script_meta: Option<Metadata>,
28+
29+
/// Environment variables to set in the rustdoc process.
30+
pub env: HashMap<String, OsString>,
2831
}
2932

3033
/// Information about the output of a unit.
@@ -190,14 +193,14 @@ impl<'cfg> Compilation<'cfg> {
190193
) -> CargoResult<ProcessBuilder> {
191194
let rustdoc = ProcessBuilder::new(&*self.config.rustdoc()?);
192195
let cmd = fill_rustc_tool_env(rustdoc, unit);
193-
let mut p = self.fill_env(cmd, &unit.pkg, script_meta, unit.kind, true)?;
194-
unit.target.edition().cmd_edition_arg(&mut p);
196+
let mut cmd = self.fill_env(cmd, &unit.pkg, script_meta, unit.kind, true)?;
197+
unit.target.edition().cmd_edition_arg(&mut cmd);
195198

196199
for crate_type in unit.target.rustc_crate_types() {
197-
p.arg("--crate-type").arg(crate_type.as_str());
200+
cmd.arg("--crate-type").arg(crate_type.as_str());
198201
}
199202

200-
Ok(p)
203+
Ok(cmd)
201204
}
202205

203206
/// Returns a [`ProcessBuilder`] appropriate for running a process for the

src/cargo/core/compiler/compile_kind.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ impl CompileTarget {
158158
/// Typically this is pretty much the same as `short_name`, but for the case
159159
/// of JSON target files this will be a full canonicalized path name for the
160160
/// current filesystem.
161-
pub fn rustc_target(&self) -> &str {
162-
&self.name
161+
pub fn rustc_target(&self) -> InternedString {
162+
self.name
163163
}
164164

165165
/// Returns a "short" version of the target name suitable for usage within

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

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
201201
self.build_script_dir(unit)
202202
} else if unit.target.is_example() {
203203
self.layout(unit.kind).examples().to_path_buf()
204+
} else if unit.artifact.is_true() {
205+
self.artifact_dir(unit)
204206
} else {
205207
self.deps_dir(unit).to_path_buf()
206208
}
@@ -287,6 +289,30 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
287289
self.layout(CompileKind::Host).build().join(dir)
288290
}
289291

292+
/// Returns the directory for compiled artifacts files.
293+
/// `/path/to/target/{debug,release}/deps/artifact/KIND/PKG-HASH`
294+
fn artifact_dir(&self, unit: &Unit) -> PathBuf {
295+
assert!(self.metas.contains_key(unit));
296+
assert!(unit.artifact.is_true());
297+
let dir = self.pkg_dir(unit);
298+
let kind = match unit.target.kind() {
299+
TargetKind::Bin => "bin",
300+
TargetKind::Lib(lib_kinds) => match lib_kinds.as_slice() {
301+
&[CrateType::Cdylib] => "cdylib",
302+
&[CrateType::Staticlib] => "staticlib",
303+
invalid => unreachable!(
304+
"BUG: unexpected artifact library type(s): {:?} - these should have been split",
305+
invalid
306+
),
307+
},
308+
invalid => unreachable!(
309+
"BUG: {:?} are not supposed to be used as artifacts",
310+
invalid
311+
),
312+
};
313+
self.layout(unit.kind).artifact().join(dir).join(kind)
314+
}
315+
290316
/// Returns the directory where information about running a build script
291317
/// is stored.
292318
/// `/path/to/target/{debug,release}/build/PKG-HASH`
@@ -354,7 +380,12 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
354380
if unit.mode != CompileMode::Build || file_type.flavor == FileFlavor::Rmeta {
355381
return None;
356382
}
357-
// Only uplift:
383+
384+
// Artifact dependencies are never uplifted.
385+
if unit.artifact.is_true() {
386+
return None;
387+
}
388+
358389
// - Binaries: The user always wants to see these, even if they are
359390
// implicitly built (for example for integration tests).
360391
// - dylibs: This ensures that the dynamic linker pulls in all the

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
33
use std::sync::{Arc, Mutex};
44

55
use crate::core::compiler::compilation::{self, UnitOutput};
6-
use crate::core::compiler::{self, Unit};
6+
use crate::core::compiler::{self, artifact, Unit};
77
use crate::core::PackageId;
88
use crate::util::errors::CargoResult;
99
use crate::util::profile;
@@ -133,7 +133,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
133133

134134
// We need to make sure that if there were any previous docs
135135
// already compiled, they were compiled with the same Rustc version that we're currently
136-
// using. Otherways we must remove the `doc/` folder and compile again forcing a rebuild.
136+
// using. Otherwise we must remove the `doc/` folder and compile again forcing a rebuild.
137137
//
138138
// This is important because the `.js`/`.html` & `.css` files that are generated by Rustc don't have
139139
// any versioning (See https://github.com/rust-lang/cargo/issues/8461).
@@ -262,6 +262,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
262262
unstable_opts,
263263
linker: self.bcx.linker(unit.kind),
264264
script_meta,
265+
env: artifact::get_env(&self, self.unit_deps(unit))?,
265266
});
266267
}
267268

src/cargo/core/compiler/custom_build.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::job::{Freshness, Job, Work};
22
use super::{fingerprint, Context, LinkType, Unit};
3+
use crate::core::compiler::artifact;
34
use crate::core::compiler::context::Metadata;
45
use crate::core::compiler::job_queue::JobState;
56
use crate::core::{profiles::ProfileRoot, PackageId, Target};
@@ -203,6 +204,11 @@ fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult<Job> {
203204
.env("RUSTDOC", &*bcx.config.rustdoc()?)
204205
.inherit_jobserver(&cx.jobserver);
205206

207+
// Find all artifact dependencies and make their file and containing directory discoverable using environment variables.
208+
for (var, value) in artifact::get_env(cx, dependencies)? {
209+
cmd.env(&var, value);
210+
}
211+
206212
if let Some(linker) = &bcx.target_data.target_config(unit.kind).linker {
207213
cmd.env(
208214
"RUSTC_LINKER",

src/cargo/core/compiler/job_queue.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,8 @@ impl<'cfg> JobQueue<'cfg> {
395395
.filter(|dep| {
396396
// Binaries aren't actually needed to *compile* tests, just to run
397397
// them, so we don't include this dependency edge in the job graph.
398-
!dep.unit.target.is_test() && !dep.unit.target.is_bin()
398+
(!dep.unit.target.is_test() && !dep.unit.target.is_bin())
399+
|| dep.unit.artifact.is_true()
399400
})
400401
.map(|dep| {
401402
// Handle the case here where our `unit -> dep` dependency may

src/cargo/core/compiler/layout.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
//! # prevent collisions. One notable exception is dynamic libraries.
4848
//! deps/
4949
//!
50+
//! # Each artifact dependency gets in its own directory.
51+
//! /artifact/$pkgname-$META/$kind
52+
//!
5053
//! # Root directory for all compiled examples.
5154
//! examples/
5255
//!
@@ -117,6 +120,8 @@ pub struct Layout {
117120
deps: PathBuf,
118121
/// The directory for build scripts: `$dest/build`
119122
build: PathBuf,
123+
/// The directory for artifacts, i.e. binaries, cdylibs, staticlibs: `$dest/deps/artifact`
124+
artifact: PathBuf,
120125
/// The directory for incremental files: `$dest/incremental`
121126
incremental: PathBuf,
122127
/// The directory for fingerprints: `$dest/.fingerprint`
@@ -164,10 +169,13 @@ impl Layout {
164169
let lock = dest.open_rw(".cargo-lock", ws.config(), "build directory")?;
165170
let root = root.into_path_unlocked();
166171
let dest = dest.into_path_unlocked();
172+
let deps = dest.join("deps");
173+
let artifact = deps.join("artifact");
167174

168175
Ok(Layout {
169-
deps: dest.join("deps"),
176+
deps,
170177
build: dest.join("build"),
178+
artifact,
171179
incremental: dest.join("incremental"),
172180
fingerprint: dest.join(".fingerprint"),
173181
examples: dest.join("examples"),
@@ -222,6 +230,10 @@ impl Layout {
222230
pub fn build(&self) -> &Path {
223231
&self.build
224232
}
233+
/// Fetch the artifact path.
234+
pub fn artifact(&self) -> &Path {
235+
&self.artifact
236+
}
225237
/// Create and return the tmp path.
226238
pub fn prepare_tmp(&self) -> CargoResult<&Path> {
227239
paths::create_dir_all(&self.tmp)?;

0 commit comments

Comments
 (0)