Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/socket-patch-cli/CLI_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,8 @@ are patched by `socket-patch apply` like every other ecosystem:
### sbt / Mill / scala-cli caches in agent mode

A JVM project root (any `JVM_PROJECT_MARKERS` file, `build.sbt` / `build.mill` / `build.sc` /
`project.scala` included) makes agent-mode Maven discovery crawl the Maven local repository. Locally, only
`project.scala` included, or a root-relative `project/build.properties` or `.scala-build/`) makes
agent-mode Maven discovery crawl the Maven local repository. Locally, only
an sbt / Mill / scala-cli project (`build.sbt`, `project/build.properties`, `build.mill`, `build.mill.yaml`,
`build.sc`, `project.scala` or `.scala-build/`; a Maven or Gradle build never reads these caches) also crawls,
after it in order and first copy winning the crawl dedup (`--global` always): every **Coursier** cache (`$COURSIER_CACHE`; `-Dcoursier.cache=`
Expand Down
5 changes: 3 additions & 2 deletions crates/socket-patch-cli/src/commands/scan/hosted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1677,7 +1677,7 @@ async fn vendored_takeover(
|| p.starts_with("pkg:maven/")
};
let gradle_jvm_entry = |entry: &socket_patch_core::vendor::VendorEntry| {
entry.ecosystem == "jvm"
entry.ecosystem == socket_patch_core::vendor::jvm::layout::LEDGER_ECOSYSTEM
&& entry.wiring.iter().any(|w| {
w.file.ends_with(".gradle")
|| w.file.ends_with(".gradle.kts")
Expand Down Expand Up @@ -2685,7 +2685,8 @@ fn created_settings_over_existing(
.keys()
.filter(|rel| {
let base = rel.rsplit('/').next().unwrap_or(rel);
(matches!(base, "settings.gradle" | "settings.gradle.kts") || base == SBT_HOSTED_FILE)
(socket_patch_core::vendor::jvm::layout::is_gradle_settings(rel)
|| base == SBT_HOSTED_FILE)
&& !done.files.contains_key(rel.as_str())
})
.find(|rel| std::fs::symlink_metadata(cwd.join(rel)).is_ok())
Expand Down
5 changes: 4 additions & 1 deletion crates/socket-patch-cli/src/commands/scan/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1228,7 +1228,10 @@ async fn gradle_scan(
.unwrap_or_default();
let want_locks = !out.gradle_purls.is_empty();
let Ok((gate, locked, mismatch, env)) = tokio::task::spawn_blocking(move || {
let gradle_build = gradle_cache::has_gradle_marker(&cwd);
let gradle_build = socket_patch_core::vendor::jvm::layout::has_build(
&cwd,
socket_patch_core::vendor::jvm::layout::BuildTool::Gradle,
);
let env = JvmEnv::from_process();
let gate = (!global && gradle_build).then(|| m2_gate(&cwd, &env));
// The cwd's build locks annotate Gradle-cached packages in a global
Expand Down
10 changes: 7 additions & 3 deletions crates/socket-patch-cli/src/commands/scan/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,14 @@ pub(crate) fn dir_markers(dir: &Path) -> Vec<String> {
})
.unwrap_or_default();
if markers.is_empty() {
// No lockfile: the manifests say what the project is.
// No lockfile: the manifests say what the project is. Every name
// here, JVM build files included, must be a regular file: the
// root-marker report lists files, so it deliberately keeps
// `is_file` rather than `layout::marker_present` (which lets a
// directory named like a build file mark a JVM build).
markers = MANIFEST_MARKERS
.iter()
.chain(socket_patch_core::crawlers::jvm_cache::JVM_PROJECT_MARKERS)
.chain(socket_patch_core::vendor::jvm::layout::JVM_PROJECT_MARKERS)
.filter(|name| dir.join(name).is_file())
.map(|name| name.to_string())
.collect();
Expand All @@ -110,7 +114,7 @@ pub(crate) fn dir_markers(dir: &Path) -> Vec<String> {
}

/// Manifests that stand in as markers for a root with no lockfile (plus
/// every JVM build file, `jvm_cache::JVM_PROJECT_MARKERS`).
/// every JVM build file, `layout::JVM_PROJECT_MARKERS`).
const MANIFEST_MARKERS: [&str; 6] = [
"package.json",
"pyproject.toml",
Expand Down
29 changes: 23 additions & 6 deletions crates/socket-patch-cli/src/commands/vendor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,15 +372,15 @@ pub(crate) async fn dispatch_revert_one_opts(
{
return RevertOutcome::failed(vendor::path::vendor_dir_symlink_detail(&link));
}
match entry.ecosystem.as_str() {
match vendor::jvm::layout::ledger_ecosystem(&entry.ecosystem) {
"npm" => vendor::npm_flavor::revert_npm_any_opts(entry, project_root, opts).await,
"pypi" => vendor::pypi::revert_pypi_opts(entry, project_root, opts).await,
"gem" => vendor::gem::revert_gem_opts(entry, project_root, opts).await,
"cargo" => vendor::cargo::revert_cargo_vendor_opts(entry, project_root, opts).await,
"golang" => vendor::golang::revert_go_vendor_opts(entry, project_root, opts).await,
"composer" => vendor::composer_lock::revert_composer_opts(entry, project_root, opts).await,
"nuget" => vendor::nuget_feed::revert_nuget_opts(entry, project_root, opts).await,
"maven" | "jvm" => vendor::maven_repo::revert_maven_opts(entry, project_root, opts).await,
"maven" => vendor::maven_repo::revert_maven_opts(entry, project_root, opts).await,
other => RevertOutcome::failed(format!(
"this build has no vendor backend for ecosystem `{other}`"
)),
Expand Down Expand Up @@ -506,8 +506,11 @@ fn orphan_label(unit: &vendor::path::SweptVendorDir) -> String {
.unwrap_or_else(|| format!("{}/{}", unit.eco, unit.uuid))
}

/// Does `eco` fall inside this run's `--ecosystems` scope?
/// Does `eco` fall inside this run's `--ecosystems` scope? A vendor-ledger
/// name counts as the package ecosystem it stands for (a `jvm` entry is
/// `maven`, [`vendor::jvm::layout::ledger_ecosystem`]).
pub(crate) fn ecosystem_in_scope(common: &GlobalArgs, eco: &str) -> bool {
let eco = vendor::jvm::layout::ledger_ecosystem(eco);
match socket_patch_core::crawlers::Ecosystem::all()
.iter()
.find(|e| e.cli_name() == eco)
Expand Down Expand Up @@ -1126,7 +1129,7 @@ async fn run_check(args: &VendorArgs) -> i32 {
// other ecosystems the ledger records.
let jvm_orphan = (!state.entries.values().any(vendor::jvm::apply::is_jvm_entry))
.then(|| {
vendor::jvm::apply::LEDGER_OWNED_PATHS
vendor::jvm::layout::LEDGER_OWNED_PATHS
.iter()
.copied()
.find(|rel| root.join(rel).exists())
Expand Down Expand Up @@ -1456,7 +1459,7 @@ impl EjectSnapshot {
));
}
planned.extend(
socket_patch_core::vendor::jvm::coursier_tree::CAPTURED_FILES
socket_patch_core::vendor::jvm::layout::CAPTURED_FILES
.iter()
.map(|s| s.to_string()),
);
Expand Down Expand Up @@ -6309,6 +6312,20 @@ mod scope_and_hint_tests {
let golang = with_scope(Some(&["golang"]));
assert!(ecosystem_in_scope(&golang, "golang"));
}

/// A v5 JVM-backend ledger entry is recorded as `jvm`, which is no
/// `--ecosystems` name: `--ecosystems maven` must still scope it in
/// (the GC passes, rollback's vendored leg and repair all filter ledger
/// entries by this), and another ecosystem's scope must leave it out.
#[test]
fn jvm_ledger_entries_are_in_the_maven_scope() {
let maven = with_scope(Some(&["maven"]));
assert!(ecosystem_in_scope(&maven, "jvm"));
assert!(ecosystem_in_scope(&maven, "maven"));
let npm_only = with_scope(Some(&["npm"]));
assert!(!ecosystem_in_scope(&npm_only, "jvm"));
assert!(ecosystem_in_scope(&with_scope(None), "jvm"));
}
}

#[cfg(test)]
Expand Down Expand Up @@ -7019,7 +7036,7 @@ mod eject_snapshot_tests {
#[tokio::test]
async fn snapshot_fails_closed_on_a_fifo() {
let tmp = tempfile::tempdir().unwrap();
let rel = socket_patch_core::vendor::jvm::coursier_tree::CAPTURED_FILES[0];
let rel = socket_patch_core::vendor::jvm::layout::CAPTURED_FILES[0];
let path = tmp.path().join(rel);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
let c = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
Expand Down
5 changes: 3 additions & 2 deletions crates/socket-patch-core/src/crawlers/coursier_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ use std::collections::HashSet;
use std::path::{Path, PathBuf};

use super::jvm_cache::debug_log;
use super::maven_crawler::{is_safe_maven_coordinate, parse_pom_group_artifact_version};
use super::maven_crawler::parse_pom_group_artifact_version;
use crate::utils::fs::read_regular_to_bytes_sync;
use crate::vendor::jvm::layout::is_path_safe;

/// The OS whose default cache locations apply (a parameter so every OS's
/// table is testable on any host).
Expand Down Expand Up @@ -201,7 +202,7 @@ fn pom_root(pom: &Path, host: &Path) -> Option<PathBuf> {
}
let bytes = read_regular_to_bytes_sync(pom).ok()?;
let (g, a, v) = parse_pom_group_artifact_version(&String::from_utf8_lossy(&bytes))?;
if a != artifact || v != version || !is_safe_maven_coordinate(&g, &a, &v) {
if a != artifact || v != version || !is_path_safe(&g, &a, &v) {
return None;
}
let mut root = artifact_dir.parent()?;
Expand Down
36 changes: 12 additions & 24 deletions crates/socket-patch-core/src/crawlers/gradle_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::path::{Path, PathBuf};

use crate::crawlers::jvm_cache::Gav;
use crate::crawlers::maven_crawler::is_safe_maven_coordinate;
use crate::gradle::graph::{self, MavenLocal, ScriptGraph};
use crate::gradle::home::{is_init_script_name, GradleHome};
use crate::gradle::{Env, Os};
use crate::manifest::schema::PatchFileInfo;
use crate::vendor::jvm::layout::{self, is_path_safe, BuildTool};

/// The leaf directory name of a Gradle module cache.
pub const FILES21: &str = "files-2.1";
Expand Down Expand Up @@ -112,7 +112,7 @@ fn is_bookkeeping(name: &str) -> bool {
/// literal directory levels (group, artifact, version), then a hash
/// directory ([`is_hash_dir_name`]) and its regular files. Bookkeeping
/// directories and lock files are skipped, as is any coordinate that
/// [`is_safe_maven_coordinate`] rejects. Sorted (walk order).
/// [`is_path_safe`] rejects. Sorted (walk order).
pub fn walk_files21(root: &Path) -> Vec<Entry> {
let mut out = Vec::new();
for (group, is_dir) in children(root) {
Expand All @@ -126,7 +126,7 @@ pub fn walk_files21(root: &Path) -> Vec<Entry> {
}
let artifact_dir = group_dir.join(&artifact);
for (version, is_dir) in children(&artifact_dir) {
if !is_dir || !is_safe_maven_coordinate(&group, &artifact, &version) {
if !is_dir || !is_path_safe(&group, &artifact, &version) {
continue;
}
let gav: Gav = (group.clone(), artifact.clone(), version.clone());
Expand Down Expand Up @@ -171,7 +171,7 @@ pub fn has_module_file<'a>(entries: impl IntoIterator<Item = &'a Entry>) -> bool
/// [`has_module_file`] for the version directory `root/<g>/<a>/<v>`.
pub fn is_installed(root: &Path, gav: &Gav) -> bool {
let (g, a, v) = gav;
if !is_safe_maven_coordinate(g, a, v) {
if !is_path_safe(g, a, v) {
return false;
}
has_module_file(&version_dir_entries(&root.join(g).join(a).join(v), gav))
Expand Down Expand Up @@ -926,36 +926,24 @@ pub fn init_scripts_for_build(home: &GradleHome, build_root: &Path) -> InitScrip

// ── the build and mavenLocal() ──────────────────────────────────────────

/// Gradle build files (the Gradle half of `jvm_cache::JVM_PROJECT_MARKERS`).
pub const GRADLE_MARKERS: &[&str] = &[
"build.gradle",
"build.gradle.kts",
"settings.gradle",
"settings.gradle.kts",
];

const SETTINGS: &[&str] = &["settings.gradle", "settings.gradle.kts"];

/// Whether `dir` holds a Gradle build or settings script.
pub fn has_gradle_marker(dir: &Path) -> bool {
GRADLE_MARKERS.iter().any(|m| dir.join(m).is_file())
/// Whether `dir` holds a Gradle settings script.
fn has_settings(dir: &Path) -> bool {
layout::GRADLE_SETTINGS_FILES
.iter()
.any(|s| layout::marker_present(dir, s))
}

/// The Gradle build roots to analyse for a cwd that is a Gradle project:
/// the cwd itself and, when it has no settings script, the nearest ancestor
/// that has one (Gradle searches upwards for the settings of a
/// subproject). Empty when the cwd has no Gradle marker.
pub fn build_roots(cwd: &Path) -> Vec<PathBuf> {
if !has_gradle_marker(cwd) {
if !layout::has_build(cwd, BuildTool::Gradle) {
return Vec::new();
}
let mut roots = vec![cwd.to_path_buf()];
if !SETTINGS.iter().any(|s| cwd.join(s).is_file()) {
if let Some(up) = cwd
.ancestors()
.skip(1)
.find(|d| SETTINGS.iter().any(|s| d.join(s).is_file()))
{
if !has_settings(cwd) {
if let Some(up) = cwd.ancestors().skip(1).find(|d| has_settings(d)) {
roots.push(up.to_path_buf());
}
}
Expand Down
9 changes: 5 additions & 4 deletions crates/socket-patch-core/src/crawlers/ivy_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ use std::io::Read as _;
use std::path::{Path, PathBuf};

use super::coursier_cache::{existing_dedup, jvm_option_values, log_source, TargetOs};
use super::maven_crawler::{is_safe_maven_coordinate, parse_pom_group_artifact_version};
use super::maven_crawler::parse_pom_group_artifact_version;
use super::types::CrawledPackage;
use crate::utils::fs::{open_regular_file_sync, read_regular_to_bytes_sync};
use crate::vendor::jvm::layout::is_path_safe;

/// The artifact directories Ivy files a module's jar under, in lookup order
/// (`bundles/` holds OSGi-packaged jars such as guava 19.0's).
Expand Down Expand Up @@ -120,7 +121,7 @@ pub fn find_by_purls(root: &Path, purls: &[String]) -> HashMap<String, CrawledPa
};
// SECURITY: untrusted coordinates are joined onto the cache root and
// the result is patched in place.
if !is_safe_maven_coordinate(&g, &a, &v) {
if !is_path_safe(&g, &a, &v) {
continue;
}
let found = org_roots.iter().find_map(|org_root| {
Expand Down Expand Up @@ -168,7 +169,7 @@ pub fn type_dirs(module_dir: &Path) -> Vec<String> {
/// An `.original` from an Ivy-pattern origin is an `ivy.xml`, not a pom,
/// and is never returned.
pub fn installed_pom(installed_dir: &Path, g: &str, a: &str, v: &str) -> Option<Vec<u8>> {
if !is_safe_maven_coordinate(g, a, v) {
if !is_path_safe(g, a, v) {
return None;
}
if let Ok(bytes) = read_regular_to_bytes_sync(&installed_dir.join(format!("{a}-{v}.pom"))) {
Expand Down Expand Up @@ -212,7 +213,7 @@ fn org_roots(root: &Path) -> Vec<PathBuf> {
/// coordinates, no regular `ivy-<rev>.xml`, an `<info>` naming other
/// coordinates, or no jar.
fn package(module_dir: &Path, org: &str, module: &str, rev: &str) -> Option<CrawledPackage> {
if !is_safe_maven_coordinate(org, module, rev) {
if !is_path_safe(org, module, rev) {
return None;
}
// Neither the module nor the organisation directory may be a link out
Expand Down
Loading
Loading