Skip to content

Commit f5d465a

Browse files
committed
feat(generate-lockfile): Add unstable --publish-time flag
Implementation for #5221 Use cases: - Improved reproduction steps using cargo scripts without including a lockfile - Debugging issues in the past - Manual stop gap for #15973 Unresolved questions: - How do we compensate for the caveats, with one option being to leave this perma-unstable? - How should we deal with Summary `pubtime` parse errors? - How strict should we be on the Summary `pubtime` format? - Should we offer a convenient way of getting a compatible timestamp? Future possibilities: - Ability to lock to a timestamp with `cargo update` updating the timestamp (could be useful for cargo scripts) This seemed like the shortest path to testing the proposed `pubtime` index entries so we can unblock other work on it like #15973. While this has some big caveats, the design is straightforward. In contrast, #15973 has a lot more design questions (is this a resolver-wide setting or a per-registry setting, what formats are accepted, etc) that could slow down development and testing `pubtime`.
1 parent a9fcb57 commit f5d465a

File tree

12 files changed

+187
-44
lines changed

12 files changed

+187
-44
lines changed

src/bin/cargo/commands/generate_lockfile.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
use clap_complete::engine::ArgValueCompleter;
2+
use clap_complete::engine::CompletionCandidate;
3+
14
use crate::command_prelude::*;
25

36
use cargo::ops;
@@ -9,13 +12,46 @@ pub fn cli() -> Command {
912
.arg_manifest_path()
1013
.arg_lockfile_path()
1114
.arg_ignore_rust_version_with_help("Ignore `rust-version` specification in packages")
15+
.arg(
16+
clap::Arg::new("publish-time")
17+
.long("publish-time")
18+
.value_name("yyyy-mm-ddThh:mm:ssZ")
19+
.add(ArgValueCompleter::new(datetime_completer))
20+
.help("Latest publish time allowed for registry packages (unstable)")
21+
.help_heading(heading::MANIFEST_OPTIONS)
22+
)
1223
.after_help(color_print::cstr!(
1324
"Run `<bright-cyan,bold>cargo help generate-lockfile</>` for more detailed information.\n"
1425
))
1526
}
1627

28+
fn datetime_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
29+
let mut completions = vec![];
30+
let Some(current) = current.to_str() else {
31+
return completions;
32+
};
33+
34+
if current.is_empty() {
35+
// While not likely what people want, it can at least give them a starting point to edit
36+
let timestamp = jiff::Timestamp::now();
37+
completions.push(CompletionCandidate::new(timestamp.to_string()));
38+
} else if let Ok(date) = current.parse::<jiff::civil::Date>() {
39+
if let Ok(zoned) = jiff::Zoned::default().with().date(date).build() {
40+
let timestamp = zoned.timestamp();
41+
completions.push(CompletionCandidate::new(timestamp.to_string()));
42+
}
43+
}
44+
completions
45+
}
46+
1747
pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
18-
let ws = args.workspace(gctx)?;
48+
let publish_time = args.get_one::<String>("publish-time");
49+
let mut ws = args.workspace(gctx)?;
50+
if let Some(publish_time) = publish_time {
51+
gctx.cli_unstable()
52+
.fail_if_stable_opt("--publish-time", 5221)?;
53+
ws.set_resolve_publish_time(publish_time.parse().map_err(anyhow::Error::from)?);
54+
}
1955
ops::generate_lockfile(&ws)?;
2056
Ok(())
2157
}

src/cargo/core/resolver/version_prefs.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub struct VersionPreferences {
2222
prefer_patch_deps: HashMap<InternedString, HashSet<Dependency>>,
2323
version_ordering: VersionOrdering,
2424
rust_versions: Vec<PartialVersion>,
25+
publish_time: Option<jiff::Timestamp>,
2526
}
2627

2728
#[derive(Copy, Clone, Default, PartialEq, Eq, Hash, Debug)]
@@ -53,6 +54,10 @@ impl VersionPreferences {
5354
self.rust_versions = vers;
5455
}
5556

57+
pub fn publish_time(&mut self, publish_time: jiff::Timestamp) {
58+
self.publish_time = Some(publish_time);
59+
}
60+
5661
/// Sort (and filter) the given vector of summaries in-place
5762
///
5863
/// Note: all summaries presumed to be for the same package.
@@ -77,6 +82,15 @@ impl VersionPreferences {
7782
.map(|deps| deps.iter().any(|d| d.matches_id(*pkg_id)))
7883
.unwrap_or(false)
7984
};
85+
if let Some(max_publish_time) = self.publish_time {
86+
summaries.retain(|s| {
87+
if let Some(summary_publish_time) = s.pubtime() {
88+
summary_publish_time <= max_publish_time
89+
} else {
90+
true
91+
}
92+
});
93+
}
8094
summaries.sort_unstable_by(|a, b| {
8195
let prefer_a = should_prefer(&a.package_id());
8296
let prefer_b = should_prefer(&b.package_id());

src/cargo/core/workspace.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ pub struct Workspace<'gctx> {
122122
resolve_honors_rust_version: bool,
123123
/// The feature unification mode used when building packages.
124124
resolve_feature_unification: FeatureUnification,
125+
/// Latest publish time allowed for packages
126+
resolve_publish_time: Option<jiff::Timestamp>,
125127
/// Workspace-level custom metadata
126128
custom_metadata: Option<toml::Value>,
127129

@@ -259,6 +261,7 @@ impl<'gctx> Workspace<'gctx> {
259261
resolve_behavior: ResolveBehavior::V1,
260262
resolve_honors_rust_version: false,
261263
resolve_feature_unification: FeatureUnification::Selected,
264+
resolve_publish_time: None,
262265
custom_metadata: None,
263266
local_overlays: HashMap::new(),
264267
}
@@ -717,6 +720,14 @@ impl<'gctx> Workspace<'gctx> {
717720
self.resolve_feature_unification
718721
}
719722

723+
pub fn set_resolve_publish_time(&mut self, publish_time: jiff::Timestamp) {
724+
self.resolve_publish_time = Some(publish_time);
725+
}
726+
727+
pub fn resolve_publish_time(&self) -> Option<jiff::Timestamp> {
728+
self.resolve_publish_time
729+
}
730+
720731
pub fn custom_metadata(&self) -> Option<&toml::Value> {
721732
self.custom_metadata.as_ref()
722733
}

src/cargo/ops/cargo_update.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,9 @@ fn status_locking(ws: &Workspace<'_>, num_pkgs: usize) -> CargoResult<()> {
722722
write!(&mut cfg, " Rust {rust_version}")?;
723723
}
724724
write!(&mut cfg, " compatible version{plural}")?;
725+
if let Some(publish_time) = ws.resolve_publish_time() {
726+
write!(&mut cfg, " as of {publish_time}")?;
727+
}
725728
}
726729

727730
ws.gctx()

src/cargo/ops/resolve.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,9 @@ pub fn resolve_with_previous<'gctx>(
448448
}
449449
version_prefs.rust_versions(rust_versions);
450450
}
451+
if let Some(publish_time) = ws.resolve_publish_time() {
452+
version_prefs.publish_time(publish_time);
453+
}
451454

452455
let avoid_patch_ids = if register_patches {
453456
register_patch_entries(registry, ws, previous, &mut version_prefs, keep_previous)?

src/doc/man/cargo-generate-lockfile.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ lockfile and has more options for controlling update behavior.
3232

3333
{{> options-ignore-rust-version }}
3434

35+
{{#option "`--publish-time` _yyyy-mm-ddThh:mm:ssZ_" }}
36+
Latest publish time allowed for registry packages (Unstable)
37+
38+
This is a best-effort filter on allowed packages, including:
39+
- packages from unsupported registries are always accepted
40+
- only the latest yank status is respected
41+
{{/option}}
42+
3543
{{> options-locked }}
3644

3745
{{> options-lockfile-path }}

src/doc/man/generated_txt/cargo-generate-lockfile.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ OPTIONS
4949
--ignore-rust-version
5050
Ignore rust-version specification in packages.
5151

52+
--publish-time yyyy-mm-ddThh:mm:ssZ
53+
Latest publish time allowed for registry packages (Unstable)
54+
55+
This is a best-effort filter on allowed packages, including:
56+
57+
o packages from unsupported registries are always accepted
58+
59+
o only the latest yank status is respected
60+
5261
--locked
5362
Asserts that the exact same dependencies and versions are used as
5463
when the existing Cargo.lock file was originally generated. Cargo

src/doc/src/commands/cargo-generate-lockfile.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ terminal.</li>
6767
</dd>
6868

6969

70+
<dt class="option-term" id="option-cargo-generate-lockfile---publish-time"><a class="option-anchor" href="#option-cargo-generate-lockfile---publish-time"><code>--publish-time</code> <em>yyyy-mm-ddThh:mm:ssZ</em></a></dt>
71+
<dd class="option-desc"><p>Latest publish time allowed for registry packages (Unstable)</p>
72+
<p>This is a best-effort filter on allowed packages, including:</p>
73+
<ul>
74+
<li>packages from unsupported registries are always accepted</li>
75+
<li>only the latest yank status is respected</li>
76+
</ul>
77+
</dd>
78+
79+
7080
<dt class="option-term" id="option-cargo-generate-lockfile---locked"><a class="option-anchor" href="#option-cargo-generate-lockfile---locked"><code>--locked</code></a></dt>
7181
<dd class="option-desc"><p>Asserts that the exact same dependencies and versions are used as when the
7282
existing <code>Cargo.lock</code> file was originally generated. Cargo will exit with an

src/doc/src/reference/unstable.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ Each new feature described below should explain how to use it.
7878
* [sbom](#sbom) --- Generates SBOM pre-cursor files for compiled artifacts
7979
* [update-breaking](#update-breaking) --- Allows upgrading to breaking versions with `update --breaking`
8080
* [feature-unification](#feature-unification) --- Enable new feature unification modes in workspaces
81+
* [publish-time] --- Limit resolver to packages older than the specified time
8182
* Output behavior
8283
* [artifact-dir](#artifact-dir) --- Adds a directory where artifacts are copied to.
8384
* [build-dir-new-layout](#build-dir-new-layout) --- Enables the new build-dir filesystem layout
@@ -1913,6 +1914,13 @@ Specify which packages participate in [feature unification](../reference/feature
19131914
* `package`: Dependency features are considered on a package-by-package basis,
19141915
preferring duplicate builds of dependencies when different sets of features are activated by the packages.
19151916

1917+
## publish-time
1918+
1919+
* Tracking Issue: [#5221](https://github.com/rust-lang/cargo/issues/5221)
1920+
1921+
With `cargo generate-lockfile -Zunstable-options --publish-time <time>`,
1922+
package resolution will not consider any package newer than the specified time.
1923+
19161924
## Package message format
19171925

19181926
* Original Issue: [#11666](https://github.com/rust-lang/cargo/issues/11666)

src/etc/man/cargo-generate-lockfile.1

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,21 @@ Path to the \fBCargo.toml\fR file. By default, Cargo searches for the
6767
Ignore \fBrust\-version\fR specification in packages.
6868
.RE
6969
.sp
70+
\fB\-\-publish\-time\fR \fIyyyy\-mm\-ddThh:mm:ssZ\fR
71+
.RS 4
72+
Latest publish time allowed for registry packages (Unstable)
73+
.sp
74+
This is a best\-effort filter on allowed packages, including:
75+
.sp
76+
.RS 4
77+
\h'-04'\(bu\h'+03'packages from unsupported registries are always accepted
78+
.RE
79+
.sp
80+
.RS 4
81+
\h'-04'\(bu\h'+03'only the latest yank status is respected
82+
.RE
83+
.RE
84+
.sp
7085
\fB\-\-locked\fR
7186
.RS 4
7287
Asserts that the exact same dependencies and versions are used as when the

0 commit comments

Comments
 (0)