Cargo 开发周期:1.76

2024年1月3日 · Ed Page 代表 Cargo 团队

我们想分享过去 6 周发生的事情,以便更好地让社区了解情况并参与其中。对于在 1.76 beta 分支之前合并的工作,它将在 Beta 频道中保留接下来的 6 周,之后将正式发布。

这与 本周 Rust 不同,它更侧重于全局,而不是单个 PR,并从更多来源提取信息,例如 Cargo 团队会议和 Zulip

这是寻找更好方式与社区互动的实验,我们将看看它的效果如何以及我们如何跟上它的步伐。

已合并

管理 Cargo 的增长

Cargo 团队一直在努力扩展我们的流程,以允许工作区中的包数量增长

在过去的一年中,我们反思了一些最近的回归问题,并发生了一些影响人们的破坏。例如:

  • 在对 cargo metadata 的输出进行未来不兼容的更新时,没有与第三方 cargo_metadata API 协调 (oli-obk/cargo_metadata#240)
  • cargo-credential 的依赖关系造成混淆,导致从 crates.io 构建这些软件包时出现无法工作的依赖树 (rust-lang/cargo#13004)

一些潜在的改进包括:

作为扩展我们扩展能力的第一步,我们将 Cargo.toml serde 类型 从 Cargo 本身中分离出来 (rust-lang/cargo#12801)。

其他可能从 cargo 库中分离出来的领域包括:

  • 将 serde 和 CLI 类型移动到 cargo-util-schemas
  • 控制台输出
  • .cargo/config.toml 进行解析和图层合并
  • 从不同的软件包来源读取(git、path、git registry、sparse registry)
长路径支持

一位用户在使用 cargo install --git 时在 Windows 上遇到了路径长度问题 (rust-lang/cargo#13020),这导致 ChrisDenton 发布了一个 PR,用于 将 Windows 清单嵌入到 Cargo 二进制文件中,仿照 rustc。在该 PR 上进行了一些探索之后,它与 rust-lang/cargo#13141 合并,以跟踪一些剩余的工作(与 rust-lang/cargo#9770 并行)。

在与 git 交互时,rust-lang/cargo#13020 中有一些关于一些额外配置设置的说明,以解决问题。

稳定 cargo metadataid 字段

目前,cargo metadata 的软件包 id 字段被定义为 不透明的。问题在于,您无法从输出中获取一个软件包并将其传递给 cargo <cmd> --package <value>。您可以使用 name 字段,但在 Cargo.lock 中有多个不兼容版本时,该字段可能会产生歧义。name软件包 ID 规范 格式的子集,大多数 --package 参数都接受该格式。rust-lang/cargo#12914 建议我们将 id 切换为软件包 ID 规范,并在 cargo metadata 的输出中声明它为非不透明的,从而允许调用者获取 id 并通过 --package 参数将其传递给 Cargo。

我们确实发现了一个障碍:软件包 ID 规范有时会产生歧义。这在 rust-lang/cargo#12933 中得到了解决。

这正在等待 Cargo 团队的意见。

-Ztrim-paths

-Ztrim-paths 是一项不稳定的功能,它提供了不同的选项来清理嵌入在最终二进制文件中的路径。这提高了在不牺牲调试体验的情况下,交付和共享工件时的隐私和可重复性。

-Ztrim-paths 通常可用,weihanglo 一直在推动此功能的稳定。最近,他们修复了清理当前工作目录中的软件包时的问题 (rust-lang/cargo#13114),并添加了端到端测试,以确保调试体验不会退步 (rust-lang/cargo#13091rust-lang/cargo#13118)。

仍有一些符号尚未清理,例如 macOS 上的 N_SON_OSO 符号,或者启用 split-debuginfo 时 Linux 上的 DW_AT_comp_dir。这在 rust-lang/rust#117652 中进行了跟踪。

在清理路径时,我们将路径的开头重新映射到一个标识符。当前的重新映射规则使得配置调试器 重新映射到其系统上的源 非常困难。替代的重新映射规则正在 rust-lang/cargo#13171 中讨论。正在提出的一个重要考虑因素是,无论字节序或位宽如何,用户都可以在其调试器中成功地重新映射,这对于跨平台调试非常重要。

-Zcheck-cfg

-Zcheck-cfg 是一项不稳定的功能,它将导致 rustc 在未定义的条件编译时发出警告,例如 #[cfg(unknown)]#[cfg(feature = "unknown")]

Urgau 一直在 rustccargo 中工作,以改进此功能以实现稳定。最近,他们:

Urgau 希望在 1.77 开发周期期间开始稳定化讨论。

RFC #3516(公共/私有依赖项)

RFC #3516(公共/私有依赖项) 已合并,这将帮助用户识别何时在其公共 API 中泄露了依赖项,从而有助于防止意外的重大更改。这在 cargo-features = ["public-dependency"] 之后。大部分实现是作为被取代的 RFC #1977 的一部分完成的。

linyihai 已介入以帮助实现剩余的 Cargo 工作,包括:

其他 Cargo 工作包括:

希望此功能能为 2024 版本做好准备。 跟踪问题 列举了剩余的工作。最大的风险可能是:

用户控制的 Cargo 诊断

Cargo 团队在向 Cargo 添加警告时非常谨慎,因为没有像 rustc--allow ... / #[allow(...)] 这样的东西可以在需要时抑制它们。这种情况随着 [lints] 的引入而改变。我们正在 rust-lang/cargo#12235 中跟踪 Cargo 警告控制(以及它可以解锁的 lint)。

第一个里程碑是使 TOML 解析错误与 rustc 的错误样式匹配,从:

error: failed to parse manifest at `[..]`

Caused by:
  TOML parse error at line 6, column 25
    |
  6 | build = 3
    | ^
  invalid type: integer `3`, expected a boolean or string

到:

error: invalid type: integer `3`, expected a boolean or string
--> Cargo.toml:6:25
  |
6 | build = 3
  | ^

与其编写我们自己的模仿 rustc 的错误消息渲染器,Muscraft 复活了 annotate-snippets 项目,目的是使其适用于 cargo,然后将 rustc 迁移到它。他们发布了 annotate-snippets v0.10,并创建了 rust-lang/cargo#13172,以便在解析 Cargo.toml 文件时将其集成到 Cargo 中。

我们还需要决定如何处理 rustccargo 之间的颜色差异Muscraft 一直在研究为什么选择 rustc 的颜色,并正在准备一个关于这两个程序应该使用的颜色方案的提案。

cargo info

我们已经收到关于cargo info命令的需求近十年了。 hi-rustin,一位 Cargo 的常客贡献者,已经开始着手设计这样一个命令。

您可以通过运行以下命令来尝试它:

$ cargo install cargo-information
$ cargo info clap

欢迎提出想法和反馈!请参阅Issue tracker

推迟 RFC

Cargo 团队正在清理积压的开放 RFC。

RFC #3379 (os_version 用于 #[cfg] 的谓词) 我将参考 RFC 中的摘要

我将提议推迟这个 RFC。我认为我们都同意这是一个很棒的功能,但我认为存在一些重要问题,特别是关于预构建 std 的版本支持如何工作,它如何与支持目标需求相关联,以及如何确定版本信息等。最重要的是,目前团队中没有人有能力来主导这个功能。

RFC #3177 (使用 unidiff 补丁文件的 [patch] 依赖项) 我们认为这会很有用,但有很多细节需要解决,而且团队中没有人能够帮助推进这项工作。从其他团队和cargo script eRFC中吸取教训,我们认为推进这项工作的最佳方式是让某人草拟一个粗略的提案,然后将其作为不稳定的功能进行实验。这个实验将侧重于学习我们需要弄清楚的内容,以便确定哪些应该包含在 RFC 中。

RFC #3287 (原生代码覆盖率支持) 我们非常希望改善围绕代码覆盖率的开发体验,但不清楚该 RFC 是否是正确的方法(例如,rust-lang/cargo#13040包含另一种替代方案)。与 RFC #3177 一样,我们需要进行实验来充实这项设计。

RFC #3263 (不要将预发布版本视为彼此兼容) 我们完全认识到这是一个问题。例如,Clap 在 3.0 预发布版本中遇到了这个问题,这也是 Clap 停止使用预发布版本的原因。然而,在过去的一年中,RFC 中仍然存在许多未解决的问题,而且 Cargo 团队中没有人有时间来推动这些讨论。一个可行的短期解决方案是使用提议的cargo linting 系统来警告用户,他们在 Cargo.toml 文件中没有使用 = 来固定其预发布版本要求。作为短期测试预发布版本的替代方案,用户可以在依赖项的 git 仓库中进行 [patch] 操作。为了更广泛地使用不成熟的 API 或行为,Clap 通过以 unstable- 为前缀的功能惯例来公开它们,这些功能被记录为没有语义版本保证。rust-lang/cargo#10881提出了对不稳定功能的原生支持。我们认识到,当一个库正在经历更广泛的破坏性更改时,这并无帮助,并且预发布版本仍然有其用武之地。

行动项: 我们需要回顾并记录实验过程,以便我们可以更容易地向人们指出运行实验的期望。

设计讨论

元数据:2024 版本

随着2024 版本的征集窗口即将关闭,我们探讨了是否应该尝试加入其他内容。

目前,我们计划了

以及可能将RFC #3537 (MSRV 感知解析器)与版本相关联。

我们集思广益了其他想法,包括

禁用默认功能 我们认为这是使功能更容易演变的大图景的一部分,特别是将内置功能置于功能之后,而不会产生破坏性更改。这可能不需要一个独立的版本,但我们也讨论了如果我们想要删除 default-features = false,这将需要一个版本。但是,我们不确定这是否是我们想要的,我们不希望匆忙进行设计,并且我们应该在切换之前有一个较长的弃用窗口。

交叉编译 Doctests 目前,当使用 --target 时,我们会跳过 doctests,而此功能将其更改为我们开始运行它们。切换到此行为可能会破坏人们的代码。在rust-lang/rust上测试 -Zdoctest-xcompile 时,使用 --target=armhf-gnu 没有看到任何错误,而 --target=arm-android 在 std 中有一些错误。从用户惊喜的角度来看,我们觉得人们会更惊讶地发现我们正在悄悄地跳过 doctests,而不是惊讶地看到编译错误。也许在版本更改时运行(并失败)doctests 会被视为一个错误修复。如果我们继续推进此操作,那么该决定可能不仅仅由我们做出,因为我们也需要在其他工具中稳定标志。关于此的讨论正在zulip上进行。

使 profile.*.debug=0 暗示 profile.*.strip = "debuginfo" 当用户使用debug=0禁用调试信息时,他们仍然会拥有来自 std 的调试符号,因为它是预构建的。通过在 debug=0 时隐式设置strip = "debuginfo",我们将更接近用户实际请求的内容。根据zulip 线程,这会加快 Linux 上的构建速度并缩小二进制文件。当设置 debug=0 时的缺点是在 Mac 上构建速度略慢,并且回溯信息将不太丰富。为了使回溯更具视角,这将使通过 std 的回溯与用户代码保持一致,而用户代码很可能是一个应用程序的大部分。我们认为这可以按原样推进,无需版本,并要求提供更正式的提案,该提案可以在该 issue中找到。

profile.*.split-debuginfo 设置为默认值 我们之前更改了 Mac 的默认值 (rust-lang/cargo#9298) 以提高编译时间和减小 target-dir 大小。如果我们进行此更改,我们意识到它不能与版本相关联,因为[profile]是一个工作区级别的字段,而 Cargo 仅具有包的版本概念,而不具有工作区的版本概念。有关进一步讨论,请参阅zulip 线程

[lints] 再探

1.74 引入了Cargo.toml 中的 [lints]

随着越来越多的人尝试此功能,主要关注点是

对于最后一点,我们讨论了所有字段的隐式工作区继承。我们关注的主要问题是这可能太神奇了,使人们更难以推断 cargo 正在做什么。一个先例是[profile]。虽然 Cargo 隐式地将 Cargo.toml 覆盖在 .cargo/config.toml 之上,但这涉及配置,而配置已经有点偏离正轨,可能不是最佳的遵循模型。但是,它也支持配置文件明确表示它们从另一个配置文件继承。我们可以让清单范围内的选择加入以继承未显式设置的字段。根据反馈,我们可以探索更改版本中此设置的默认值。一个尴尬的情况是依赖项。我们不应自动添加所有依赖项。我们可能也不应允许没有来源的依赖项(即,允许跳过 workspace = true)。但是不让依赖项参与将会不一致。

[profile] 的先例的讨论也引发了对可以从中继承的多组值的讨论。我们讨论了几个想法,包括

  • 具有命名字段集的全部或全部继承 (inherits = "public-members")
  • 具有命名字段的每个字段继承 (rust-version.workspace = "public-members")
  • 命名您可以从中继承的其他包,无论是全部还是每个字段
cargo script

用于嵌入清单的 RFC被解决之前,cargo script RFC和实现的进度已停滞不前。

当前的提案是

#!/usr/bin/env cargo
```cargo
[dependencies]
clap = { version = "4.2", features = ["derive"] }
```

use clap::Parser;

#[derive(Parser, Debug)]
#[clap(version)]
struct Args {
    #[clap(short, long, help = "Path to config")]
    config: Option<std::path::PathBuf>,
}

fn main() {
    let args = Args::parse();
    println!("{:?}", args);
}

清单嵌入在我们称为代码围栏 frontmatter 的内容中,该内容以 markdown 为模型。cargo 标识符称为 infostring。

从长远来看,我们可以采用 infostring 的两个方向

  • 父工具(在本例中为 cargo)是否拥有 infostring 的定义,并且允许它使用任何它想要的标识符
  • rustc 是否拥有 infostring 的含义,从而允许 Rust 项目添加其他类型的元数据,而无需担心破坏依赖于自定义标识符的工具

嵌入式清单语法 RFC 使用新部分进行了更新,通过建议我们现在硬编码对 cargo 的支持,并将决定留给未来,当我们有更多关于如何使用它的上下文时,从而回避了此讨论。

我们还认识到,当用户尝试将这些内容放入 markdown 代码围栏中时,使用三个反引号可能会给用户带来麻烦,因为用户可能不知道如何转义它或忘记转义它,从而导致沮丧。

关于zulip上正在进行关于嵌入式工具元数据的语法讨论。

SBOM

供应链安全最近受到了很多关注。其中一个要素是能够追溯到一个二进制文件中都包含了什么以及它是如何构建的。这被称为软件物料清单(Software Bill of Materials,简称SBOM)。

之前,arlosi 创建了一个关于这个主题的Pre-RFC。这个 Pre-RFC 一直在引发讨论,包括:

  • 列举第三方解决方案的局限性(链接
  • 关于 Cargo 的 SBOM 格式的作用,它应该是一个中间格式,为集成者提供创建最终 SBOM 所需的信息,还是应该足够独立,可以直接转换为所选的格式。这会影响诸如以下字段:
    • 作者(可以从清单中提取)
    • 哈希值(涉及使用哪种算法的哪些工件的问题)
    • 时间戳(不可重现,而这是某些 SBOM 用例的要求)

arlosi 计划将反馈意见纳入 Pre-RFC 中,进行最后一次反馈征集,然后转到完整的 RFC。

RFC #3537:在选择依赖项时,使 Cargo 遵守最低支持的 Rust 版本 (MSRV)

Cargo 和 crates.io 生态系统的一个令人沮丧的点是,当添加一个依赖项时,构建会失败,因为它使用了你的 Rust 版本不支持的新特性。我们一直在 rust-lang/cargo#9930 中跟踪这个问题。历史上,我们推迟了这方面的工作,因为我们预计当没有兼容的包时出现的错误会比解决的问题引起更多的困惑和用户挫败感。我们曾希望 PubGrub 依赖项解析器能够解决这个问题,但在我们切换依赖项解析器之前还有很多工作要做。

在 1.74 开发周期中,我们合并了 rust-lang/cargo#12560,它添加了一个永久不稳定的实现,以便人们至少可以使用 nightly 进行一次性的依赖项解析。在 1.76 开发周期之前,我们想出了一种通过仅优先选择稳定版本来绕过解析器错误消息的方法,并在 rust-lang/cargo#12950 中合并了该方法。

这个绕过方法被写成 Pre-RFC:MSRV 感知解析器,然后引出了 RFC #3537:在选择依赖项时,使 Cargo 遵守最低支持的 Rust 版本 (MSRV)

主要的讨论之一是关于我们是否应该默认遵守 MSRV,还是需要选择加入。在 cargo 团队会议和办公时间进行了一些讨论后,未来的计划是将文档的重点重新放在工作流程、我们想要推动的行为(例如,避免停滞)以及不同的可能解决方案如何影响工作流程和用户行为上。

我们正在等待 RFC 作者将这种新方法整合到 RFC 中。

RFC #3371:CARGO_TARGET_BASE_DIR 支持

RFC #3371 允许用户将他们所有的 target-dirs 移动到一个公共根目录下,而无需用户进行大量的簿记工作。它在 6 月份被提议合并,但它脱离了我们的视线,我们终于能够讨论它。我们澄清说,这个提议独立于每个用户的缓存,并且这两个努力基本上是独立的,都是值得的。每个用户的缓存会减少我们放在 target-dir 中的内容,但工作区仍然需要一个 target-dir 用于不可缓存的构建和最终工件。

虽然还有其他解决方案可以满足 CARGO_TARGET_BASE_DIR 的动机,但我们认为 CARGO_TARGET_BASE_DIR 是一个有原则的快捷方式,可以更快地为我们带来大部分好处。

RFC 中提出的一个担忧是,人们如何找到他们的 target-dir(例如,打包他们构建的 [[bin]])。如果我们考虑将工作区的 target-dir 切换到一个中心位置,这一点就变得更加重要。在 RFC 中,提出了将 target/ 符号链接到 target-dir 的想法。目前尚不清楚对那些需要它的人的好处是否大于对那些不需要它的人的困扰。用户可以通过 cargo metadata 获取 target-dir 的位置。稳定 --out-dir 将绕过大多数访问 target-dir 的用途。这些可能就足够了。如果不够,也许我们可以探索一个配置字段来控制符号链接的创建。

然后,我们借鉴了索引的 dl 字段,探索了一些设计空间。例如,为 {home}{cargo_home} 设置占位符将使在帐户之间复制配置更容易。如果我们扩展 CARGO_TARGET_DIR 的占位符支持,而不是添加 CARGO_TARGET_BASE_DIR,允许 CARGO_TARGET_DIR={cargo_home}/target/{manifest_path_hash} 会怎样?这似乎会大大简化设计。

现在,这又回到了 RFC 作者的案头上,以处理这些反馈并根据他们的想法更新提案。

RFC #3493:cargo update --precise <prerelease>

要今天使用预发布版本,用户必须通过他们的版本要求选择加入。RFC #3493 更改了 cargo 的依赖项解析器,以便用户可以在他们的 Cargo.lock 中选择加入预发布版本。如果用户想测试预发布依赖项中的回归,或者想提前访问功能但不需要它(例如,性能改进),这很有效。如果一个包需要预发布版本中的某些东西,比如一个新的 API,那么应该在版本要求中指定,这更像是 RFC #3263 的重点。

由于我们已经在讨论推迟 #3263,我们讨论了是否也应该推迟 #3493。虽然我们想改进预发布版本,但团队中没有人可以帮助管理这项工作,而且该提案将涉及对 cargo 的侵入性更改,这可能会很脆弱。对于我们花多少时间来处理预发布版本,目前尚不清楚这是否是最重要的。

在讨论过程中,我们意识到有一个坚实的基础可以借鉴设计,即撤回的包。我们还可以通过建议 semver 保留现有的版本匹配逻辑,并在不同的函数名下公开新行为来最大限度地降低风险。我们还意识到,这个 RFC 是 RFC #3263 的先决条件,这样 cargo 才能正确地统一预发布版本和常规发布版本的需求。

为了记录,有人担心这需要修改 Cargo.lock。如果是这样,那么它可能会非常复杂,以至于我们需要先进行实验性实现。然而,经过进一步讨论,我们发现不太可能需要修改 Cargo.lock

我们更新了 RFC,现在正在等待作者完成讨论。

cargo update --precise <yanked>

RFC #3493 允许用户通过 --precise 强制使用预发布版本,以及 rust-lang/cargo#12425 对破坏性更改做同样的事情之间,将这个概念扩展到撤回的包似乎是合适的,从而解决 rust-lang/cargo#4225。我们认为在这种情况下需要信任用户。用户可能有充分的理由访问撤回的包,无论是短期测试某些东西,还是长期使用并接受风险。我们考虑为此添加一个额外的标志,但是预发布版本和破坏性更改不需要标志。对于破坏性更改,该标志将用于 --precise 之外,以选择所有包。我们有可能将这个概念扩展到预发布版本和撤回的包。为了谨慎起见,我们联系了之前的 cargo 团队成员,以防我们遗漏了什么背景,他们给予了批准。

rust-lang/cargo#4225 被标记为已接受,我们欢迎大家为此贡献一个 PR。

其他

其他相关的主题

没有进展的重点领域

这些是 Cargo 团队成员感兴趣的领域,在本开发周期中没有可报告的进展。

准备开发

需要设计和/或实验

计划

如何提供帮助

如果您有改进 cargo 的想法,我们建议首先查看我们的积压,然后在 Internals 上探讨这个想法。

如果有一个您希望解决的特定问题,但这里没有讨论,您可以采取一些步骤来帮助推动它:

  • 总结现有的对话(示例:更好地支持 Docker 层缓存Cargo.lock 策略的更改MSRV 感知解析器
  • 记录其他生态系统中的现有技术,以便我们可以借鉴他人所做的工作,并在合理的情况下创建用户熟悉的东西
  • 记录 Cargo 中相关的问题和解决方案,以便我们了解是否在正确的抽象层面上进行解决。
  • 在这些帖子的基础上,提出一个考虑到上述信息和 Cargo 兼容性要求的解决方案(示例)。

我们可以在 zulip 上为 S-accepted 标签的问题提供指导,您可以在 贡献者开放办公时间期间与我们实时交流。如果您想帮助解决这里提到的大型项目之一,并且是刚开始,修复一些问题将帮助您熟悉流程和期望,使事情进展更顺利。如果您想在没有导师的情况下解决问题,对您独立完成工作的期望会更高。