new: template manifests and registry infrastructure

Split the Template trait into a data half and a logic half. Every
template is now declared by a manifest under data/templates/<id>/
(CLI id, wizard label, detection markers, Build-Depends, architecture,
rules dh line, rules-extra body, control source fields, gitignore
entries and static file bodies with {placeholder} substitution),
embedded through the TEMPLATE_SOURCES index and parsed once into the
registry; the order of the index is the wizard menu order and the
detection priority at once. The logic half is the slim TemplateHooks
trait (probe, post_write, file-body overrides merged over the manifest
bodies by path shadowing, Build-Depends/architecture amendments and
extra context values), registered per template as a HOOKS static: a
template without hooks needs zero Rust.

- TemplateId becomes a Copy wrapper of the stable CLI string; the
  enum, its all/as_str/display_name/from_label matches and the old
  statics array collapse into the registry accessors.
- rust's rules overrides move to data/templates/rust/rules.extra.tpl
  with {locked}/{artifact} hook context; python's backend table,
  meson/cmake's pkg-config opt-in, autotools' gettext and python's
  C-extension hints become hook amendments over the manifest baseline.
- detect.rs drops its hardcoded marker cascade: the manifests'
  detect.files drive detection in registry order, with the shell
  single-script heuristic and the never-detected empty template kept
  as the code special cases they are. License sniffing is untouched.
- The template tests port to manifest validation: registry coverage
  and stable order, placeholder presence in the rendering context,
  rules composition, the Build-Depends/architecture/dh-line table now
  asserted against the manifest data, and the hook shadowing merge.

The static skeleton bodies of the shell/empty/makefile/go templates
stay in their Rust hooks for now; the next commit moves them into
their manifests.
This commit is contained in:
2026-09-18 14:46:28 +02:00
parent fc0d2f247e
commit 04a572cd77
26 changed files with 1477 additions and 716 deletions
+19 -19
View File
@@ -181,7 +181,7 @@ fn scaffold_steps(opts: &NewOptions, pb: &ProgressBar) -> Result<ScaffoldOutcome
// mode, plus the template's own entries (rust: the vendored layout) in
// every mode — missing ones appended, an existing file never
// overwritten.
let template_gitignore = template.gitignore_entries(opts);
let template_gitignore = template.gitignore_entries();
let mut entries: Vec<&str> = Vec::new();
let mut header = None;
if skeleton {
@@ -216,7 +216,7 @@ fn scaffold_steps(opts: &NewOptions, pb: &ProgressBar) -> Result<ScaffoldOutcome
if opts.source_format == SourceFormat::Quilt {
pb.set_message("Creating orig tarball");
let vendored_rust =
template.id() == options::TemplateId::Rust && orig::has_vendored_dir(&target);
template.id() == options::TemplateId::RUST && orig::has_vendored_dir(&target);
let created = orig::create_orig(
&target,
&opts.name,
@@ -349,7 +349,7 @@ mod tests {
let dir = tempdir().unwrap();
scaffold_in(
dir.path(),
opts(TemplateId::Shell, "mytool", SourceDir::Skeleton),
opts(TemplateId::SHELL, "mytool", SourceDir::Skeleton),
)
.unwrap();
@@ -423,7 +423,7 @@ mod tests {
#[serial]
fn scaffold_skeleton_forced_quilt_snapshots_the_tree() {
let dir = tempdir().unwrap();
let mut o = opts(TemplateId::Shell, "mytool", SourceDir::Skeleton);
let mut o = opts(TemplateId::SHELL, "mytool", SourceDir::Skeleton);
o.source_format = SourceFormat::Quilt;
o.orig = Some(OrigOrigin::Snapshot);
scaffold_in(dir.path(), o).unwrap();
@@ -471,7 +471,7 @@ mod tests {
fn scaffold_empty_base_and_metapackage_flavors() {
let dir = tempdir().unwrap();
// Metapackage flavor: non-empty depends.
let mut o = opts(TemplateId::Empty, "metapkg", SourceDir::Skeleton);
let mut o = opts(TemplateId::EMPTY, "metapkg", SourceDir::Skeleton);
o.depends = vec!["hello".into(), "hello-data (>= 1.0)".into()];
scaffold_in(dir.path(), o).unwrap();
@@ -493,7 +493,7 @@ mod tests {
let dir = tempdir().unwrap();
scaffold_in(
dir.path(),
opts(TemplateId::Empty, "basepkg", SourceDir::Skeleton),
opts(TemplateId::EMPTY, "basepkg", SourceDir::Skeleton),
)
.unwrap();
let control =
@@ -505,7 +505,7 @@ mod tests {
#[serial]
fn scaffold_release_targets_series() {
let dir = tempdir().unwrap();
let mut o = opts(TemplateId::Empty, "released", SourceDir::Skeleton);
let mut o = opts(TemplateId::EMPTY, "released", SourceDir::Skeleton);
o.release = true;
scaffold_in(dir.path(), o).unwrap();
@@ -525,7 +525,7 @@ mod tests {
let tree = dir.path().join("packdir");
std::fs::create_dir_all(&tree).unwrap();
std::fs::write(tree.join("run.sh"), "#!/bin/sh\necho hi\n").unwrap();
scaffold_in(&tree, opts(TemplateId::Shell, "runtool", SourceDir::Here)).unwrap();
scaffold_in(&tree, opts(TemplateId::SHELL, "runtool", SourceDir::Here)).unwrap();
// debian/ lands directly in the directory; no skeleton file, no
// root .gitignore (the shell template contributes none and the
@@ -574,7 +574,7 @@ mod tests {
std::fs::write(tree.join("debian/control"), "Source: mytool\n").unwrap();
let err = scaffold_in(
dir.path(),
opts(TemplateId::Shell, "mytool", SourceDir::Skeleton),
opts(TemplateId::SHELL, "mytool", SourceDir::Skeleton),
)
.unwrap_err();
assert!(err.to_string().contains("debian/control"), "{err}");
@@ -585,7 +585,7 @@ mod tests {
std::fs::write(dir.path().join("mytool/junk"), "x").unwrap();
let err = scaffold_in(
dir.path(),
opts(TemplateId::Shell, "mytool", SourceDir::Skeleton),
opts(TemplateId::SHELL, "mytool", SourceDir::Skeleton),
)
.unwrap_err();
assert!(err.to_string().contains("not empty"), "{err}");
@@ -593,7 +593,7 @@ mod tests {
// Existing orig tarball (quilt only): nothing gets written.
let dir = tempdir().unwrap();
std::fs::write(dir.path().join("mytool_0.1.0.orig.tar.xz"), b"old").unwrap();
let mut o = opts(TemplateId::Shell, "mytool", SourceDir::Skeleton);
let mut o = opts(TemplateId::SHELL, "mytool", SourceDir::Skeleton);
o.source_format = SourceFormat::Quilt;
o.orig = Some(OrigOrigin::Snapshot);
let err = scaffold_in(dir.path(), o).unwrap_err();
@@ -605,7 +605,7 @@ mod tests {
let err = scaffold_in(
dir.path(),
opts(
TemplateId::Shell,
TemplateId::SHELL,
"mytool",
SourceDir::Path(dir.path().join("missing")),
),
@@ -622,7 +622,7 @@ mod tests {
let dir = tempdir().unwrap();
scaffold_in(
dir.path(),
opts(TemplateId::Shell, "nativepkg", SourceDir::Skeleton),
opts(TemplateId::SHELL, "nativepkg", SourceDir::Skeleton),
)
.unwrap();
@@ -649,7 +649,7 @@ mod tests {
let dir = tempdir().unwrap();
let outcome = scaffold_in(
dir.path(),
opts(TemplateId::Rust, "mytool", SourceDir::Skeleton),
opts(TemplateId::RUST, "mytool", SourceDir::Skeleton),
)
.unwrap();
@@ -711,7 +711,7 @@ mod tests {
let tree = dir.path().join("packdir");
std::fs::create_dir_all(&tree).unwrap();
std::fs::write(tree.join(".gitignore"), "# my project\n*.log\n").unwrap();
scaffold_in(&tree, opts(TemplateId::Rust, "mytool", SourceDir::Here)).unwrap();
scaffold_in(&tree, opts(TemplateId::RUST, "mytool", SourceDir::Here)).unwrap();
let gitignore = std::fs::read_to_string(tree.join(".gitignore")).unwrap();
assert!(
@@ -753,7 +753,7 @@ mod tests {
.unwrap();
std::fs::write(source.join("src/main.rs"), "fn main() {}\n").unwrap();
let mut o = opts(TemplateId::Rust, "mytool", SourceDir::Path(source.clone()));
let mut o = opts(TemplateId::RUST, "mytool", SourceDir::Path(source.clone()));
o.source_format = SourceFormat::Quilt;
o.orig = Some(OrigOrigin::Snapshot);
let outcome = scaffold_in(dir.path(), o).unwrap();
@@ -848,7 +848,7 @@ mod tests {
let dir = tempdir().unwrap();
scaffold_in(
dir.path(),
opts(TemplateId::Python, "mytool", SourceDir::Skeleton),
opts(TemplateId::PYTHON, "mytool", SourceDir::Skeleton),
)
.unwrap();
@@ -880,7 +880,7 @@ mod tests {
let tree = dir.path().join("mytool");
std::fs::create_dir_all(&tree).unwrap();
std::fs::write(tree.join("run.sh"), "#!/bin/sh\necho hi\n").unwrap();
scaffold_in(&tree, opts(TemplateId::Shell, "mytool", SourceDir::Here)).unwrap();
scaffold_in(&tree, opts(TemplateId::SHELL, "mytool", SourceDir::Here)).unwrap();
let output = crate::build::run_source_build(
&dir.path().join("mytool"),
@@ -909,7 +909,7 @@ mod tests {
let dir = tempdir().unwrap();
scaffold_in(
dir.path(),
opts(TemplateId::Shell, "mytool", SourceDir::Skeleton),
opts(TemplateId::SHELL, "mytool", SourceDir::Skeleton),
)
.unwrap();