Compare commits

...

18 Commits

Author SHA1 Message Date
Weihang Lo 599094c9a1
Merge 1d6c52e88c into eee4ea2f5a 2024-04-27 03:29:03 +02:00
bors eee4ea2f5a Auto merge of #13812 - Muscraft:dont-always-inherit-workspace-lints, r=epage
fix(cargo-lints): Don't always inherit workspace lints

When working on changes for #13805, I noticed that we always passed the contents of `[workspace.lints.cargo]` into the currently implemented lints,  even if `[lints]` was not specified or did not contain `workspace = true`. This PR makes it so we only pass in the workspace cargo lints if `[lints]` contains `workspace = true`.

You can verify this change by looking at the first commit, where I added a test showing the current behavior, and looking at the second commit and seeing the test output no longer shows a warning about specifying `im-a-teapot`.
2024-04-27 00:56:12 +00:00
bors c4e19cc890 Auto merge of #13811 - ehuss:remove-sleep-test, r=weihanglo
Update SleepTraker returns_in_order unit test

This updates the `returns_in_order` SleepTracker unit test so that it is not so sensitive to how fast the system is running. Previously it assumed that the function calls would take less than a millisecond to finish, but that is not a valid assumption for a slow-running system.

I have changed it to simplify the test, with the assumption that it takes less than 30 seconds for it to run, which should have a safety margin of a few orders of magnitude.
2024-04-26 23:30:40 +00:00
Eric Huss 06fb65e753 Update SleepTraker returns_in_order unit test 2024-04-26 16:02:09 -07:00
Scott Schafer cf197fc499
fix(cargo-lints): Don't always inherit workspace lints 2024-04-26 16:37:41 -06:00
Scott Schafer c3b104e11e
test(cargo-lints): Show workspace lints always inherited 2024-04-26 16:26:36 -06:00
Weihang Lo 1d6c52e88c
test(patch-files): verify pkgid sourceid representation 2024-04-25 14:37:12 -04:00
Weihang Lo 769dabf4e3
test(patch-files): verify patch works 2024-04-25 14:37:12 -04:00
Weihang Lo aa9d4a3f65
test(patch-files): verify invalid manifest 2024-04-25 14:37:12 -04:00
Weihang Lo 167f0e2378
test(patch-files): verify non-blocking gates and warnings 2024-04-25 14:37:12 -04:00
Weihang Lo 161f9a07b6
feat: parse `dep.patches` to construct patched source 2024-04-25 14:37:09 -04:00
Weihang Lo b45ab89f7e
feat: new source `PatchedSource` 2024-04-25 14:36:09 -04:00
Weihang Lo d9cdf2e538
feat: new table `[patchtool]` in config.toml 2024-04-25 14:36:09 -04:00
Weihang Lo 2e1e117a33
feat: dont canonicalize `patched+` to https for github 2024-04-25 14:36:09 -04:00
Weihang Lo 0bb3ad1352
feat: support `SourceKind::Patched` in SourceId
One thing left out here is a consistent/stable hash for patches,
The are absolute local paths that might introduces unnecessary
changes in lockfile. To mitigate this, patches under the current
workspace root will be relative.
2024-04-25 14:36:09 -04:00
Weihang Lo 79bd19cbbc
feat(schemas): `SourceKind::Patched` (unstable)
`SourceKind::Patched` represents a source patched by local patch files.
2024-04-25 14:36:08 -04:00
Weihang Lo 30f9de2863
feat(schemas): `dep.patches` field (unstable) 2024-04-25 14:36:08 -04:00
Weihang Lo 861ff74fa7
feat(unstable): new unstable feature `patch-files`
Since `[patch]` section exists also in config,
so have it inboth cargo-features and -Z flag.
2024-04-25 14:36:08 -04:00
22 changed files with 1901 additions and 78 deletions

2
Cargo.lock generated
View File

@ -466,7 +466,7 @@ dependencies = [
[[package]]
name = "cargo-util-schemas"
version = "0.3.1"
version = "0.4.0"
dependencies = [
"semver",
"serde",

View File

@ -33,7 +33,7 @@ cargo-platform = { path = "crates/cargo-platform", version = "0.1.5" }
cargo-test-macro = { version = "0.2.0", path = "crates/cargo-test-macro" }
cargo-test-support = { version = "0.2.0", path = "crates/cargo-test-support" }
cargo-util = { version = "0.2.9", path = "crates/cargo-util" }
cargo-util-schemas = { version = "0.3.0", path = "crates/cargo-util-schemas" }
cargo-util-schemas = { version = "0.4.0", path = "crates/cargo-util-schemas" }
cargo_metadata = "0.18.1"
clap = "4.5.4"
color-print = "0.3.5"

View File

@ -213,6 +213,7 @@ fn substitute_macros(input: &str) -> String {
("[DOCTEST]", " Doc-tests"),
("[PACKAGING]", " Packaging"),
("[PACKAGED]", " Packaged"),
("[PATCHING]", " Patching"),
("[DOWNLOADING]", " Downloading"),
("[DOWNLOADED]", " Downloaded"),
("[UPLOADING]", " Uploading"),

View File

@ -1,6 +1,6 @@
[package]
name = "cargo-util-schemas"
version = "0.3.1"
version = "0.4.0"
rust-version = "1.77" # MSRV:1
edition.workspace = true
license.workspace = true

View File

@ -7,4 +7,6 @@ pub use package_id_spec::PackageIdSpecError;
pub use partial_version::PartialVersion;
pub use partial_version::PartialVersionError;
pub use source_kind::GitReference;
pub use source_kind::PatchInfo;
pub use source_kind::PatchInfoError;
pub use source_kind::SourceKind;

View File

@ -7,6 +7,7 @@ use url::Url;
use crate::core::GitReference;
use crate::core::PartialVersion;
use crate::core::PartialVersionError;
use crate::core::PatchInfo;
use crate::core::SourceKind;
use crate::manifest::PackageName;
use crate::restricted_names::NameValidationError;
@ -145,6 +146,14 @@ impl PackageIdSpec {
kind = Some(SourceKind::Path);
url = strip_url_protocol(&url);
}
"patched" => {
let patch_info =
PatchInfo::from_query(url.query_pairs()).map_err(ErrorKind::PatchInfo)?;
url.set_query(None);
kind = Some(SourceKind::Patched(patch_info));
// We don't strip protocol and leave `patch` as part of URL
// in order to distinguish them.
}
kind => return Err(ErrorKind::UnsupportedProtocol(kind.into()).into()),
}
} else {
@ -232,10 +241,16 @@ impl fmt::Display for PackageIdSpec {
write!(f, "{protocol}+")?;
}
write!(f, "{}", url)?;
if let Some(SourceKind::Git(git_ref)) = self.kind.as_ref() {
if let Some(pretty) = git_ref.pretty_ref(true) {
write!(f, "?{}", pretty)?;
match self.kind.as_ref() {
Some(SourceKind::Git(git_ref)) => {
if let Some(pretty) = git_ref.pretty_ref(true) {
write!(f, "?{pretty}")?;
}
}
Some(SourceKind::Patched(patch_info)) => {
write!(f, "?{}", patch_info.as_query())?;
}
_ => {}
}
if url.path_segments().unwrap().next_back().unwrap() != &*self.name {
printed_name = true;
@ -314,13 +329,16 @@ enum ErrorKind {
#[error(transparent)]
PartialVersion(#[from] crate::core::PartialVersionError),
#[error(transparent)]
PatchInfo(#[from] crate::core::PatchInfoError),
}
#[cfg(test)]
mod tests {
use super::ErrorKind;
use super::PackageIdSpec;
use crate::core::{GitReference, SourceKind};
use crate::core::{GitReference, PatchInfo, SourceKind};
use url::Url;
#[test]
@ -599,6 +617,18 @@ mod tests {
},
"path+file:///path/to/my/project/foo#1.1.8",
);
// Unstable
ok(
"patched+https://crates.io/foo?name=bar&version=1.2.0&patch=%2Fto%2Fa.patch&patch=%2Fb.patch#bar@1.2.0",
PackageIdSpec {
name: String::from("bar"),
version: Some("1.2.0".parse().unwrap()),
url: Some(Url::parse("patched+https://crates.io/foo").unwrap()),
kind: Some(SourceKind::Patched(PatchInfo::new("bar".into(), "1.2.0".into(), vec!["/to/a.patch".into(), "/b.patch".into()]))),
},
"patched+https://crates.io/foo?name=bar&version=1.2.0&patch=%2Fto%2Fa.patch&patch=%2Fb.patch#bar@1.2.0",
);
}
#[test]
@ -651,5 +681,17 @@ mod tests {
err!("@1.2.3", ErrorKind::NameValidation(_));
err!("registry+https://github.com", ErrorKind::NameValidation(_));
err!("https://crates.io/1foo#1.2.3", ErrorKind::NameValidation(_));
err!(
"patched+https://crates.io/foo?version=1.2.0&patch=%2Fb.patch#bar@1.2.0",
ErrorKind::PatchInfo(_)
);
err!(
"patched+https://crates.io/foo?name=bar&patch=%2Fb.patch#bar@1.2.0",
ErrorKind::PatchInfo(_)
);
err!(
"patched+https://crates.io/foo?name=bar&version=1.2.0&#bar@1.2.0",
ErrorKind::PatchInfo(_)
);
}
}

View File

@ -1,4 +1,5 @@
use std::cmp::Ordering;
use std::path::PathBuf;
/// The possible kinds of code source.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -15,6 +16,8 @@ pub enum SourceKind {
LocalRegistry,
/// A directory-based registry.
Directory,
/// A source with paths to patch files (unstable).
Patched(PatchInfo),
}
impl SourceKind {
@ -27,6 +30,8 @@ impl SourceKind {
SourceKind::SparseRegistry => None,
SourceKind::LocalRegistry => Some("local-registry"),
SourceKind::Directory => Some("directory"),
// Patched source URL already includes the `patched+` prefix, see `SourceId::new`
SourceKind::Patched(_) => None,
}
}
}
@ -107,6 +112,10 @@ impl Ord for SourceKind {
(SourceKind::Directory, _) => Ordering::Less,
(_, SourceKind::Directory) => Ordering::Greater,
(SourceKind::Patched(a), SourceKind::Patched(b)) => a.cmp(b),
(SourceKind::Patched(_), _) => Ordering::Less,
(_, SourceKind::Patched(_)) => Ordering::Greater,
(SourceKind::Git(a), SourceKind::Git(b)) => a.cmp(b),
}
}
@ -199,3 +208,101 @@ impl<'a> std::fmt::Display for PrettyRef<'a> {
Ok(())
}
}
/// Information to find the source package and patch files.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PatchInfo {
/// Name of the package to be patched.
name: String,
/// Verision of the package to be patched.
version: String,
/// Absolute paths to patch files.
///
/// These are absolute to ensure Cargo can locate them in the patching phase.
patches: Vec<PathBuf>,
}
impl PatchInfo {
pub fn new(name: String, version: String, patches: Vec<PathBuf>) -> PatchInfo {
PatchInfo {
name,
version,
patches,
}
}
/// Collects patch information from query string.
///
/// * `name` --- Package name
/// * `version` --- Package exact version
/// * `patch` --- Paths to patch files. Mutiple occurrences allowed.
pub fn from_query(
query_pairs: impl Iterator<Item = (impl AsRef<str>, impl AsRef<str>)>,
) -> Result<PatchInfo, PatchInfoError> {
let mut name = None;
let mut version = None;
let mut patches = Vec::new();
for (k, v) in query_pairs {
let v = v.as_ref();
match k.as_ref() {
"name" => name = Some(v.to_owned()),
"version" => version = Some(v.to_owned()),
"patch" => patches.push(PathBuf::from(v)),
_ => {}
}
}
let name = name.ok_or_else(|| PatchInfoError("name"))?;
let version = version.ok_or_else(|| PatchInfoError("version"))?;
if patches.is_empty() {
return Err(PatchInfoError("path"));
}
Ok(PatchInfo::new(name, version, patches))
}
/// As a URL query string.
pub fn as_query(&self) -> PatchInfoQuery<'_> {
PatchInfoQuery(self)
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn version(&self) -> &str {
self.version.as_str()
}
pub fn patches(&self) -> &[PathBuf] {
self.patches.as_slice()
}
}
/// A [`PatchInfo`] that can be `Display`ed as URL query string.
pub struct PatchInfoQuery<'a>(&'a PatchInfo);
impl<'a> std::fmt::Display for PatchInfoQuery<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "name=")?;
for value in url::form_urlencoded::byte_serialize(self.0.name.as_bytes()) {
write!(f, "{value}")?;
}
write!(f, "&version=")?;
for value in url::form_urlencoded::byte_serialize(self.0.version.as_bytes()) {
write!(f, "{value}")?;
}
for path in &self.0.patches {
write!(f, "&patch=")?;
let path = path.to_str().expect("utf8 patch").replace("\\", "/");
for value in url::form_urlencoded::byte_serialize(path.as_bytes()) {
write!(f, "{value}")?;
}
}
Ok(())
}
}
/// Error parsing patch info from URL query string.
#[derive(Debug, thiserror::Error)]
#[error("missing query string `{0}`")]
pub struct PatchInfoError(&'static str);

View File

@ -732,6 +732,12 @@ pub struct TomlDetailedDependency<P: Clone = String> {
#[serde(rename = "default_features")]
pub default_features2: Option<bool>,
pub package: Option<PackageName>,
/// `patches = [<path>, ...]` for specifying patch files (unstable).
///
/// Paths of patches are relative to the file it appears in.
/// If that's a `Cargo.toml`, they'll be relative to that TOML file,
/// and if it's a `.cargo/config.toml` file, they'll berelative to that file.
pub patches: Option<Vec<P>>,
pub public: Option<bool>,
/// One or more of `bin`, `cdylib`, `staticlib`, `bin:<name>`.
@ -770,6 +776,7 @@ impl<P: Clone> Default for TomlDetailedDependency<P> {
default_features: Default::default(),
default_features2: Default::default(),
package: Default::default(),
patches: Default::default(),
public: Default::default(),
artifact: Default::default(),
lib: Default::default(),

View File

@ -509,6 +509,9 @@ features! {
/// Allow multiple packages to participate in the same API namespace
(unstable, open_namespaces, "", "reference/unstable.html#open-namespaces"),
/// Allow patching dependencies with patch files.
(unstable, patch_files, "", "reference/unstable.html#patch-files"),
}
/// Status and metadata for a single unstable feature.
@ -770,6 +773,7 @@ unstable_cli_options!(
next_lockfile_bump: bool,
no_index_update: bool = ("Do not update the registry index even if the cache is outdated"),
panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"),
patch_files: bool = ("Allow patching dependencies with patch files"),
profile_rustflags: bool = ("Enable the `rustflags` option in profiles in .cargo/config.toml file"),
public_dependency: bool = ("Respect a dependency's `public` field in Cargo.toml to control public/private dependencies"),
publish_timeout: bool = ("Enable the `publish.timeout` key in .cargo/config.toml file"),
@ -1151,6 +1155,7 @@ impl CliUnstable {
"mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?,
"no-index-update" => self.no_index_update = parse_empty(k, v)?,
"panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?,
"patch-files" => self.patch_files = parse_empty(k, v)?,
"public-dependency" => self.public_dependency = parse_empty(k, v)?,
"profile-rustflags" => self.profile_rustflags = parse_empty(k, v)?,
"trim-paths" => self.trim_paths = parse_empty(k, v)?,

View File

@ -1,6 +1,7 @@
use crate::core::GitReference;
use crate::core::PackageId;
use crate::core::SourceKind;
use crate::sources::patched::PatchedSource;
use crate::sources::registry::CRATES_IO_HTTP_INDEX;
use crate::sources::source::Source;
use crate::sources::{DirectorySource, CRATES_IO_DOMAIN, CRATES_IO_INDEX, CRATES_IO_REGISTRY};
@ -8,6 +9,7 @@ use crate::sources::{GitSource, PathSource, RegistrySource};
use crate::util::interning::InternedString;
use crate::util::{context, CanonicalUrl, CargoResult, GlobalContext, IntoUrl};
use anyhow::Context as _;
use cargo_util_schemas::core::PatchInfo;
use serde::de;
use serde::ser;
use std::cmp::{self, Ordering};
@ -176,6 +178,14 @@ impl SourceId {
let url = url.into_url()?;
SourceId::new(SourceKind::Path, url, None)
}
"patched" => {
let mut url = url.into_url()?;
let patch_info = PatchInfo::from_query(url.query_pairs())
.with_context(|| format!("parse `{url}`"))?;
url.set_fragment(None);
url.set_query(None);
SourceId::for_patches(SourceId::from_url(url.as_str())?, patch_info)
}
kind => Err(anyhow::format_err!("unsupported source protocol: {}", kind)),
}
}
@ -245,6 +255,16 @@ impl SourceId {
SourceId::new(SourceKind::Directory, url, None)
}
pub fn for_patches(orig_source_id: SourceId, patch_info: PatchInfo) -> CargoResult<SourceId> {
let url = orig_source_id.as_encoded_url();
// `Url::set_scheme` disallow conversions between non-special and speicial schemes,
// so parse the url from string again.
let url = format!("patched+{url}")
.parse()
.with_context(|| format!("cannot set patched scheme on `{url}`"))?;
SourceId::new(SourceKind::Patched(patch_info), url, None)
}
/// Returns the `SourceId` corresponding to the main repository.
///
/// This is the main cargo registry by default, but it can be overridden in
@ -419,6 +439,7 @@ impl SourceId {
.expect("path sources cannot be remote");
Ok(Box::new(DirectorySource::new(&path, self, gctx)))
}
SourceKind::Patched(_) => Ok(Box::new(PatchedSource::new(self, gctx)?)),
}
}
@ -665,6 +686,13 @@ impl fmt::Display for SourceId {
}
SourceKind::LocalRegistry => write!(f, "registry `{}`", url_display(&self.inner.url)),
SourceKind::Directory => write!(f, "dir {}", url_display(&self.inner.url)),
SourceKind::Patched(ref patch_info) => {
let n = patch_info.patches().len();
let plural = if n == 1 { "" } else { "s" };
let name = patch_info.name();
let version = patch_info.version();
write!(f, "{name}@{version} with {n} patch file{plural}")
}
}
}
}
@ -730,6 +758,14 @@ impl<'a> fmt::Display for SourceIdAsUrl<'a> {
write!(f, "#{}", precise)?;
}
}
if let SourceIdInner {
kind: SourceKind::Patched(patch_info),
..
} = &self.inner
{
write!(f, "?{}", patch_info.as_query())?;
}
Ok(())
}
}
@ -806,6 +842,8 @@ mod tests {
use std::hash::Hasher;
use std::path::Path;
use cargo_util_schemas::core::PatchInfo;
let gen_hash = |source_id: SourceId| {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
source_id.stable_hash(Path::new("/tmp/ws"), &mut hasher);
@ -850,6 +888,12 @@ mod tests {
let source_id = SourceId::for_directory(path).unwrap();
assert_eq!(gen_hash(source_id), 17459999773908528552);
assert_eq!(crate::util::hex::short_hash(&source_id), "6568fe2c2fab5bfe");
let patch_info = PatchInfo::new("foo".into(), "1.0.0".into(), vec![path.into()]);
let registry_source_id = SourceId::for_registry(&url).unwrap();
let source_id = SourceId::for_patches(registry_source_id, patch_info).unwrap();
assert_eq!(gen_hash(source_id), 10476212805277277232);
assert_eq!(crate::util::hex::short_hash(&source_id), "45f3b913ab447282");
}
#[test]

View File

@ -478,7 +478,7 @@ impl<'gctx> Workspace<'gctx> {
})?,
};
patch.insert(
url,
url.clone(),
deps.iter()
.map(|(name, dep)| {
crate::util::toml::to_dependency(
@ -492,6 +492,7 @@ impl<'gctx> Workspace<'gctx> {
// any relative paths are resolved before they'd be joined with root.
Path::new("unused-relative-path"),
/* kind */ None,
&url,
)
})
.collect::<CargoResult<Vec<_>>>()?,
@ -1147,11 +1148,26 @@ impl<'gctx> Workspace<'gctx> {
}
pub fn emit_warnings(&self) -> CargoResult<()> {
let ws_lints = self
.root_maybe()
.workspace_config()
.inheritable()
.and_then(|i| i.lints().ok())
.unwrap_or_default();
let ws_cargo_lints = ws_lints
.get("cargo")
.cloned()
.unwrap_or_default()
.into_iter()
.map(|(k, v)| (k.replace('-', "_"), v))
.collect();
for (path, maybe_pkg) in &self.packages.packages {
let path = path.join("Cargo.toml");
if let MaybePackage::Package(pkg) = maybe_pkg {
if self.gctx.cli_unstable().cargo_lints {
self.emit_lints(pkg, &path)?
self.emit_lints(pkg, &path, &ws_cargo_lints)?
}
}
let warnings = match maybe_pkg {
@ -1179,22 +1195,12 @@ impl<'gctx> Workspace<'gctx> {
Ok(())
}
pub fn emit_lints(&self, pkg: &Package, path: &Path) -> CargoResult<()> {
let ws_lints = self
.root_maybe()
.workspace_config()
.inheritable()
.and_then(|i| i.lints().ok())
.unwrap_or_default();
let ws_cargo_lints = ws_lints
.get("cargo")
.cloned()
.unwrap_or_default()
.into_iter()
.map(|(k, v)| (k.replace('-', "_"), v))
.collect();
pub fn emit_lints(
&self,
pkg: &Package,
path: &Path,
ws_cargo_lints: &manifest::TomlToolLints,
) -> CargoResult<()> {
let mut error_count = 0;
let toml_lints = pkg
.manifest()
@ -1212,11 +1218,21 @@ impl<'gctx> Workspace<'gctx> {
.map(|(name, lint)| (name.replace('-', "_"), lint))
.collect();
// We should only be using workspace lints if the `[lints]` table is
// present in the manifest, and `workspace` is set to `true`
let ws_cargo_lints = pkg
.manifest()
.resolved_toml()
.lints
.as_ref()
.is_some_and(|l| l.workspace)
.then(|| ws_cargo_lints);
check_im_a_teapot(
pkg,
&path,
&normalized_lints,
&ws_cargo_lints,
ws_cargo_lints,
&mut error_count,
self.gctx,
)?;
@ -1224,7 +1240,7 @@ impl<'gctx> Workspace<'gctx> {
pkg,
&path,
&normalized_lints,
&ws_cargo_lints,
ws_cargo_lints,
&mut error_count,
self.gctx,
)?;
@ -1232,7 +1248,7 @@ impl<'gctx> Workspace<'gctx> {
pkg,
&path,
&normalized_lints,
&ws_cargo_lints,
ws_cargo_lints,
&mut error_count,
self.gctx,
)?;

View File

@ -38,6 +38,7 @@ pub use self::replaced::ReplacedSource;
pub mod config;
pub mod directory;
pub mod git;
pub mod patched;
pub mod path;
pub mod registry;
pub mod replaced;

View File

@ -0,0 +1,351 @@
//! A source that takes other source and patches it with local patch files.
//! See [`PatchedSource`] for details.
use std::path::Path;
use std::path::PathBuf;
use std::task::Poll;
use anyhow::Context as _;
use cargo_util::paths;
use cargo_util::ProcessBuilder;
use cargo_util::Sha256;
use cargo_util_schemas::core::PatchInfo;
use cargo_util_schemas::core::SourceKind;
use lazycell::LazyCell;
use crate::core::Dependency;
use crate::core::Package;
use crate::core::PackageId;
use crate::core::SourceId;
use crate::core::Verbosity;
use crate::sources::source::MaybePackage;
use crate::sources::source::QueryKind;
use crate::sources::source::Source;
use crate::sources::IndexSummary;
use crate::sources::PathSource;
use crate::sources::SourceConfigMap;
use crate::util::cache_lock::CacheLockMode;
use crate::util::hex;
use crate::util::OptVersionReq;
use crate::CargoResult;
use crate::GlobalContext;
/// A file indicates that if present, the patched source is ready to use.
const READY_LOCK: &str = ".cargo-ok";
/// `PatchedSource` is a source that, when fetching, it patches a paticular
/// package with given local patch files.
///
/// This could only be created from [the `[patch]` section][patch] with any
/// entry carrying `{ .., patches = ["..."] }` field. Other kinds of dependency
/// sections (normal, dev, build) shouldn't allow to create any `PatchedSource`.
///
/// [patch]: https://doc.rust-lang.org/nightly/cargo/reference/overriding-dependencies.html#the-patch-section
///
/// ## Filesystem layout
///
/// When Cargo fetches a package from a `PatchedSource`, it'll copy everything
/// from the original source to a dedicated patched source directory. That
/// directory is located under `$CARGO_HOME`. The patched source of each package
/// would be put under:
///
/// ```text
/// $CARGO_HOME/patched-src/<hash-of-original-source>/<pkg>-<version>/<cksum-of-patches>/`.
/// ```
///
/// The file tree of the patched source directory roughly looks like:
///
/// ```text
/// $CARGO_HOME/patched-src/github.com-6d038ece37e82ae2
/// ├── gimli-0.29.0/
/// │ ├── a0d193bd15a5ed96/ # checksum of all patch files from a patch to gimli@0.29.0
/// │ ├── c58e1db3de7c154d/
/// └── serde-1.0.197/
/// └── deadbeef12345678/
/// ```
///
/// ## `SourceId` for tracking the original package
///
/// Due to the nature that a patched source is actually locked to a specific
/// version of one package, the SourceId URL of a `PatchedSource` needs to
/// carry such information. It looks like:
///
/// ```text
/// patched+registry+https://github.com/rust-lang/crates.io-index?name=foo&version=1.0.0&patch=0001-bugfix.patch
/// ```
///
/// where the `patched+` protocol is essential for Cargo to distinguish between
/// a patched source and the source it patches. The query string contains the
/// name and version of the package being patched. We want patches to be as
/// reproducible as it could, so lock to one specific version here.
/// See [`PatchInfo::from_query`] to learn what are being tracked.
///
/// To achieve it, the version specified in any of the entry in `[patch]` must
/// be an exact version via the `=` SemVer comparsion operator. For example,
/// this will fetch source of serde@1.2.3 from crates.io, and apply patches to it.
///
/// ```toml
/// [patch.crates-io]
/// serde = { version = "=1.2.3", patches = ["patches/0001-serde-bug.patch"] }
/// ```
///
/// ## Patch tools
///
/// When patching a package, Cargo will change the working directory to
/// the root directory of the copied source code, and then execute the tool
/// specified via the `patchtool.path` config value in the Cargo configuration.
/// Paths of patch files will be provided as absolute paths to the tool.
pub struct PatchedSource<'gctx> {
source_id: SourceId,
/// The source of the package we're going to patch.
original_source: Box<dyn Source + 'gctx>,
/// Checksum from all patch files.
patches_checksum: LazyCell<String>,
/// For respecting `[source]` replacement configuration.
map: SourceConfigMap<'gctx>,
path_source: Option<PathSource<'gctx>>,
quiet: bool,
gctx: &'gctx GlobalContext,
}
impl<'gctx> PatchedSource<'gctx> {
pub fn new(
source_id: SourceId,
gctx: &'gctx GlobalContext,
) -> CargoResult<PatchedSource<'gctx>> {
let original_id = {
let mut url = source_id.url().clone();
url.set_query(None);
url.set_fragment(None);
let url = url.as_str();
let Some(url) = url.strip_prefix("patched+") else {
anyhow::bail!("patched source url requires a `patched` scheme, got `{url}`");
};
SourceId::from_url(&url)?
};
let map = SourceConfigMap::new(gctx)?;
let source = PatchedSource {
source_id,
original_source: map.load(original_id, &Default::default())?,
patches_checksum: LazyCell::new(),
map,
path_source: None,
quiet: false,
gctx,
};
Ok(source)
}
/// Downloads the package source if needed.
fn download_pkg(&mut self) -> CargoResult<Package> {
let patch_info = self.patch_info();
let exact_req = &format!("={}", patch_info.version());
let original_id = self.original_source.source_id();
let dep = Dependency::parse(patch_info.name(), Some(exact_req), original_id)?;
let pkg_id = loop {
match self.original_source.query_vec(&dep, QueryKind::Exact) {
Poll::Ready(deps) => break deps?.remove(0).as_summary().package_id(),
Poll::Pending => self.original_source.block_until_ready()?,
}
};
let source = self.map.load(original_id, &Default::default())?;
Box::new(source).download_now(pkg_id, self.gctx)
}
fn copy_pkg_src(&self, pkg: &Package, dst: &Path) -> CargoResult<()> {
let src = pkg.root();
for entry in walkdir::WalkDir::new(src) {
let entry = entry?;
let path = entry.path().strip_prefix(src).unwrap();
let src = entry.path();
let dst = dst.join(path);
if entry.file_type().is_dir() {
paths::create_dir_all(dst)?;
} else {
// TODO: handle symlink?
paths::copy(src, dst)?;
}
}
Ok(())
}
fn apply_patches(&self, pkg: &Package, dst: &Path) -> CargoResult<()> {
let patches = self.patch_info().patches();
let n = patches.len();
assert!(n > 0, "must have at least one patch, got {n}");
self.gctx.shell().status("Patching", pkg)?;
let patchtool_config = self.gctx.patchtool_config()?;
let Some(tool) = patchtool_config.path.as_ref() else {
anyhow::bail!("missing `[patchtool]` for patching dependencies");
};
let program = tool.path.resolve_program(self.gctx);
let mut cmd = ProcessBuilder::new(program);
cmd.cwd(dst).args(&tool.args);
for patch_path in patches {
let patch_path = self.gctx.cwd().join(patch_path);
let mut cmd = cmd.clone();
cmd.arg(patch_path);
if matches!(self.gctx.shell().verbosity(), Verbosity::Verbose) {
self.gctx.shell().status("Running", &cmd)?;
cmd.exec()?;
} else {
cmd.exec_with_output()?;
}
}
Ok(())
}
/// Gets the destination directory we put the patched source at.
fn dest_src_dir(&self, pkg: &Package) -> CargoResult<PathBuf> {
let patched_src_root = self.gctx.patched_source_path();
let patched_src_root = self
.gctx
.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &patched_src_root);
let pkg_id = pkg.package_id();
let source_id = pkg_id.source_id();
let ident = source_id.url().host_str().unwrap_or_default();
let hash = hex::short_hash(&source_id);
let name = pkg_id.name();
let version = pkg_id.version();
let mut dst = patched_src_root.join(format!("{ident}-{hash}"));
dst.push(format!("{name}-{version}"));
dst.push(self.patches_checksum()?);
Ok(dst)
}
fn patches_checksum(&self) -> CargoResult<&String> {
self.patches_checksum.try_borrow_with(|| {
let mut cksum = Sha256::new();
for patch in self.patch_info().patches() {
cksum.update_path(patch)?;
}
let mut cksum = cksum.finish_hex();
// TODO: is it safe to truncate sha256?
cksum.truncate(16);
Ok(cksum)
})
}
fn patch_info(&self) -> &PatchInfo {
let SourceKind::Patched(info) = self.source_id.kind() else {
panic!("patched source must be SourceKind::Patched");
};
info
}
}
impl<'gctx> Source for PatchedSource<'gctx> {
fn source_id(&self) -> SourceId {
self.source_id
}
fn supports_checksums(&self) -> bool {
false
}
fn requires_precise(&self) -> bool {
false
}
fn query(
&mut self,
dep: &Dependency,
kind: QueryKind,
f: &mut dyn FnMut(IndexSummary),
) -> Poll<CargoResult<()>> {
// Version requirement here is still the `=` exact one for fetching
// the source to patch, so switch it to a wildchard requirement.
// It is safe because this source contains one and the only package.
let mut dep = dep.clone();
dep.set_version_req(OptVersionReq::Any);
if let Some(src) = self.path_source.as_mut() {
src.query(&dep, kind, f)
} else {
Poll::Pending
}
}
fn invalidate_cache(&mut self) {
// No cache for a patched source
}
fn set_quiet(&mut self, quiet: bool) {
self.quiet = quiet;
}
fn download(&mut self, id: PackageId) -> CargoResult<MaybePackage> {
self.path_source
.as_mut()
.expect("path source must exist")
.download(id)
}
fn finish_download(&mut self, _pkg_id: PackageId, _contents: Vec<u8>) -> CargoResult<Package> {
panic!("no download should have started")
}
fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
let fingerprint = self.original_source.fingerprint(pkg)?;
let cksum = self.patches_checksum()?;
Ok(format!("{fingerprint}/{cksum}"))
}
fn describe(&self) -> String {
use std::fmt::Write as _;
let mut desc = self.original_source.describe();
let n_patches = self.patch_info().patches().len();
write!(desc, "with {n_patches} patch file").unwrap();
if n_patches > 1 {
write!(desc, "s").unwrap();
}
desc
}
fn add_to_yanked_whitelist(&mut self, _pkgs: &[PackageId]) {
// There is no yanked package for a patched source
}
fn is_yanked(&mut self, _pkg: PackageId) -> Poll<CargoResult<bool>> {
// There is no yanked package for a patched source
Poll::Ready(Ok(false))
}
fn block_until_ready(&mut self) -> CargoResult<()> {
if self.path_source.is_some() {
return Ok(());
}
let pkg = self.download_pkg().context("failed to download source")?;
let dst = self.dest_src_dir(&pkg)?;
let ready_lock = dst.join(READY_LOCK);
let cksum = self.patches_checksum()?;
match paths::read(&ready_lock) {
Ok(prev_cksum) if &prev_cksum == cksum => {
// We've applied patches. Assume they never change.
}
_ => {
// Either we were interrupted, or never get started.
// We just start over here.
if let Err(e) = paths::remove_dir_all(&dst) {
tracing::trace!("failed to remove `{}`: {e}", dst.display());
}
self.copy_pkg_src(&pkg, &dst)
.context("failed to copy source")?;
self.apply_patches(&pkg, &dst)
.context("failed to apply patches")?;
paths::write(&ready_lock, cksum)?;
}
}
self.path_source = Some(PathSource::new(&dst, self.source_id, self.gctx));
Ok(())
}
}

View File

@ -39,7 +39,12 @@ impl CanonicalUrl {
// almost certainly not using the same case conversion rules that GitHub
// does. (See issue #84)
if url.host_str() == Some("github.com") {
url = format!("https{}", &url[url::Position::AfterScheme..])
let proto = if url.scheme().starts_with("patched+") {
"patched+https"
} else {
"https"
};
url = format!("{proto}{}", &url[url::Position::AfterScheme..])
.parse()
.unwrap();
let path = url.path().to_lowercase();

View File

@ -228,6 +228,7 @@ pub struct GlobalContext {
doc_extern_map: LazyCell<RustdocExternMap>,
progress_config: ProgressConfig,
env_config: LazyCell<EnvConfig>,
patchtool_config: LazyCell<PatchtoolConfig>,
/// This should be false if:
/// - this is an artifact of the rustc distribution process for "stable" or for "beta"
/// - this is an `#[test]` that does not opt in with `enable_nightly_features`
@ -322,6 +323,7 @@ impl GlobalContext {
doc_extern_map: LazyCell::new(),
progress_config: ProgressConfig::default(),
env_config: LazyCell::new(),
patchtool_config: LazyCell::new(),
nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
ws_roots: RefCell::new(HashMap::new()),
global_cache_tracker: LazyCell::new(),
@ -400,6 +402,11 @@ impl GlobalContext {
self.registry_base_path().join("src")
}
/// Gets the directory containg patched package sources (`<cargo_home>/patched-src`).
pub fn patched_source_path(&self) -> Filesystem {
self.home_path.join("patched-src")
}
/// Gets the default Cargo registry.
pub fn default_registry(&self) -> CargoResult<Option<String>> {
Ok(self
@ -1857,6 +1864,11 @@ impl GlobalContext {
Ok(env_config)
}
pub fn patchtool_config(&self) -> CargoResult<&PatchtoolConfig> {
self.patchtool_config
.try_borrow_with(|| self.get::<PatchtoolConfig>("patchtool"))
}
/// This is used to validate the `term` table has valid syntax.
///
/// This is necessary because loading the term settings happens very
@ -2741,6 +2753,12 @@ where
deserializer.deserialize_option(ProgressVisitor)
}
#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct PatchtoolConfig {
pub path: Option<PathAndArgs>,
}
#[derive(Debug)]
enum EnvConfigValueInner {
Simple(String),

View File

@ -88,7 +88,7 @@ impl Lint {
pub fn level(
&self,
pkg_lints: &TomlToolLints,
ws_lints: &TomlToolLints,
ws_lints: Option<&TomlToolLints>,
edition: Edition,
) -> (LintLevel, LintLevelReason) {
self.groups
@ -188,7 +188,7 @@ fn level_priority(
default_level: LintLevel,
edition_lint_opts: Option<(Edition, LintLevel)>,
pkg_lints: &TomlToolLints,
ws_lints: &TomlToolLints,
ws_lints: Option<&TomlToolLints>,
edition: Edition,
) -> (LintLevel, LintLevelReason, i8) {
let (unspecified_level, reason) = if let Some(level) = edition_lint_opts
@ -211,7 +211,7 @@ fn level_priority(
LintLevelReason::Package,
defined_level.priority(),
)
} else if let Some(defined_level) = ws_lints.get(name) {
} else if let Some(defined_level) = ws_lints.and_then(|l| l.get(name)) {
(
defined_level.level().into(),
LintLevelReason::Workspace,
@ -234,7 +234,7 @@ pub fn check_im_a_teapot(
pkg: &Package,
path: &Path,
pkg_lints: &TomlToolLints,
ws_lints: &TomlToolLints,
ws_lints: Option<&TomlToolLints>,
error_count: &mut usize,
gctx: &GlobalContext,
) -> CargoResult<()> {
@ -306,7 +306,7 @@ pub fn check_implicit_features(
pkg: &Package,
path: &Path,
pkg_lints: &TomlToolLints,
ws_lints: &TomlToolLints,
ws_lints: Option<&TomlToolLints>,
error_count: &mut usize,
gctx: &GlobalContext,
) -> CargoResult<()> {
@ -390,7 +390,7 @@ pub fn unused_dependencies(
pkg: &Package,
path: &Path,
pkg_lints: &TomlToolLints,
ws_lints: &TomlToolLints,
ws_lints: Option<&TomlToolLints>,
error_count: &mut usize,
gctx: &GlobalContext,
) -> CargoResult<()> {

View File

@ -90,13 +90,15 @@ impl<T> SleepTracker<T> {
#[test]
fn returns_in_order() {
let mut s = SleepTracker::new();
s.push(3, 3);
s.push(30_000, 30_000);
s.push(1, 1);
s.push(6, 6);
s.push(5, 5);
s.push(2, 2);
s.push(10000, 10000);
assert_eq!(s.len(), 6);
std::thread::sleep(Duration::from_millis(100));
assert_eq!(s.to_retry(), &[1, 2, 3, 5, 6]);
assert_eq!(s.len(), 2);
std::thread::sleep(Duration::from_millis(2));
assert_eq!(s.to_retry(), &[1]);
assert!(s.to_retry().is_empty());
let next = s.time_to_next().expect("should be next");
assert!(
next < Duration::from_millis(30_000),
"{next:?} should be less than 30s"
);
}

View File

@ -1,4 +1,5 @@
use annotate_snippets::{Level, Renderer, Snippet};
use cargo_util_schemas::core::PatchInfo;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
@ -28,6 +29,7 @@ use crate::core::{GitReference, PackageIdSpec, SourceId, WorkspaceConfig, Worksp
use crate::sources::{CRATES_IO_INDEX, CRATES_IO_REGISTRY};
use crate::util::errors::{CargoResult, ManifestError};
use crate::util::interning::InternedString;
use crate::util::CanonicalUrl;
use crate::util::{self, context::ConfigRelativePath, GlobalContext, IntoUrl, OptVersionReq};
mod embedded;
@ -1265,7 +1267,7 @@ fn to_real_manifest(
)?;
}
let replace = replace(&resolved_toml, &mut manifest_ctx)?;
let patch = patch(&resolved_toml, &mut manifest_ctx)?;
let patch = patch(&resolved_toml, &mut manifest_ctx, &features)?;
{
let mut names_sources = BTreeMap::new();
@ -1528,7 +1530,7 @@ fn to_virtual_manifest(
};
(
replace(&original_toml, &mut manifest_ctx)?,
patch(&original_toml, &mut manifest_ctx)?,
patch(&original_toml, &mut manifest_ctx, &features)?,
)
};
if let Some(profiles) = &original_toml.profile {
@ -1607,7 +1609,7 @@ fn gather_dependencies(
for (n, v) in dependencies.iter() {
let resolved = v.resolved().expect("previously resolved");
let dep = dep_to_dependency(&resolved, n, manifest_ctx, kind)?;
let dep = dep_to_dependency(&resolved, n, manifest_ctx, kind, None)?;
manifest_ctx.deps.push(dep);
}
Ok(())
@ -1641,7 +1643,7 @@ fn replace(
);
}
let mut dep = dep_to_dependency(replacement, spec.name(), manifest_ctx, None)?;
let mut dep = dep_to_dependency(replacement, spec.name(), manifest_ctx, None, None)?;
let version = spec.version().ok_or_else(|| {
anyhow!(
"replacements must specify a version \
@ -1664,7 +1666,9 @@ fn replace(
fn patch(
me: &manifest::TomlManifest,
manifest_ctx: &mut ManifestContext<'_, '_>,
features: &Features,
) -> CargoResult<HashMap<Url, Vec<Dependency>>> {
let patch_files_enabled = features.require(Feature::patch_files()).is_ok();
let mut patch = HashMap::new();
for (toml_url, deps) in me.patch.iter().flatten() {
let url = match &toml_url[..] {
@ -1681,7 +1685,7 @@ fn patch(
})?,
};
patch.insert(
url,
url.clone(),
deps.iter()
.map(|(name, dep)| {
unused_dep_keys(
@ -1690,7 +1694,13 @@ fn patch(
dep.unused_keys(),
&mut manifest_ctx.warnings,
);
dep_to_dependency(dep, name, manifest_ctx, None)
dep_to_dependency(
dep,
name,
manifest_ctx,
None,
Some((&url, patch_files_enabled)),
)
})
.collect::<CargoResult<Vec<_>>>()?,
);
@ -1698,6 +1708,7 @@ fn patch(
Ok(patch)
}
/// Transforms a `patch` entry to a [`Dependency`].
pub(crate) fn to_dependency<P: ResolveToPath + Clone>(
dep: &manifest::TomlDependency<P>,
name: &str,
@ -1707,20 +1718,18 @@ pub(crate) fn to_dependency<P: ResolveToPath + Clone>(
platform: Option<Platform>,
root: &Path,
kind: Option<DepKind>,
patch_source_url: &Url,
) -> CargoResult<Dependency> {
dep_to_dependency(
dep,
name,
&mut ManifestContext {
deps: &mut Vec::new(),
source_id,
gctx,
warnings,
platform,
root,
},
kind,
)
let manifest_ctx = &mut ManifestContext {
deps: &mut Vec::new(),
source_id,
gctx,
warnings,
platform,
root,
};
let patch_source_url = Some((patch_source_url, gctx.cli_unstable().patch_files));
dep_to_dependency(dep, name, manifest_ctx, kind, patch_source_url)
}
fn dep_to_dependency<P: ResolveToPath + Clone>(
@ -1728,6 +1737,7 @@ fn dep_to_dependency<P: ResolveToPath + Clone>(
name: &str,
manifest_ctx: &mut ManifestContext<'_, '_>,
kind: Option<DepKind>,
patch_source_url: Option<(&Url, bool)>,
) -> CargoResult<Dependency> {
match *orig {
manifest::TomlDependency::Simple(ref version) => detailed_dep_to_dependency(
@ -1738,9 +1748,10 @@ fn dep_to_dependency<P: ResolveToPath + Clone>(
name,
manifest_ctx,
kind,
patch_source_url,
),
manifest::TomlDependency::Detailed(ref details) => {
detailed_dep_to_dependency(details, name, manifest_ctx, kind)
detailed_dep_to_dependency(details, name, manifest_ctx, kind, patch_source_url)
}
}
}
@ -1750,6 +1761,7 @@ fn detailed_dep_to_dependency<P: ResolveToPath + Clone>(
name_in_toml: &str,
manifest_ctx: &mut ManifestContext<'_, '_>,
kind: Option<DepKind>,
patch_source_url: Option<(&Url, bool)>,
) -> CargoResult<Dependency> {
if orig.version.is_none() && orig.path.is_none() && orig.git.is_none() {
anyhow::bail!(
@ -1885,6 +1897,11 @@ fn detailed_dep_to_dependency<P: ResolveToPath + Clone>(
)
}
}
if let Some(source_id) = patched_source_id(orig, manifest_ctx, &dep, patch_source_url)? {
dep.set_source_id(source_id);
}
Ok(dep)
}
@ -1973,6 +1990,88 @@ fn to_dependency_source_id<P: ResolveToPath + Clone>(
}
}
// Handle `patches` field for `[patch]` table, if any.
fn patched_source_id<P: ResolveToPath + Clone>(
orig: &manifest::TomlDetailedDependency<P>,
manifest_ctx: &mut ManifestContext<'_, '_>,
dep: &Dependency,
patch_source_url: Option<(&Url, bool)>,
) -> CargoResult<Option<SourceId>> {
let name_in_toml = dep.name_in_toml().as_str();
let message = "see https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#patch-files about the status of this feature.";
match (patch_source_url, orig.patches.as_ref()) {
(_, None) => {
// not a SourceKind::Patched dep.
Ok(None)
}
(None, Some(_)) => {
let kind = dep.kind().kind_table();
manifest_ctx.warnings.push(format!(
"unused manifest key: {kind}.{name_in_toml}.patches; {message}"
));
Ok(None)
}
(Some((url, false)), Some(_)) => {
manifest_ctx.warnings.push(format!(
"ignoring `patches` on patch for `{name_in_toml}` in `{url}`; {message}"
));
Ok(None)
}
(Some((url, true)), Some(patches)) => {
let source_id = dep.source_id();
if !source_id.is_registry() {
bail!(
"patch for `{name_in_toml}` in `{url}` requires a registry source \
when patching with patch files"
);
}
if &CanonicalUrl::new(url)? != source_id.canonical_url() {
bail!(
"patch for `{name_in_toml}` in `{url}` must refer to the same source \
when patching with patch files"
)
}
let version = match dep.version_req().locked_version() {
Some(v) => Some(v.to_owned()),
None if dep.version_req().is_exact() => {
// Remove the `=` exact operator.
orig.version
.as_deref()
.map(|v| v[1..].trim().parse().ok())
.flatten()
}
None => None,
};
let Some(version) = version else {
bail!(
"patch for `{name_in_toml}` in `{url}` requires an exact version \
when patching with patch files"
);
};
let patches: Vec<_> = patches
.iter()
.map(|path| {
let path = path.resolve(manifest_ctx.gctx);
let path = manifest_ctx.root.join(path);
// keep paths inside workspace relative to workspace, otherwise absolute.
path.strip_prefix(manifest_ctx.gctx.cwd())
.map(Into::into)
.unwrap_or_else(|_| paths::normalize_path(&path))
})
.collect();
if patches.is_empty() {
bail!(
"patch for `{name_in_toml}` in `{url}` requires at least one patch file \
when patching with patch files"
);
}
let pkg_name = dep.package_name().to_string();
let patch_info = PatchInfo::new(pkg_name, version.to_string(), patches);
SourceId::for_patches(source_id, patch_info).map(Some)
}
}
}
pub trait ResolveToPath {
fn resolve(&self, gctx: &GlobalContext) -> PathBuf;
}

View File

@ -1,4 +1,4 @@
<svg width="1230px" height="722px" xmlns="http://www.w3.org/2000/svg">
<svg width="1230px" height="740px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { background: #000000 }
@ -69,33 +69,35 @@
</tspan>
<tspan x="10px" y="460px"><tspan> </tspan><tspan class="fg-cyan bold">-Z panic-abort-tests </tspan><tspan> Enable support to run tests with -Cpanic=abort</tspan>
</tspan>
<tspan x="10px" y="478px"><tspan> </tspan><tspan class="fg-cyan bold">-Z profile-rustflags </tspan><tspan> Enable the `rustflags` option in profiles in .cargo/config.toml file</tspan>
<tspan x="10px" y="478px"><tspan> </tspan><tspan class="fg-cyan bold">-Z patch-files </tspan><tspan> Allow patching dependencies with patch files</tspan>
</tspan>
<tspan x="10px" y="496px"><tspan> </tspan><tspan class="fg-cyan bold">-Z public-dependency </tspan><tspan> Respect a dependency's `public` field in Cargo.toml to control public/private dependencies</tspan>
<tspan x="10px" y="496px"><tspan> </tspan><tspan class="fg-cyan bold">-Z profile-rustflags </tspan><tspan> Enable the `rustflags` option in profiles in .cargo/config.toml file</tspan>
</tspan>
<tspan x="10px" y="514px"><tspan> </tspan><tspan class="fg-cyan bold">-Z publish-timeout </tspan><tspan> Enable the `publish.timeout` key in .cargo/config.toml file</tspan>
<tspan x="10px" y="514px"><tspan> </tspan><tspan class="fg-cyan bold">-Z public-dependency </tspan><tspan> Respect a dependency's `public` field in Cargo.toml to control public/private dependencies</tspan>
</tspan>
<tspan x="10px" y="532px"><tspan> </tspan><tspan class="fg-cyan bold">-Z rustdoc-map </tspan><tspan> Allow passing external documentation mappings to rustdoc</tspan>
<tspan x="10px" y="532px"><tspan> </tspan><tspan class="fg-cyan bold">-Z publish-timeout </tspan><tspan> Enable the `publish.timeout` key in .cargo/config.toml file</tspan>
</tspan>
<tspan x="10px" y="550px"><tspan> </tspan><tspan class="fg-cyan bold">-Z rustdoc-scrape-examples</tspan><tspan> Allows Rustdoc to scrape code examples from reverse-dependencies</tspan>
<tspan x="10px" y="550px"><tspan> </tspan><tspan class="fg-cyan bold">-Z rustdoc-map </tspan><tspan> Allow passing external documentation mappings to rustdoc</tspan>
</tspan>
<tspan x="10px" y="568px"><tspan> </tspan><tspan class="fg-cyan bold">-Z script </tspan><tspan> Enable support for single-file, `.rs` packages</tspan>
<tspan x="10px" y="568px"><tspan> </tspan><tspan class="fg-cyan bold">-Z rustdoc-scrape-examples</tspan><tspan> Allows Rustdoc to scrape code examples from reverse-dependencies</tspan>
</tspan>
<tspan x="10px" y="586px"><tspan> </tspan><tspan class="fg-cyan bold">-Z target-applies-to-host </tspan><tspan> Enable the `target-applies-to-host` key in the .cargo/config.toml file</tspan>
<tspan x="10px" y="586px"><tspan> </tspan><tspan class="fg-cyan bold">-Z script </tspan><tspan> Enable support for single-file, `.rs` packages</tspan>
</tspan>
<tspan x="10px" y="604px"><tspan> </tspan><tspan class="fg-cyan bold">-Z trim-paths </tspan><tspan> Enable the `trim-paths` option in profiles</tspan>
<tspan x="10px" y="604px"><tspan> </tspan><tspan class="fg-cyan bold">-Z target-applies-to-host </tspan><tspan> Enable the `target-applies-to-host` key in the .cargo/config.toml file</tspan>
</tspan>
<tspan x="10px" y="622px"><tspan> </tspan><tspan class="fg-cyan bold">-Z unstable-options </tspan><tspan> Allow the usage of unstable options</tspan>
<tspan x="10px" y="622px"><tspan> </tspan><tspan class="fg-cyan bold">-Z trim-paths </tspan><tspan> Enable the `trim-paths` option in profiles</tspan>
</tspan>
<tspan x="10px" y="640px">
<tspan x="10px" y="640px"><tspan> </tspan><tspan class="fg-cyan bold">-Z unstable-options </tspan><tspan> Allow the usage of unstable options</tspan>
</tspan>
<tspan x="10px" y="658px"><tspan>Run with `</tspan><tspan class="fg-cyan bold">cargo -Z</tspan><tspan> </tspan><tspan class="fg-cyan">[FLAG] [COMMAND]</tspan><tspan>`</tspan>
<tspan x="10px" y="658px">
</tspan>
<tspan x="10px" y="676px">
<tspan x="10px" y="676px"><tspan>Run with `</tspan><tspan class="fg-cyan bold">cargo -Z</tspan><tspan> </tspan><tspan class="fg-cyan">[FLAG] [COMMAND]</tspan><tspan>`</tspan>
</tspan>
<tspan x="10px" y="694px"><tspan>See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html for more information about these flags.</tspan>
<tspan x="10px" y="694px">
</tspan>
<tspan x="10px" y="712px">
<tspan x="10px" y="712px"><tspan>See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html for more information about these flags.</tspan>
</tspan>
<tspan x="10px" y="730px">
</tspan>
</text>

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -982,3 +982,43 @@ error: `im_a_teapot` is specified
)
.run();
}
#[cargo_test]
fn dont_always_inherit_workspace_lints() {
let p = project()
.file(
"Cargo.toml",
r#"
[workspace]
members = ["foo"]
[workspace.lints.cargo]
im-a-teapot = "warn"
"#,
)
.file(
"foo/Cargo.toml",
r#"
cargo-features = ["test-dummy-unstable"]
[package]
name = "foo"
version = "0.0.1"
edition = "2015"
authors = []
im-a-teapot = true
"#,
)
.file("foo/src/lib.rs", "")
.build();
p.cargo("check -Zcargo-lints")
.masquerade_as_nightly_cargo(&["cargo-lints"])
.with_stderr(
"\
[CHECKING] foo v0.0.1 ([CWD]/foo)
[FINISHED] [..]
",
)
.run();
}

View File

@ -134,6 +134,7 @@ mod owner;
mod package;
mod package_features;
mod patch;
mod patch_files;
mod path;
mod paths;
mod pkgid;

File diff suppressed because it is too large Load Diff