mirror of
https://github.com/zed-industries/zed.git
synced 2026-10-28 08:55:27 +08:00
xtask: Forbid legacy Objective-C dependencies (#65935)
Reject objc, cocoa, and related legacy wrappers in workspace packages and unused workspace dependency declarations. Cover aliases, optional and target-specific dependencies, build and dev dependencies, and permitted objc2 replacements. Release Notes: - N/A
This commit is contained in:
1 parent
2d6c664dc2
commit
9b9e7b814a
1 file changed
+163
-21
+163
-21
@@ -10,14 +10,28 @@ pub fn load_workspace() -> Result<Metadata> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet, VecDeque},
|
||||
fs,
|
||||
};
|
||||
|
||||
use cargo_metadata::DependencyKind;
|
||||
use anyhow::{Context as _, Result};
|
||||
use cargo_metadata::{DependencyKind, Metadata, MetadataCommand};
|
||||
|
||||
use super::load_workspace;
|
||||
|
||||
/// Direct dependencies forbidden in workspace packages, paired with the reason shown on failure.
|
||||
const FORBIDDEN_DIRECT_DEPENDENCIES: &[(&str, &str)] = &[("block", "use `block2` instead")];
|
||||
const FORBIDDEN_DIRECT_DEPENDENCIES: &[(&str, &str)] = &[
|
||||
("block", "use `block2` instead"),
|
||||
(
|
||||
"cocoa",
|
||||
"use `objc2-app-kit` and `objc2-foundation` instead",
|
||||
),
|
||||
("cocoa-foundation", "use `objc2-foundation` instead"),
|
||||
("objc", "use `objc2` instead"),
|
||||
("objc-foundation", "use `objc2-foundation` instead"),
|
||||
("objc_id", "use `objc2::rc::Retained` instead"),
|
||||
];
|
||||
|
||||
/// Crates that must not depend on each other, directly or transitively:
|
||||
/// such edges chain large UI crates one after another and serialize the
|
||||
@@ -39,24 +53,9 @@ mod tests {
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn no_forbidden_direct_dependencies() {
|
||||
let workspace = load_workspace().expect("failed to load cargo metadata");
|
||||
let mut violations = Vec::new();
|
||||
|
||||
for package in workspace.workspace_packages() {
|
||||
for dependency in &package.dependencies {
|
||||
let Some((_, reason)) = FORBIDDEN_DIRECT_DEPENDENCIES
|
||||
.iter()
|
||||
.find(|(name, _)| *name == dependency.name)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
violations.push(format!(
|
||||
"{} directly depends on `{}`; {reason}",
|
||||
package.name, dependency.name
|
||||
));
|
||||
}
|
||||
}
|
||||
fn no_forbidden_direct_dependencies() -> Result<()> {
|
||||
let workspace = load_workspace()?;
|
||||
let violations = forbidden_direct_dependency_violations(&workspace)?;
|
||||
|
||||
assert_eq!(
|
||||
violations,
|
||||
@@ -64,6 +63,105 @@ mod tests {
|
||||
"forbidden direct dependencies:\n{}",
|
||||
violations.join("\n"),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forbidden_direct_dependencies_cover_manifest_declarations() -> Result<()> {
|
||||
let fixture = tempfile::tempdir()?;
|
||||
for name in [
|
||||
"block",
|
||||
"cocoa",
|
||||
"cocoa-foundation",
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
"objc_id",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"metal",
|
||||
"workspace",
|
||||
] {
|
||||
let directory = fixture.path().join(name);
|
||||
fs::create_dir(&directory)?;
|
||||
fs::write(directory.join("lib.rs"), "")?;
|
||||
let dependencies = if name == "metal" {
|
||||
"[dependencies]\nobjc = { path = '../objc' }\n"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
fs::write(
|
||||
directory.join("Cargo.toml"),
|
||||
format!(
|
||||
"[package]\nname = '{name}'\nversion = '0.1.0'\nedition = '2024'\n\
|
||||
[lib]\npath = 'lib.rs'\n{dependencies}"
|
||||
),
|
||||
)?;
|
||||
}
|
||||
let manifest = fixture.path().join("workspace/Cargo.toml");
|
||||
let allowed = format!(
|
||||
"{}\n{}",
|
||||
fs::read_to_string(&manifest)?,
|
||||
r#"[workspace]
|
||||
resolver = "2"
|
||||
[dependencies]
|
||||
block2 = { path = "../block2" }
|
||||
objc2 = { path = "../objc2" }
|
||||
objc2-app-kit = { path = "../objc2-app-kit" }
|
||||
objc2-foundation = { path = "../objc2-foundation" }
|
||||
metal = { path = "../metal" }
|
||||
"#,
|
||||
);
|
||||
let forbidden = r#"
|
||||
[dependencies.block]
|
||||
path = "../block"
|
||||
[dependencies.legacy_cocoa]
|
||||
package = "cocoa"
|
||||
path = "../cocoa"
|
||||
[dependencies.cocoa-foundation]
|
||||
path = "../cocoa-foundation"
|
||||
optional = true
|
||||
[target.'cfg(any())'.dependencies]
|
||||
objc = { path = "../objc" }
|
||||
[dev-dependencies]
|
||||
objc-foundation = { path = "../objc-foundation" }
|
||||
[target.'cfg(any())'.build-dependencies]
|
||||
legacy_id = { workspace = true, optional = true }
|
||||
[workspace.dependencies]
|
||||
legacy_id = { package = "objc_id", path = "../objc_id" }
|
||||
objc = "0.1.0"
|
||||
unused_cocoa = { package = "cocoa", version = "0.1.0" }
|
||||
unused_objc2 = { package = "objc2", version = "0.1.0" }
|
||||
"#;
|
||||
fs::write(&manifest, format!("{allowed}{forbidden}"))?;
|
||||
let mut command = MetadataCommand::new();
|
||||
command
|
||||
.manifest_path(&manifest)
|
||||
.other_options(vec!["--offline".to_owned()]);
|
||||
let mut violations = forbidden_direct_dependency_violations(&command.exec()?)?;
|
||||
violations.sort();
|
||||
assert_eq!(
|
||||
violations,
|
||||
[
|
||||
"Cargo.toml [workspace.dependencies]: `legacy_id` uses `objc_id`; use `objc2::rc::Retained` instead",
|
||||
"Cargo.toml [workspace.dependencies]: `objc` uses `objc`; use `objc2` instead",
|
||||
"Cargo.toml [workspace.dependencies]: `unused_cocoa` uses `cocoa`; use `objc2-app-kit` and `objc2-foundation` instead",
|
||||
"workspace directly depends on `block`; use `block2` instead",
|
||||
"workspace directly depends on `cocoa-foundation`; use `objc2-foundation` instead",
|
||||
"workspace directly depends on `cocoa`; use `objc2-app-kit` and `objc2-foundation` instead",
|
||||
"workspace directly depends on `objc-foundation`; use `objc2-foundation` instead",
|
||||
"workspace directly depends on `objc_id`; use `objc2::rc::Retained` instead",
|
||||
"workspace directly depends on `objc`; use `objc2` instead",
|
||||
],
|
||||
"including renamed optional inactive-target build dependency",
|
||||
);
|
||||
fs::write(&manifest, allowed)?;
|
||||
assert_eq!(
|
||||
forbidden_direct_dependency_violations(&command.exec()?)?,
|
||||
Vec::<String>::new(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -142,6 +240,50 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
fn forbidden_direct_dependency_violations(workspace: &Metadata) -> Result<Vec<String>> {
|
||||
let mut violations = Vec::new();
|
||||
for package in workspace.workspace_packages() {
|
||||
for dependency in &package.dependencies {
|
||||
let Some((_, reason)) = FORBIDDEN_DIRECT_DEPENDENCIES
|
||||
.iter()
|
||||
.find(|(name, _)| *name == dependency.name)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
violations.push(format!(
|
||||
"{} directly depends on `{}`; {reason}",
|
||||
package.name, dependency.name
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let manifest_path = workspace.workspace_root.join("Cargo.toml");
|
||||
let contents = fs::read_to_string(&manifest_path)
|
||||
.with_context(|| format!("failed to read {manifest_path}"))?;
|
||||
let manifest = toml::from_str::<toml::Value>(&contents)
|
||||
.with_context(|| format!("failed to parse {manifest_path}"))?;
|
||||
let dependencies = manifest
|
||||
.get("workspace")
|
||||
.and_then(|workspace| workspace.get("dependencies"))
|
||||
.and_then(toml::Value::as_table);
|
||||
for (alias, dependency) in dependencies.into_iter().flatten() {
|
||||
let name = dependency
|
||||
.get("package")
|
||||
.and_then(toml::Value::as_str)
|
||||
.unwrap_or(alias);
|
||||
let Some((_, reason)) = FORBIDDEN_DIRECT_DEPENDENCIES
|
||||
.iter()
|
||||
.find(|(forbidden, _)| *forbidden == name)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
violations.push(format!(
|
||||
"Cargo.toml [workspace.dependencies]: `{alias}` uses `{name}`; {reason}"
|
||||
));
|
||||
}
|
||||
Ok(violations)
|
||||
}
|
||||
|
||||
fn dependency_path<'a>(
|
||||
graph: &BTreeMap<&'a str, BTreeSet<&'a str>>,
|
||||
from: &'a str,
|
||||
|
||||
Reference in new issue
Block a user