| @@ -1,4 +1,4 @@ |
| -use std::path::PathBuf; |
| +use std::path::{Path, PathBuf}; |
| use std::process::Stdio; |
| |
| use crate::builders::{ArtifactPath, Builder, LogEvent, LogSender}; |
| @@ -125,11 +125,13 @@ impl Builder for ScriptBuilder { |
| |
| let mut matched = false; |
| |
| - // Walk the directory to find matching paths |
| - let parent = pattern |
| - .parent() |
| - .unwrap_or_else(|| std::path::Path::new(".")); |
| - for entry in walkdir::WalkDir::new(parent) |
| + // Walk the directory to find matching paths. |
| + // Extract the longest non-glob prefix to use as the walk root, |
| + // so patterns like `target/**/*.txt` work correctly (the naive |
| + // `.parent()` on `target/**/*.txt` yields `target/**` which is |
| + // not a real directory). |
| + let walk_root = glob_walk_root(&pattern); |
| + for entry in walkdir::WalkDir::new(walk_root) |
| .into_iter() |
| .filter_map(|e| e.ok()) |
| { |
| @@ -184,3 +186,70 @@ impl Builder for ScriptBuilder { |
| Ok(artifacts) |
| } |
| } |
| + |
| +/// Given a glob pattern, walk up the path tree until we find a component |
| +/// that contains no glob metacharacters (`*`, `?`, `[`). The result is |
| +/// a concrete directory (or `.`) suitable as a `WalkDir` root. |
| +/// |
| +/// For example: |
| +/// - `target/**/*.txt` → `target/` |
| +/// - `target/release/foo-*` → `target/release/` |
| +/// - `*.tar.gz` → `.` |
| +fn glob_walk_root(pattern: &Path) -> &Path { |
| + let mut current = pattern; |
| + loop { |
| + let s = current.to_string_lossy(); |
| + if s.is_empty() { |
| + return Path::new("."); |
| + } |
| + if !s.contains('*') && !s.contains('?') && !s.contains('[') { |
| + return current; |
| + } |
| + match current.parent() { |
| + Some(parent) => current = parent, |
| + None => return Path::new("."), |
| + } |
| + } |
| +} |
| + |
| +#[cfg(test)] |
| +mod tests { |
| + use super::*; |
| + |
| + #[test] |
| + fn test_glob_walk_root_simple() { |
| + assert_eq!( |
| + glob_walk_root(Path::new("target/**/*.txt")), |
| + Path::new("target") |
| + ); |
| + } |
| + |
| + #[test] |
| + fn test_glob_walk_root_nested() { |
| + assert_eq!( |
| + glob_walk_root(Path::new("target/release/foo-*")), |
| + Path::new("target/release") |
| + ); |
| + } |
| + |
| + #[test] |
| + fn test_glob_walk_root_no_glob() { |
| + assert_eq!( |
| + glob_walk_root(Path::new("README.md")), |
| + Path::new("README.md") |
| + ); |
| + } |
| + |
| + #[test] |
| + fn test_glob_walk_root_root_glob() { |
| + assert_eq!(glob_walk_root(Path::new("*.tar.gz")), Path::new(".")); |
| + } |
| + |
| + #[test] |
| + fn test_glob_walk_root_deep_glob() { |
| + assert_eq!( |
| + glob_walk_root(Path::new("foo/bar/*/baz/**/*.txt")), |
| + Path::new("foo/bar") |
| + ); |
| + } |
| +} |