Skip to content

Commit 3a1b3fc

Browse files
authored
Add -Z json-target-spec (#16557)
This adds the `-Z json-target-spec` CLI flag to assist with using custom `.json` target spec files. `rustc` recently switched so that it requires `-Z unstable-options` when using custom spec files (rust-lang/rust#151534). This can make it rather awkward to use spec files with cargo because it then requires setting RUSTFLAGS and RUSTDOCFLAGS to pass `-Zunstable-options`. It also ends up causing some confusing error messages. See the individual commits for more details. This ended up being a larger diff than I wanted. I'm not 100% certain this is worth it, but I think it significantly improves the experience using `.json` files, so I I'm leaning towards it. ### How to test and review this PR? Testing can be done with `rustc` built from rust-lang/rust#151534 to ensure that everything passes after that PR (including setting `CARGO_RUN_BUILD_STD_TESTS=1`).
2 parents a2a76a5 + b78dc10 commit 3a1b3fc

14 files changed

Lines changed: 250 additions & 130 deletions

File tree

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

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,7 @@ impl TargetInfo {
200200
process.inherit_jobserver(client);
201201
}
202202

203-
if let CompileKind::Target(target) = kind {
204-
process.arg("--target").arg(target.rustc_target());
205-
}
203+
kind.add_target_arg(&mut process);
206204

207205
let crate_type_process = process.clone();
208206
const KNOWN_CRATE_TYPES: &[CrateType] = &[
@@ -331,11 +329,7 @@ impl TargetInfo {
331329
.args(&rustflags)
332330
.env_remove("RUSTC_LOG");
333331

334-
if let CompileKind::Target(target) = kind {
335-
target_spec_process
336-
.arg("--target")
337-
.arg(target.rustc_target());
338-
}
332+
kind.add_target_arg(&mut target_spec_process);
339333

340334
#[derive(Deserialize)]
341335
struct Metadata {
@@ -965,7 +959,7 @@ impl<'gctx> RustcTargetData<'gctx> {
965959
let mut target_config = HashMap::new();
966960
let mut target_info = HashMap::new();
967961
let target_applies_to_host = gctx.target_applies_to_host()?;
968-
let host_target = CompileTarget::new(&rustc.host)?;
962+
let host_target = CompileTarget::new(&rustc.host, gctx.cli_unstable().json_target_spec)?;
969963
let host_info = TargetInfo::new(gctx, requested_kinds, &rustc, CompileKind::Host)?;
970964

971965
// This config is used for link overrides and choosing a linker.

src/cargo/core/compiler/compile_kind.rs

Lines changed: 57 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use crate::util::errors::CargoResult;
55
use crate::util::interning::InternedString;
66
use crate::util::{GlobalContext, StableHasher, try_canonicalize};
77
use anyhow::Context as _;
8+
use anyhow::bail;
9+
use cargo_util::ProcessBuilder;
810
use serde::Serialize;
911
use std::collections::BTreeSet;
1012
use std::fs;
@@ -92,9 +94,15 @@ impl CompileKind {
9294

9395
if value.as_str() == "host-tuple" {
9496
let host_triple = env!("RUST_HOST_TARGET");
95-
Ok(CompileKind::Target(CompileTarget::new(host_triple)?))
97+
Ok(CompileKind::Target(CompileTarget::new(
98+
host_triple,
99+
gctx.cli_unstable().json_target_spec,
100+
)?))
96101
} else {
97-
Ok(CompileKind::Target(CompileTarget::new(value.as_str())?))
102+
Ok(CompileKind::Target(CompileTarget::new(
103+
value.as_str(),
104+
gctx.cli_unstable().json_target_spec,
105+
)?))
98106
}
99107
})
100108
// First collect into a set to deduplicate any `--target` passed
@@ -132,6 +140,17 @@ impl CompileKind {
132140
CompileKind::Target(target) => target.fingerprint_hash(),
133141
}
134142
}
143+
144+
/// Adds the `--target` flag to the given [`ProcessBuilder`] if this is a
145+
/// non-host build.
146+
pub fn add_target_arg(&self, builder: &mut ProcessBuilder) {
147+
if let CompileKind::Target(target) = self {
148+
builder.arg("--target").arg(target.rustc_target());
149+
if matches!(target, CompileTarget::Json { .. }) {
150+
builder.arg("-Zunstable-options");
151+
}
152+
}
153+
}
135154
}
136155

137156
impl serde::ser::Serialize for CompileKind {
@@ -141,7 +160,7 @@ impl serde::ser::Serialize for CompileKind {
141160
{
142161
match self {
143162
CompileKind::Host => None::<&str>.serialize(s),
144-
CompileKind::Target(t) => Some(t.name).serialize(s),
163+
CompileKind::Target(t) => Some(t.rustc_target()).serialize(s),
145164
}
146165
}
147166
}
@@ -164,31 +183,39 @@ impl serde::ser::Serialize for CompileKind {
164183
/// file stem of JSON target files. For built-in rustc targets this is just an
165184
/// uninterpreted string basically.
166185
#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord, Serialize)]
167-
pub struct CompileTarget {
168-
name: InternedString,
186+
pub enum CompileTarget {
187+
Tuple(InternedString),
188+
Json {
189+
short: InternedString,
190+
path: InternedString,
191+
},
169192
}
170193

171194
impl CompileTarget {
172-
pub fn new(name: &str) -> CargoResult<CompileTarget> {
195+
pub fn new(name: &str, unstable_json: bool) -> CargoResult<CompileTarget> {
173196
let name = name.trim();
174197
if name.is_empty() {
175-
anyhow::bail!("target was empty");
198+
bail!("target was empty");
176199
}
177200
if !name.ends_with(".json") {
178-
return Ok(CompileTarget { name: name.into() });
201+
return Ok(CompileTarget::Tuple(name.into()));
202+
}
203+
204+
if !unstable_json {
205+
bail!("`.json` target specs require -Zjson-target-spec");
179206
}
180207

181208
// If `name` ends in `.json` then it's likely a custom target
182209
// specification. Canonicalize the path to ensure that different builds
183210
// with different paths always produce the same result.
184-
let path = try_canonicalize(Path::new(name))
185-
.with_context(|| format!("target path {:?} is not a valid file", name))?;
186-
187-
let name = path
188-
.into_os_string()
189-
.into_string()
190-
.map_err(|_| anyhow::format_err!("target path is not valid unicode"))?;
191-
Ok(CompileTarget { name: name.into() })
211+
let p = try_canonicalize(Path::new(name))
212+
.with_context(|| format!("target path `{name}` is not a valid file"))?;
213+
let path = p
214+
.to_str()
215+
.ok_or_else(|| anyhow::format_err!("target path `{name}` is not valid unicode"))?
216+
.into();
217+
let short = p.file_stem().unwrap().to_str().unwrap().into();
218+
Ok(CompileTarget::Json { short, path })
192219
}
193220

194221
/// Returns the full unqualified name of this target, suitable for passing
@@ -198,7 +225,10 @@ impl CompileTarget {
198225
/// of JSON target files this will be a full canonicalized path name for the
199226
/// current filesystem.
200227
pub fn rustc_target(&self) -> InternedString {
201-
self.name
228+
match self {
229+
CompileTarget::Tuple(name) => *name,
230+
CompileTarget::Json { path, .. } => *path,
231+
}
202232
}
203233

204234
/// Returns a "short" version of the target name suitable for usage within
@@ -208,34 +238,25 @@ impl CompileTarget {
208238
/// JSON target files this returns just the file stem (e.g. `foo` out of
209239
/// `foo.json`) instead of the full path.
210240
pub fn short_name(&self) -> &str {
211-
// Flexible target specifications often point at json files, so if it
212-
// looks like we've got one of those just use the file stem (the file
213-
// name without ".json") as a short name for this target. Note that the
214-
// `unwrap()` here should never trigger since we have a nonempty name
215-
// and it starts as utf-8 so it's always utf-8
216-
if self.name.ends_with(".json") {
217-
Path::new(&self.name).file_stem().unwrap().to_str().unwrap()
218-
} else {
219-
&self.name
241+
match self {
242+
CompileTarget::Tuple(name) => name,
243+
CompileTarget::Json { short, .. } => short,
220244
}
221245
}
222246

223247
/// See [`CompileKind::fingerprint_hash`].
224248
pub fn fingerprint_hash(&self) -> u64 {
225249
let mut hasher = StableHasher::new();
226-
match self
227-
.name
228-
.ends_with(".json")
229-
.then(|| fs::read_to_string(self.name))
230-
{
231-
Some(Ok(contents)) => {
250+
match self {
251+
CompileTarget::Tuple(name) => name.hash(&mut hasher),
252+
CompileTarget::Json { path, .. } => {
232253
// This may have some performance concerns, since it is called
233254
// fairly often. If that ever seems worth fixing, consider
234255
// embedding this in `CompileTarget`.
235-
contents.hash(&mut hasher);
236-
}
237-
_ => {
238-
self.name.hash(&mut hasher);
256+
match fs::read_to_string(path) {
257+
Ok(contents) => contents.hash(&mut hasher),
258+
Err(_) => path.hash(&mut hasher),
259+
}
239260
}
240261
}
241262
Hasher::finish(&hasher)

src/cargo/core/compiler/mod.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -864,9 +864,7 @@ fn prepare_rustdoc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResu
864864
add_path_args(bcx.ws, unit, &mut rustdoc);
865865
add_cap_lints(bcx, unit, &mut rustdoc);
866866

867-
if let CompileKind::Target(target) = unit.kind {
868-
rustdoc.arg("--target").arg(target.rustc_target());
869-
}
867+
unit.kind.add_target_arg(&mut rustdoc);
870868
let doc_dir = build_runner.files().out_dir(unit);
871869
rustdoc.arg("-o").arg(&doc_dir);
872870
rustdoc.args(&features_args(unit));
@@ -1414,9 +1412,7 @@ fn build_base_args(
14141412
}
14151413
}
14161414

1417-
if let CompileKind::Target(n) = unit.kind {
1418-
cmd.arg("--target").arg(n.rustc_target());
1419-
}
1415+
unit.kind.add_target_arg(cmd);
14201416

14211417
opt(
14221418
cmd,

src/cargo/core/dependency.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,7 @@ impl Artifact {
496496
artifacts: &[impl AsRef<str>],
497497
is_lib: bool,
498498
target: Option<&str>,
499+
unstable_json: bool,
499500
) -> CargoResult<Self> {
500501
let kinds = ArtifactKind::validate(
501502
artifacts
@@ -506,7 +507,9 @@ impl Artifact {
506507
Ok(Artifact {
507508
inner: Arc::new(kinds),
508509
is_lib,
509-
target: target.map(ArtifactTarget::parse).transpose()?,
510+
target: target
511+
.map(|name| ArtifactTarget::parse(name, unstable_json))
512+
.transpose()?,
510513
})
511514
}
512515

@@ -536,10 +539,10 @@ pub enum ArtifactTarget {
536539
}
537540

538541
impl ArtifactTarget {
539-
pub fn parse(target: &str) -> CargoResult<ArtifactTarget> {
542+
pub fn parse(target: &str, unstable_json: bool) -> CargoResult<ArtifactTarget> {
540543
Ok(match target {
541544
"target" => ArtifactTarget::BuildDependencyAssumeTarget,
542-
name => ArtifactTarget::Force(CompileTarget::new(name)?),
545+
name => ArtifactTarget::Force(CompileTarget::new(name, unstable_json)?),
543546
})
544547
}
545548

src/cargo/core/features.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,7 @@ unstable_cli_options!(
870870
#[serde(deserialize_with = "deserialize_gitoxide_features")]
871871
gitoxide: Option<GitoxideFeatures> = ("Use gitoxide for the given git interactions, or all of them if no argument is given"),
872872
host_config: bool = ("Enable the `[host]` section in the .cargo/config.toml file"),
873+
json_target_spec: bool = ("Enable `.json` target spec files"),
873874
lockfile_path: bool = ("Enable the `resolver.lockfile-path` config option"),
874875
minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum"),
875876
msrv_policy: bool = ("Enable rust-version aware policy within cargo"),
@@ -1409,6 +1410,7 @@ impl CliUnstable {
14091410
)?
14101411
}
14111412
"host-config" => self.host_config = parse_empty(k, v)?,
1413+
"json-target-spec" => self.json_target_spec = parse_empty(k, v)?,
14121414
"lockfile-path" => self.lockfile_path = parse_empty(k, v)?,
14131415
"next-lockfile-bump" => self.next_lockfile_bump = parse_empty(k, v)?,
14141416
"minimal-versions" => self.minimal_versions = parse_empty(k, v)?,

src/cargo/core/resolver/features.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,7 @@ impl<'a, 'gctx> FeatureResolver<'a, 'gctx> {
811811
pkg_id: PackageId,
812812
dep: &Dependency,
813813
lib_fk: FeaturesFor,
814+
unstable_json_spec: bool,
814815
) -> CargoResult<Vec<FeaturesFor>> {
815816
let Some(artifact) = dep.artifact() else {
816817
return Ok(vec![lib_fk]);
@@ -844,7 +845,9 @@ impl<'a, 'gctx> FeatureResolver<'a, 'gctx> {
844845
ArtifactTarget::BuildDependencyAssumeTarget => {
845846
for kind in this.requested_targets {
846847
let target = match kind {
847-
CompileKind::Host => CompileTarget::new(&host_triple).unwrap(),
848+
CompileKind::Host => {
849+
CompileTarget::new(&host_triple, unstable_json_spec).unwrap()
850+
}
848851
CompileKind::Target(target) => *target,
849852
};
850853
activate_target(target)?;
@@ -859,6 +862,7 @@ impl<'a, 'gctx> FeatureResolver<'a, 'gctx> {
859862
Ok(result)
860863
}
861864

865+
let unstable_json_spec = self.ws.gctx().cli_unstable().json_target_spec;
862866
self.resolve
863867
.deps(pkg_id)
864868
.map(|(dep_id, deps)| {
@@ -915,7 +919,8 @@ impl<'a, 'gctx> FeatureResolver<'a, 'gctx> {
915919
fk
916920
};
917921

918-
let dep_fks = artifact_features_for(self, pkg_id, dep, lib_fk)?;
922+
let dep_fks =
923+
artifact_features_for(self, pkg_id, dep, lib_fk, unstable_json_spec)?;
919924
Ok(dep_fks.into_iter().map(move |dep_fk| (dep, dep_fk)))
920925
})
921926
.flatten_ok()

src/cargo/ops/cargo_compile/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,7 @@ pub fn print<'a>(
221221
if let Some(args) = target_rustc_args {
222222
process.args(args);
223223
}
224-
if let CompileKind::Target(t) = kind {
225-
process.arg("--target").arg(t.rustc_target());
226-
}
224+
kind.add_target_arg(&mut process);
227225
process.arg("--print").arg(print_opt_value);
228226
process.exec()?;
229227
}
@@ -400,7 +398,10 @@ pub fn create_bcx<'a, 'gctx>(
400398
// If `--target` has not been specified, then the unit graph is built
401399
// assuming `--target $HOST` was specified. See
402400
// `rebuild_unit_graph_shared` for more on why this is done.
403-
let explicit_host_kind = CompileKind::Target(CompileTarget::new(&target_data.rustc.host)?);
401+
let explicit_host_kind = CompileKind::Target(CompileTarget::new(
402+
&target_data.rustc.host,
403+
gctx.cli_unstable().json_target_spec,
404+
)?);
404405
let explicit_host_kinds: Vec<_> = build_config
405406
.requested_kinds
406407
.iter()

src/cargo/ops/cargo_test.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::core::compiler::{Compilation, CompileKind, Doctest, Unit, UnitHash, UnitOutput};
1+
use crate::core::compiler::{Compilation, Doctest, Unit, UnitHash, UnitOutput};
22
use crate::core::profiles::PanicStrategy;
33
use crate::core::shell::ColorChoice;
44
use crate::core::shell::Verbosity;
@@ -211,10 +211,7 @@ fn run_doc_tests(
211211
add_path_args(ws, unit, &mut p);
212212
p.arg("--test-run-directory").arg(unit.pkg.root());
213213

214-
if let CompileKind::Target(target) = unit.kind {
215-
// use `rustc_target()` to properly handle JSON target paths
216-
p.arg("--target").arg(target.rustc_target());
217-
}
214+
unit.kind.add_target_arg(&mut p);
218215

219216
if let Some((runtool, runtool_args)) = compilation.target_runner(unit.kind) {
220217
p.arg("--test-runtool").arg(runtool);

0 commit comments

Comments
 (0)