Skip to content

Commit 0a4530b

Browse files
committed
feat(metadata): analyze and test the examples directory only for the root project
1 parent 162873e commit 0a4530b

9 files changed

Lines changed: 393 additions & 12 deletions

File tree

crates/metadata/src/lockfile.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,13 @@ impl Lockfile {
282282
for lock in locks {
283283
let metadata = self.get_metadata(&lock.source)?;
284284
let path = metadata.project_path();
285+
// Analyzed only for the build root (`Metadata::paths`).
286+
let examples = path.join("examples");
285287

286288
for src in &veryl_path::gather_files_with_extension(&path, "veryl", false)? {
289+
if src.starts_with(&examples) {
290+
continue;
291+
}
287292
let Ok(rel) = src.strip_prefix(&path) else {
288293
return Err(MetadataError::InvalidSourceLocation(src.clone()));
289294
};
@@ -297,6 +302,7 @@ impl Lockfile {
297302
src: src.to_path_buf(),
298303
dst,
299304
map,
305+
example: false,
300306
});
301307
}
302308
}

crates/metadata/src/metadata.rs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -339,15 +339,33 @@ impl Metadata {
339339
};
340340
let mut explicit_routed = canonical_files.as_ref().map(|v| vec![false; v.len()]);
341341

342-
for source in &sources {
343-
let src_base = base.join(source);
342+
// `examples/` is reserved; dependency source collection
343+
// (`Lockfile::paths`) skips it entirely.
344+
let examples_base = base.join("examples");
345+
if let Some(source) = sources
346+
.iter()
347+
.find(|x| base.join(x).starts_with(&examples_base))
348+
{
349+
return Err(MetadataError::ReservedSourceDir(base.join(source)));
350+
}
344351

352+
let mut source_dirs: Vec<(PathBuf, bool)> =
353+
sources.iter().map(|x| (base.join(x), false)).collect();
354+
if examples_base.exists() {
355+
source_dirs.push((examples_base.clone(), true));
356+
}
357+
358+
for (src_base, is_example) in source_dirs {
345359
let src_files = if let Some(cf) = canonical_files.as_ref() {
346360
// Only keep files that live under this source dir; other
347-
// source dirs in `sources` will pick them up.
361+
// source dirs in `sources` will pick them up. Files under
362+
// `examples/` belong to the examples dir only, even when a
363+
// source dir contains it.
348364
let mut ret = Vec::new();
349365
for (i, path) in cf.iter().enumerate() {
350-
if path.starts_with(&src_base) {
366+
if path.starts_with(&src_base)
367+
&& (is_example || !path.starts_with(&examples_base))
368+
{
351369
ret.push(path.clone());
352370
if let Some(ref mut flags) = explicit_routed {
353371
flags[i] = true;
@@ -356,7 +374,12 @@ impl Metadata {
356374
}
357375
ret
358376
} else {
359-
veryl_path::gather_files_with_extension(&src_base, "veryl", symlink)?
377+
let mut files =
378+
veryl_path::gather_files_with_extension(&src_base, "veryl", symlink)?;
379+
if !is_example {
380+
files.retain(|x| !x.starts_with(&examples_base));
381+
}
382+
files
360383
};
361384

362385
for src in src_files {
@@ -400,6 +423,7 @@ impl Metadata {
400423
src: src.to_path_buf(),
401424
dst,
402425
map,
426+
example: is_example,
403427
});
404428
}
405429
}

crates/metadata/src/metadata_error.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ pub enum MetadataError {
3131
#[error("source file \"{0}\" is outside the project")]
3232
InvalidSourceLocation(PathBuf),
3333

34+
#[diagnostic(
35+
code(MetadataError::ReservedSourceDir),
36+
help("remove it from [build] sources")
37+
)]
38+
#[error(
39+
"\"{0}\" cannot be used as a source directory: `examples/` is reserved and analyzed automatically for the root project"
40+
)]
41+
ReservedSourceDir(PathBuf),
42+
3443
#[diagnostic(code(MetadataError::Git), help(""))]
3544
#[error("git operation failure: {0}")]
3645
Git(Box<dyn std::error::Error + Sync + Send>),

crates/metadata/src/tests.rs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,3 +746,138 @@ fn lockfile_save_is_atomic() {
746746
r.join().unwrap();
747747
}
748748
}
749+
750+
const EXAMPLES_TOML: &str = r#"
751+
[project]
752+
name = "test"
753+
version = "0.1.0"
754+
755+
[build]
756+
sources = ["src"]
757+
target = {type = "directory", path = "target"}
758+
"#;
759+
760+
fn create_metadata_with_examples() -> (Metadata, TempDir) {
761+
let tempdir = tempfile::tempdir().unwrap();
762+
let metadata = create_project(tempdir.path(), "test", EXAMPLES_TOML, false);
763+
let project_path = metadata.project_path();
764+
fs::create_dir_all(project_path.join("src")).unwrap();
765+
fs::write(project_path.join("src/a.veryl"), "module A {}\n").unwrap();
766+
fs::create_dir_all(project_path.join("examples")).unwrap();
767+
fs::write(project_path.join("examples/ex.veryl"), "module Ex {}\n").unwrap();
768+
769+
(metadata, tempdir)
770+
}
771+
772+
#[test]
773+
fn paths_include_root_examples() {
774+
let (mut metadata, _tempdir) = create_metadata_with_examples();
775+
776+
let paths = metadata.paths::<&str>(&[], false, false).unwrap();
777+
assert_eq!(paths.len(), 2);
778+
779+
let src = paths.iter().find(|x| x.src.ends_with("a.veryl")).unwrap();
780+
let example = paths.iter().find(|x| x.src.ends_with("ex.veryl")).unwrap();
781+
assert!(!src.example);
782+
assert!(example.example);
783+
}
784+
785+
#[test]
786+
fn paths_include_examples_with_default_sources() {
787+
// The default `sources = [""]` walks the whole project.
788+
let toml = r#"
789+
[project]
790+
name = "test"
791+
version = "0.1.0"
792+
793+
[build]
794+
target = {type = "directory", path = "target"}
795+
"#;
796+
let tempdir = tempfile::tempdir().unwrap();
797+
let mut metadata = create_project(tempdir.path(), "test", toml, false);
798+
let project_path = metadata.project_path();
799+
fs::create_dir_all(project_path.join("src")).unwrap();
800+
fs::write(project_path.join("src/a.veryl"), "module A {}\n").unwrap();
801+
fs::create_dir_all(project_path.join("examples")).unwrap();
802+
fs::write(project_path.join("examples/ex.veryl"), "module Ex {}\n").unwrap();
803+
804+
let paths = metadata.paths::<&str>(&[], false, false).unwrap();
805+
assert_eq!(paths.len(), 2);
806+
807+
let src = paths.iter().find(|x| x.src.ends_with("a.veryl")).unwrap();
808+
let example = paths.iter().find(|x| x.src.ends_with("ex.veryl")).unwrap();
809+
assert!(!src.example);
810+
assert!(example.example);
811+
}
812+
813+
#[test]
814+
fn sources_under_examples_are_rejected() {
815+
for source in ["examples", "examples/sub"] {
816+
let toml = format!(
817+
r#"
818+
[project]
819+
name = "test"
820+
version = "0.1.0"
821+
822+
[build]
823+
sources = ["src", "{source}"]
824+
target = {{type = "directory", path = "target"}}
825+
"#
826+
);
827+
let tempdir = tempfile::tempdir().unwrap();
828+
let mut metadata = create_project(tempdir.path(), "test", &toml, false);
829+
let project_path = metadata.project_path();
830+
fs::create_dir_all(project_path.join("src")).unwrap();
831+
fs::write(project_path.join("src/a.veryl"), "module A {}\n").unwrap();
832+
fs::create_dir_all(project_path.join("examples/sub")).unwrap();
833+
fs::write(project_path.join("examples/ex.veryl"), "module Ex {}\n").unwrap();
834+
835+
let result = metadata.paths::<&str>(&[], false, false);
836+
assert!(matches!(result, Err(MetadataError::ReservedSourceDir(_))));
837+
}
838+
}
839+
840+
#[test]
841+
fn paths_route_explicit_example_file() {
842+
let (mut metadata, _tempdir) = create_metadata_with_examples();
843+
let file = metadata.project_path().join("examples/ex.veryl");
844+
845+
let paths = metadata.paths(&[file], false, false).unwrap();
846+
assert_eq!(paths.len(), 1);
847+
assert!(paths[0].example);
848+
}
849+
850+
#[test]
851+
fn lockfile_paths_exclude_dependency_examples() {
852+
let tempdir = tempfile::tempdir().unwrap();
853+
854+
let dep_path = tempdir.path().join("dep");
855+
fs::create_dir_all(dep_path.join("src")).unwrap();
856+
fs::create_dir_all(dep_path.join("examples")).unwrap();
857+
fs::write(
858+
dep_path.join("Veryl.toml"),
859+
r#"
860+
[project]
861+
name = "dep"
862+
version = "0.1.0"
863+
"#,
864+
)
865+
.unwrap();
866+
fs::write(dep_path.join("src/a.veryl"), "module A {}\n").unwrap();
867+
fs::write(dep_path.join("examples/ex.veryl"), "module Ex {}\n").unwrap();
868+
869+
let main_toml = r#"
870+
[project]
871+
name = "main"
872+
version = "0.1.0"
873+
874+
[dependencies]
875+
dep = {path = "../dep"}
876+
"#;
877+
let metadata = create_project(tempdir.path(), "main", main_toml, false);
878+
879+
let lockfile = Lockfile::new(&metadata).unwrap();
880+
let paths = lockfile.paths(Path::new("target")).unwrap();
881+
assert!(paths.iter().any(|x| x.src.ends_with("a.veryl")));
882+
assert!(!paths.iter().any(|x| x.src.ends_with("ex.veryl")));
883+
}

crates/path/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ pub struct PathSet {
1414
pub src: PathBuf,
1515
pub dst: PathBuf,
1616
pub map: PathBuf,
17+
/// Analyzed like sources but excluded from emit and filelist.
18+
pub example: bool,
1719
}
1820

1921
pub fn cache_path() -> PathBuf {

crates/std/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ pub fn paths(base_dst: &Path) -> Result<Vec<PathSet>, PathError> {
5656
src: src.to_path_buf(),
5757
dst,
5858
map,
59+
example: false,
5960
});
6061
}
6162

0 commit comments

Comments
 (0)