Skip to content

Commit ba6fb40

Browse files
committed
Auto merge of #13713 - epage:no-auto, r=weihanglo
fix(toml): Warn, rather than fail publish, if a target is excluded ### What does this PR try to resolve? We have a couple of problems with publishing - Inconsistent errors: if a target that `package` doesn't verify is missing `path`, it will error, while one with `path` won't, see #13456 - Users may want to exclude targets and their choices are - Go ahead and include them. I originally excluded my examples before doc-scraping was a think. The problem was if I had to set `required-features`, I then could no longer exclude them - Muck with `Cargo.toml` during publish and pass `--allow-dirty` This fixes both by auto-stripping targets on publish. We will warn the user that we did so. This is a mostly-one-way door on behavior because we are turning an error case into a warning. For the most part, I think this is the right thing to do. My biggest regret is that the warning is only during `package`/`publish` as it will be too late to act on it and people who want to know will want to know when the problem is introduced. The error is also very late in the process but at least its before a non-reversible action has been taken. Dry-run and `yank` help. Fixes #13456 Fixes #5806 ### How should we test and review this PR? Tests are added in the first commit and you can then follow the commits to see how the test output evolved. The biggest risk factors for this change are - If the target-stripping logic mis-identifies a path as excluded because of innocuous path differences (e.g. case) - Setting a minimum MSRV for published packages: `auto*` were added in 1.27 (#5335) but were insta-stable. `autobins = false` did nothing until 1.32 (#6329). I have not checked to see how this behaves pre-1.32 or pre-1.27. Since my memory of that error is vague, I believe it will either do a redundant discovery *or* it will implicitly skip discovery Resolved risks - #13729 ensured our generated target paths don't have `\` in them - #13729 ensures the paths are normalize so the list of packaged paths For case-insensitive filesystems, I added tests to show the original behavior (works locally but will fail when depended on from a case-sensitive filesystem) and tracked how that changed with this PR (on publish warn that those targets are stripped). We could try to normalize the case but it will also follow symlinks and is likely indicative of larger casing problems that the user had. Weighing how broken things are now , it didn't seem changing behavior on this would be too big of a deal. We should do a Call for Testing when this hits nightly to have people to `cargo package` and look for targets exclusion warnings that don't make sense. ### Additional information This builds on #13701 and the work before it. By enumerating all targets in `Cargo.toml`, it makes it so rust-lang/crates.io#5882 and rust-lang/crates.io#814 can be implemented without any other filesystem interactions. A follow up PR is need to make much of a difference in performance because we unconditionally walk the file system just in case `autodiscover != Some(false)` or a target is missing a `path`. We cannot turn off auto-discovery of libs, so that will always be done for bin-only packages.
2 parents d071c59 + 8cc2f39 commit ba6fb40

File tree

12 files changed

+1517
-112
lines changed

12 files changed

+1517
-112
lines changed

crates/cargo-util-schemas/src/manifest/mod.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,15 @@ impl TomlPackage {
215215
self.authors.as_ref().map(|v| v.resolved()).transpose()
216216
}
217217

218+
pub fn resolved_build(&self) -> Result<Option<&String>, UnresolvedError> {
219+
let readme = self.build.as_ref().ok_or(UnresolvedError)?;
220+
match readme {
221+
StringOrBool::Bool(false) => Ok(None),
222+
StringOrBool::Bool(true) => Err(UnresolvedError),
223+
StringOrBool::String(value) => Ok(Some(value)),
224+
}
225+
}
226+
218227
pub fn resolved_exclude(&self) -> Result<Option<&Vec<String>>, UnresolvedError> {
219228
self.exclude.as_ref().map(|v| v.resolved()).transpose()
220229
}
@@ -243,15 +252,12 @@ impl TomlPackage {
243252
}
244253

245254
pub fn resolved_readme(&self) -> Result<Option<&String>, UnresolvedError> {
246-
self.readme
247-
.as_ref()
248-
.map(|v| {
249-
v.resolved().and_then(|sb| match sb {
250-
StringOrBool::Bool(_) => Err(UnresolvedError),
251-
StringOrBool::String(value) => Ok(value),
252-
})
253-
})
254-
.transpose()
255+
let readme = self.readme.as_ref().ok_or(UnresolvedError)?;
256+
readme.resolved().and_then(|sb| match sb {
257+
StringOrBool::Bool(false) => Ok(None),
258+
StringOrBool::Bool(true) => Err(UnresolvedError),
259+
StringOrBool::String(value) => Ok(Some(value)),
260+
})
255261
}
256262

257263
pub fn resolved_keywords(&self) -> Result<Option<&Vec<String>>, UnresolvedError> {

src/cargo/core/manifest.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ impl Manifest {
483483
pub fn contents(&self) -> &str {
484484
self.contents.as_str()
485485
}
486+
/// See [`Manifest::resolved_toml`] for what "resolved" means
486487
pub fn to_resolved_contents(&self) -> CargoResult<String> {
487488
let toml = toml::to_string_pretty(self.resolved_toml())?;
488489
Ok(format!("{}\n{}", MANIFEST_PREAMBLE, toml))
@@ -496,6 +497,11 @@ impl Manifest {
496497
&self.original_toml
497498
}
498499
/// The [`TomlManifest`] with all fields expanded
500+
///
501+
/// This is the intersection of what fields need resolving for cargo-publish that also are
502+
/// useful for the operation of cargo, including
503+
/// - workspace inheritance
504+
/// - target discovery
499505
pub fn resolved_toml(&self) -> &TomlManifest {
500506
&self.resolved_toml
501507
}

src/cargo/ops/cargo_package.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,11 @@ fn tar(
689689

690690
let base_name = format!("{}-{}", pkg.name(), pkg.version());
691691
let base_path = Path::new(&base_name);
692-
let publish_pkg = prepare_for_publish(pkg, ws)?;
692+
let included = ar_files
693+
.iter()
694+
.map(|ar_file| ar_file.rel_path.clone())
695+
.collect::<Vec<_>>();
696+
let publish_pkg = prepare_for_publish(pkg, ws, &included)?;
693697

694698
let mut uncompressed_size = 0;
695699
for ar_file in ar_files {

src/cargo/util/toml/mod.rs

Lines changed: 114 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ fn to_workspace_root_config(
256256
ws_root_config
257257
}
258258

259+
/// See [`Manifest::resolved_toml`] for more details
259260
#[tracing::instrument(skip_all)]
260261
fn resolve_toml(
261262
original_toml: &manifest::TomlManifest,
@@ -264,7 +265,7 @@ fn resolve_toml(
264265
manifest_file: &Path,
265266
gctx: &GlobalContext,
266267
warnings: &mut Vec<String>,
267-
_errors: &mut Vec<String>,
268+
errors: &mut Vec<String>,
268269
) -> CargoResult<manifest::TomlManifest> {
269270
if let Some(workspace) = &original_toml.workspace {
270271
if workspace.resolver.as_deref() == Some("3") {
@@ -277,11 +278,11 @@ fn resolve_toml(
277278
package: None,
278279
project: None,
279280
profile: original_toml.profile.clone(),
280-
lib: original_toml.lib.clone(),
281-
bin: original_toml.bin.clone(),
282-
example: original_toml.example.clone(),
283-
test: original_toml.test.clone(),
284-
bench: original_toml.bench.clone(),
281+
lib: None,
282+
bin: None,
283+
example: None,
284+
test: None,
285+
bench: None,
285286
dependencies: None,
286287
dev_dependencies: None,
287288
dev_dependencies2: None,
@@ -318,6 +319,47 @@ fn resolve_toml(
318319
});
319320
resolved_toml.package = Some(resolved_package);
320321

322+
resolved_toml.lib = targets::resolve_lib(
323+
original_toml.lib.as_ref(),
324+
package_root,
325+
&original_package.name,
326+
edition,
327+
warnings,
328+
)?;
329+
resolved_toml.bin = Some(targets::resolve_bins(
330+
original_toml.bin.as_ref(),
331+
package_root,
332+
&original_package.name,
333+
edition,
334+
original_package.autobins,
335+
warnings,
336+
resolved_toml.lib.is_some(),
337+
)?);
338+
resolved_toml.example = Some(targets::resolve_examples(
339+
original_toml.example.as_ref(),
340+
package_root,
341+
edition,
342+
original_package.autoexamples,
343+
warnings,
344+
errors,
345+
)?);
346+
resolved_toml.test = Some(targets::resolve_tests(
347+
original_toml.test.as_ref(),
348+
package_root,
349+
edition,
350+
original_package.autotests,
351+
warnings,
352+
errors,
353+
)?);
354+
resolved_toml.bench = Some(targets::resolve_benches(
355+
original_toml.bench.as_ref(),
356+
package_root,
357+
edition,
358+
original_package.autobenches,
359+
warnings,
360+
errors,
361+
)?);
362+
321363
let activated_opt_deps = resolved_toml
322364
.features
323365
.as_ref()
@@ -494,7 +536,7 @@ fn resolve_package_toml<'a>(
494536
.map(|value| field_inherit_with(value, "authors", || inherit()?.authors()))
495537
.transpose()?
496538
.map(manifest::InheritableField::Value),
497-
build: original_package.build.clone(),
539+
build: targets::resolve_build(original_package.build.as_ref(), package_root),
498540
metabuild: original_package.metabuild.clone(),
499541
default_target: original_package.default_target.clone(),
500542
forced_target: original_package.forced_target.clone(),
@@ -519,10 +561,10 @@ fn resolve_package_toml<'a>(
519561
.map(manifest::InheritableField::Value),
520562
workspace: original_package.workspace.clone(),
521563
im_a_teapot: original_package.im_a_teapot.clone(),
522-
autobins: original_package.autobins.clone(),
523-
autoexamples: original_package.autoexamples.clone(),
524-
autotests: original_package.autotests.clone(),
525-
autobenches: original_package.autobenches.clone(),
564+
autobins: Some(false),
565+
autoexamples: Some(false),
566+
autotests: Some(false),
567+
autobenches: Some(false),
526568
default_run: original_package.default_run.clone(),
527569
description: original_package
528570
.description
@@ -553,7 +595,10 @@ fn resolve_package_toml<'a>(
553595
.transpose()?
554596
.as_ref(),
555597
)
556-
.map(|s| manifest::InheritableField::Value(StringOrBool::String(s))),
598+
.map(|s| manifest::InheritableField::Value(StringOrBool::String(s)))
599+
.or(Some(manifest::InheritableField::Value(StringOrBool::Bool(
600+
false,
601+
)))),
557602
keywords: original_package
558603
.keywords
559604
.clone()
@@ -1146,11 +1191,10 @@ fn to_real_manifest(
11461191
// If we have a lib with no path, use the inferred lib or else the package name.
11471192
let targets = to_targets(
11481193
&features,
1194+
&original_toml,
11491195
&resolved_toml,
1150-
package_name,
11511196
package_root,
11521197
edition,
1153-
&resolved_package.build,
11541198
&resolved_package.metabuild,
11551199
warnings,
11561200
errors,
@@ -2357,10 +2401,15 @@ fn unused_dep_keys(
23572401
}
23582402
}
23592403

2360-
pub fn prepare_for_publish(me: &Package, ws: &Workspace<'_>) -> CargoResult<Package> {
2404+
pub fn prepare_for_publish(
2405+
me: &Package,
2406+
ws: &Workspace<'_>,
2407+
included: &[PathBuf],
2408+
) -> CargoResult<Package> {
23612409
let contents = me.manifest().contents();
23622410
let document = me.manifest().document();
2363-
let original_toml = prepare_toml_for_publish(me.manifest().resolved_toml(), ws, me.root())?;
2411+
let original_toml =
2412+
prepare_toml_for_publish(me.manifest().resolved_toml(), ws, me.root(), included)?;
23642413
let resolved_toml = original_toml.clone();
23652414
let features = me.manifest().unstable_features().clone();
23662415
let workspace_config = me.manifest().workspace_config().clone();
@@ -2392,6 +2441,7 @@ fn prepare_toml_for_publish(
23922441
me: &manifest::TomlManifest,
23932442
ws: &Workspace<'_>,
23942443
package_root: &Path,
2444+
included: &[PathBuf],
23952445
) -> CargoResult<manifest::TomlManifest> {
23962446
let gctx = ws.gctx();
23972447

@@ -2408,11 +2458,21 @@ fn prepare_toml_for_publish(
24082458
package.workspace = None;
24092459
if let Some(StringOrBool::String(path)) = &package.build {
24102460
let path = paths::normalize_path(Path::new(path));
2411-
let path = path
2412-
.into_os_string()
2413-
.into_string()
2414-
.map_err(|_err| anyhow::format_err!("non-UTF8 `package.build`"))?;
2415-
package.build = Some(StringOrBool::String(normalize_path_string_sep(path)));
2461+
let build = if included.contains(&path) {
2462+
let path = path
2463+
.into_os_string()
2464+
.into_string()
2465+
.map_err(|_err| anyhow::format_err!("non-UTF8 `package.build`"))?;
2466+
let path = normalize_path_string_sep(path);
2467+
StringOrBool::String(path)
2468+
} else {
2469+
ws.gctx().shell().warn(format!(
2470+
"ignoring `package.build` as `{}` is not included in the published package",
2471+
path.display()
2472+
))?;
2473+
StringOrBool::Bool(false)
2474+
};
2475+
package.build = Some(build);
24162476
}
24172477
let current_resolver = package
24182478
.resolver
@@ -2502,14 +2562,14 @@ fn prepare_toml_for_publish(
25022562
}
25032563

25042564
let lib = if let Some(target) = &me.lib {
2505-
Some(prepare_target_for_publish(target, "library")?)
2565+
prepare_target_for_publish(target, included, "library", ws.gctx())?
25062566
} else {
25072567
None
25082568
};
2509-
let bin = prepare_targets_for_publish(me.bin.as_ref(), "binary")?;
2510-
let example = prepare_targets_for_publish(me.example.as_ref(), "example")?;
2511-
let test = prepare_targets_for_publish(me.test.as_ref(), "test")?;
2512-
let bench = prepare_targets_for_publish(me.bench.as_ref(), "benchmark")?;
2569+
let bin = prepare_targets_for_publish(me.bin.as_ref(), included, "binary", ws.gctx())?;
2570+
let example = prepare_targets_for_publish(me.example.as_ref(), included, "example", ws.gctx())?;
2571+
let test = prepare_targets_for_publish(me.test.as_ref(), included, "test", ws.gctx())?;
2572+
let bench = prepare_targets_for_publish(me.bench.as_ref(), included, "benchmark", ws.gctx())?;
25132573

25142574
let all = |_d: &manifest::TomlDependency| true;
25152575
let mut manifest = manifest::TomlManifest {
@@ -2667,31 +2727,51 @@ fn prepare_toml_for_publish(
26672727

26682728
fn prepare_targets_for_publish(
26692729
targets: Option<&Vec<manifest::TomlTarget>>,
2730+
included: &[PathBuf],
26702731
context: &str,
2732+
gctx: &GlobalContext,
26712733
) -> CargoResult<Option<Vec<manifest::TomlTarget>>> {
26722734
let Some(targets) = targets else {
26732735
return Ok(None);
26742736
};
26752737

26762738
let mut prepared = Vec::with_capacity(targets.len());
26772739
for target in targets {
2678-
let target = prepare_target_for_publish(target, context)?;
2740+
let Some(target) = prepare_target_for_publish(target, included, context, gctx)? else {
2741+
continue;
2742+
};
26792743
prepared.push(target);
26802744
}
26812745

2682-
Ok(Some(prepared))
2746+
if prepared.is_empty() {
2747+
Ok(None)
2748+
} else {
2749+
Ok(Some(prepared))
2750+
}
26832751
}
26842752

26852753
fn prepare_target_for_publish(
26862754
target: &manifest::TomlTarget,
2755+
included: &[PathBuf],
26872756
context: &str,
2688-
) -> CargoResult<manifest::TomlTarget> {
2689-
let mut target = target.clone();
2690-
if let Some(path) = target.path {
2691-
let path = normalize_path(&path.0);
2692-
target.path = Some(manifest::PathValue(normalize_path_sep(path, context)?));
2757+
gctx: &GlobalContext,
2758+
) -> CargoResult<Option<manifest::TomlTarget>> {
2759+
let path = target.path.as_ref().expect("previously resolved");
2760+
let path = normalize_path(&path.0);
2761+
if !included.contains(&path) {
2762+
let name = target.name.as_ref().expect("previously resolved");
2763+
gctx.shell().warn(format!(
2764+
"ignoring {context} `{name}` as `{}` is not included in the published package",
2765+
path.display()
2766+
))?;
2767+
return Ok(None);
26932768
}
2694-
Ok(target)
2769+
2770+
let mut target = target.clone();
2771+
let path = normalize_path_sep(path, context)?;
2772+
target.path = Some(manifest::PathValue(path.into()));
2773+
2774+
Ok(Some(target))
26952775
}
26962776

26972777
fn normalize_path_sep(path: PathBuf, context: &str) -> CargoResult<PathBuf> {

0 commit comments

Comments
 (0)