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:
@@ -0,0 +1,19 @@
|
||||
## The `autotools` template: a C project with a configure.ac built through
|
||||
## debhelper's auto-detection (dh runs autoreconf itself when it finds
|
||||
## configure.ac, debhelper >= 10 — no override needed). The logic half —
|
||||
## the AC_INIT probe, the skeleton bodies and the gettext detection
|
||||
## (appended to Build-Depends) — lives in src/new/templates/autotools.rs.
|
||||
##
|
||||
## Schema: see src/new/templates/mod.rs.
|
||||
|
||||
id: autotools
|
||||
label: C/C++ (Autotools)
|
||||
detect:
|
||||
files: [configure.ac]
|
||||
build_depends:
|
||||
- autoconf
|
||||
- automake
|
||||
- libtool
|
||||
architecture: any
|
||||
rules_dh_line: "dh $@"
|
||||
files: []
|
||||
@@ -0,0 +1,16 @@
|
||||
## The `cmake` template: a C/C++ project built with CMake through the
|
||||
## debhelper cmake buildsystem. The logic half — the project() probe, the
|
||||
## skeleton bodies and the wizard's pkg-config opt-in (appended to
|
||||
## Build-Depends) — lives in src/new/templates/cmake.rs.
|
||||
##
|
||||
## Schema: see src/new/templates/mod.rs.
|
||||
|
||||
id: cmake
|
||||
label: C/C++ (CMake)
|
||||
detect:
|
||||
files: [CMakeLists.txt]
|
||||
build_depends:
|
||||
- cmake
|
||||
architecture: any
|
||||
rules_dh_line: "dh $@ --buildsystem=cmake"
|
||||
files: []
|
||||
@@ -0,0 +1,14 @@
|
||||
## The `empty` template: a metapackage (non-empty Depends list) or an
|
||||
## empty base package with no build system at all — pure `dh $@` plumbing
|
||||
## as a starting point for hand-written rules.
|
||||
##
|
||||
## Schema: see src/new/templates/mod.rs.
|
||||
|
||||
id: empty
|
||||
label: Metapackage / empty base (no build system)
|
||||
detect:
|
||||
files: []
|
||||
build_depends: []
|
||||
architecture: all
|
||||
rules_dh_line: "dh $@"
|
||||
files: []
|
||||
@@ -0,0 +1,18 @@
|
||||
## The `go` template: a Go module built through dh-golang. The logic half
|
||||
## — the go.mod module-line probe and the `{go_import_path}` value below —
|
||||
## lives in src/new/templates/go.rs.
|
||||
##
|
||||
## Schema: see src/new/templates/mod.rs.
|
||||
|
||||
id: go
|
||||
label: Go module
|
||||
detect:
|
||||
files: [go.mod]
|
||||
build_depends:
|
||||
- golang-any
|
||||
- dh-golang
|
||||
architecture: any
|
||||
rules_dh_line: "dh $@ --buildsystem=golang"
|
||||
source_fields:
|
||||
XS-Go-Import-Path: "{go_import_path}"
|
||||
files: []
|
||||
@@ -0,0 +1,17 @@
|
||||
## The `makefile` template: a generic project driven by a plain Makefile.
|
||||
## debhelper's makefile buildsystem runs `make` for the build and
|
||||
## `make install DESTDIR=...` when the Makefile carries an `install:`
|
||||
## target (missing targets are skipped gracefully), so plain `dh $@`
|
||||
## plumbing is enough here.
|
||||
##
|
||||
## Schema: see src/new/templates/mod.rs.
|
||||
|
||||
id: makefile
|
||||
label: Generic (Makefile)
|
||||
detect:
|
||||
files: [Makefile]
|
||||
build_depends:
|
||||
- build-essential
|
||||
architecture: any
|
||||
rules_dh_line: "dh $@"
|
||||
files: []
|
||||
@@ -0,0 +1,16 @@
|
||||
## The `meson` template: a C/C++ project built with Meson through the
|
||||
## debhelper meson buildsystem. The logic half — the project() probe, the
|
||||
## skeleton bodies and the wizard's pkg-config opt-in (appended to
|
||||
## Build-Depends) — lives in src/new/templates/meson.rs.
|
||||
##
|
||||
## Schema: see src/new/templates/mod.rs.
|
||||
|
||||
id: meson
|
||||
label: C/C++ (Meson)
|
||||
detect:
|
||||
files: [meson.build]
|
||||
build_depends:
|
||||
- meson
|
||||
architecture: any
|
||||
rules_dh_line: "dh $@ --buildsystem=meson"
|
||||
files: []
|
||||
@@ -0,0 +1,21 @@
|
||||
## The `python` template: a PEP 517 project built with pybuild. The logic
|
||||
## half — the pyproject.toml/setup.py probe, the skeleton bodies and the
|
||||
## Build-Depends/architecture resolution for existing projects (backend
|
||||
## package, pyproject presence, C-extension hints) — lives in
|
||||
## src/new/templates/python.rs; the lists below are the fresh-skeleton
|
||||
## baseline it starts from.
|
||||
##
|
||||
## Schema: see src/new/templates/mod.rs.
|
||||
|
||||
id: python
|
||||
label: Python (pyproject.toml / setup.py)
|
||||
detect:
|
||||
files: [pyproject.toml, setup.py, setup.cfg]
|
||||
build_depends:
|
||||
- dh-python
|
||||
- python3-all
|
||||
- pybuild-plugin-pyproject
|
||||
- python3-setuptools
|
||||
architecture: all
|
||||
rules_dh_line: "dh $@ --with python3 --buildsystem=pybuild"
|
||||
files: []
|
||||
@@ -0,0 +1,26 @@
|
||||
## The `rust` template: a vendored Cargo build (see the module docs of
|
||||
## src/new/templates/rust.rs for the vendoring strategy). The logic half —
|
||||
## the cargo vendor post-write hook, the Cargo.toml/src skeletons with
|
||||
## their crate-name sanitizing, and the `{locked}` / `{artifact}` values of
|
||||
## rules.extra.tpl — lives in that module.
|
||||
##
|
||||
## Schema: see src/new/templates/mod.rs.
|
||||
|
||||
id: rust
|
||||
label: Rust (Cargo.toml)
|
||||
detect:
|
||||
files: [Cargo.toml]
|
||||
build_depends:
|
||||
- cargo:native
|
||||
- rustc:native
|
||||
architecture: any
|
||||
rules_dh_line: "dh $@"
|
||||
# The vendored-build overrides appended to debian/rules; `--locked` is only
|
||||
# used when the packaged tree already carries a Cargo.lock (the vendoring
|
||||
# hook patches it in once it creates the lockfile), and the built artifact
|
||||
# of a fresh skeleton is named after its crate.
|
||||
rules_extra_file: rules.extra.tpl
|
||||
gitignore_entries:
|
||||
- vendor/
|
||||
- .cargo/config.toml
|
||||
files: []
|
||||
@@ -0,0 +1,19 @@
|
||||
override_dh_auto_build:
|
||||
cargo build --release --offline{locked}
|
||||
|
||||
override_dh_auto_install:
|
||||
install -Dm755 target/release/{artifact} debian/{name}/usr/bin/{command}
|
||||
|
||||
override_dh_auto_test:
|
||||
cargo test --release --offline{locked}
|
||||
|
||||
override_dh_update_autotools_config:
|
||||
|
||||
override_dh_clean:
|
||||
# dh_clean unlinks `*.orig` patch backups, but vendored crates
|
||||
# ship files like `Cargo.toml.orig` that cargo's per-file
|
||||
# checksums require on cold builds (chroots, Launchpad).
|
||||
dh_clean -X .orig
|
||||
|
||||
override_dh_auto_clean:
|
||||
cargo clean
|
||||
@@ -0,0 +1,16 @@
|
||||
## The `shell` template: a single interpreted script installed to
|
||||
## /usr/bin with plain `dh $@` plumbing. Detection is not marker-based: the
|
||||
## single-script heuristic of src/new/detect.rs (a lone *.sh or shebang
|
||||
## file) maps here. The probe pre-filling the wizard answers from the
|
||||
## script file name lives in src/new/templates/shell.rs.
|
||||
##
|
||||
## Schema: see src/new/templates/mod.rs.
|
||||
|
||||
id: shell
|
||||
label: Shell script / single interpreted file
|
||||
detect:
|
||||
files: []
|
||||
build_depends: []
|
||||
architecture: all
|
||||
rules_dh_line: "dh $@"
|
||||
files: []
|
||||
+11
-11
@@ -51,7 +51,7 @@ pub fn orig_tarball_path(
|
||||
}
|
||||
|
||||
/// Render every common `debian/` file of the package.
|
||||
pub fn files(opts: &NewOptions, template: &dyn Template) -> Vec<OutputFile> {
|
||||
pub fn files(opts: &NewOptions, template: &Template) -> Vec<OutputFile> {
|
||||
let mut files = vec![
|
||||
source_format(opts),
|
||||
changelog(opts),
|
||||
@@ -195,7 +195,7 @@ fn render_continuation_text(text: &str) -> String {
|
||||
/// comes from the template (`all` for shell/empty), and a non-empty
|
||||
/// `opts.depends` (the empty/metapackage flavor) lands in the binary
|
||||
/// stanza's `Depends` field.
|
||||
fn control(opts: &NewOptions, template: &dyn Template) -> OutputFile {
|
||||
fn control(opts: &NewOptions, template: &Template) -> OutputFile {
|
||||
let mut control = String::new();
|
||||
|
||||
// Source stanza.
|
||||
@@ -238,7 +238,7 @@ fn control(opts: &NewOptions, template: &dyn Template) -> OutputFile {
|
||||
/// `debian/rules`: the shebang and `%:` target whose recipe is the
|
||||
/// template's dh line (plus the template's extra overrides, when any),
|
||||
/// written with the executable bit.
|
||||
fn rules(opts: &NewOptions, template: &dyn Template) -> OutputFile {
|
||||
fn rules(opts: &NewOptions, template: &Template) -> OutputFile {
|
||||
let mut contents = format!("#!/usr/bin/make -f\n%:\n\t{}\n", template.rules_dh_line());
|
||||
let extra = template.rules_extra(opts);
|
||||
if !extra.is_empty() {
|
||||
@@ -533,7 +533,7 @@ mod tests {
|
||||
fn opts() -> NewOptions {
|
||||
NewOptions {
|
||||
name: "mytool".into(),
|
||||
template: TemplateId::Shell,
|
||||
template: TemplateId::SHELL,
|
||||
source_dir: SourceDir::Skeleton,
|
||||
upstream_version: "0.1.0".into(),
|
||||
revision: 1,
|
||||
@@ -559,7 +559,7 @@ mod tests {
|
||||
#[test]
|
||||
fn source_format_and_local_options() {
|
||||
let o = opts();
|
||||
let files = super::files(&o, crate::new::templates::get(TemplateId::Shell).unwrap());
|
||||
let files = super::files(&o, crate::new::templates::get(TemplateId::SHELL).unwrap());
|
||||
let find = |path: &str| {
|
||||
files
|
||||
.iter()
|
||||
@@ -582,7 +582,7 @@ mod tests {
|
||||
};
|
||||
let files = super::files(
|
||||
&native,
|
||||
crate::new::templates::get(TemplateId::Shell).unwrap(),
|
||||
crate::new::templates::get(TemplateId::SHELL).unwrap(),
|
||||
);
|
||||
assert!(
|
||||
files
|
||||
@@ -642,7 +642,7 @@ mod tests {
|
||||
#[test]
|
||||
fn control_rendering_and_parse() {
|
||||
let o = opts();
|
||||
let control = super::control(&o, crate::new::templates::get(TemplateId::Shell).unwrap());
|
||||
let control = super::control(&o, crate::new::templates::get(TemplateId::SHELL).unwrap());
|
||||
|
||||
// RFC822 continuation: first dep on the field line, the rest indented.
|
||||
assert!(
|
||||
@@ -679,7 +679,7 @@ mod tests {
|
||||
homepage: None,
|
||||
..opts()
|
||||
};
|
||||
let control = super::control(&o, crate::new::templates::get(TemplateId::Shell).unwrap());
|
||||
let control = super::control(&o, crate::new::templates::get(TemplateId::SHELL).unwrap());
|
||||
assert!(!control.contents.contains("Homepage:"));
|
||||
let parsed = crate::debian::ControlInfo::parse_content(&control.contents).unwrap();
|
||||
assert!(parsed.source.get("Homepage").is_none());
|
||||
@@ -688,7 +688,7 @@ mod tests {
|
||||
#[test]
|
||||
fn rules_is_executable_minimal_makefile() {
|
||||
let o = opts();
|
||||
let rules = super::rules(&o, crate::new::templates::get(TemplateId::Shell).unwrap());
|
||||
let rules = super::rules(&o, crate::new::templates::get(TemplateId::SHELL).unwrap());
|
||||
assert!(rules.executable);
|
||||
assert_eq!(rules.contents, "#!/usr/bin/make -f\n%:\n\tdh $@\n");
|
||||
}
|
||||
@@ -701,7 +701,7 @@ mod tests {
|
||||
"version=4\nhttps://github.com/example/mytool/releases .*/v?@ANY_VERSION@\\.tar\\.gz\n"
|
||||
.to_string(),
|
||||
);
|
||||
let files = super::files(&o, crate::new::templates::get(TemplateId::Shell).unwrap());
|
||||
let files = super::files(&o, crate::new::templates::get(TemplateId::SHELL).unwrap());
|
||||
let find = |path: &str| {
|
||||
files
|
||||
.iter()
|
||||
@@ -731,7 +731,7 @@ mod tests {
|
||||
// Without the extras none of the files are rendered.
|
||||
let plain = super::files(
|
||||
&opts(),
|
||||
crate::new::templates::get(TemplateId::Shell).unwrap(),
|
||||
crate::new::templates::get(TemplateId::SHELL).unwrap(),
|
||||
);
|
||||
assert!(!plain.iter().any(|f| f.path.starts_with("debian/tests")));
|
||||
assert!(!plain.iter().any(|f| f.path == "debian/watch"));
|
||||
|
||||
+35
-39
@@ -4,13 +4,16 @@
|
||||
//! The rule set is deliberately simple and table-driven (highest precedence
|
||||
//! first):
|
||||
//!
|
||||
//! 1. well-known build-system marker files at the top level of the
|
||||
//! directory (`Cargo.toml`, `pyproject.toml`/`setup.py`/`setup.cfg`,
|
||||
//! `meson.build`, `CMakeLists.txt`, `configure.ac`, `go.mod`,
|
||||
//! `Makefile`) — more than one distinct template matching is
|
||||
//! [`Detection::Ambiguous`],
|
||||
//! 1. the `detect.files` marker files declared by the template manifests
|
||||
//! (`data/templates/<id>/manifest.yml`, in registry order: `Cargo.toml`,
|
||||
//! `pyproject.toml`/`setup.py`/`setup.cfg`, `meson.build`,
|
||||
//! `CMakeLists.txt`, `configure.ac`, `go.mod`, `Makefile`) looked for at
|
||||
//! the top level of the directory — more than one distinct template
|
||||
//! matching is [`Detection::Ambiguous`]; templates without markers
|
||||
//! (shell: the single-script heuristic below; empty: never detected)
|
||||
//! declare none,
|
||||
//! 2. otherwise, exactly one top-level script (a `*.sh` file, or a file
|
||||
//! whose first line is a `#!` shebang) → [`TemplateId::Shell`],
|
||||
//! whose first line is a `#!` shebang) → [`TemplateId::SHELL`],
|
||||
//! several scripts or none → nothing,
|
||||
//! 3. otherwise [`Detection::Empty`].
|
||||
//!
|
||||
@@ -25,6 +28,7 @@ use regex::Regex;
|
||||
|
||||
use super::licenses;
|
||||
use super::options::TemplateId;
|
||||
use super::templates;
|
||||
|
||||
/// Outcome of the detection.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -38,26 +42,18 @@ pub enum Detection {
|
||||
Empty,
|
||||
}
|
||||
|
||||
/// Marker files per template, in precedence order (see the module docs).
|
||||
const MARKERS: [(TemplateId, &[&str]); 7] = [
|
||||
(TemplateId::Rust, &["Cargo.toml"]),
|
||||
(
|
||||
TemplateId::Python,
|
||||
&["pyproject.toml", "setup.py", "setup.cfg"],
|
||||
),
|
||||
(TemplateId::Meson, &["meson.build"]),
|
||||
(TemplateId::Cmake, &["CMakeLists.txt"]),
|
||||
(TemplateId::Autotools, &["configure.ac"]),
|
||||
(TemplateId::Go, &["go.mod"]),
|
||||
(TemplateId::Makefile, &["Makefile"]),
|
||||
];
|
||||
|
||||
/// Detect the template matching the project in `dir`.
|
||||
/// Detect the template matching the project in `dir`: the manifests'
|
||||
/// marker files in registry order (the detection priority), then the
|
||||
/// shell single-script heuristic.
|
||||
pub fn detect(dir: &Path) -> Detection {
|
||||
let mut hits: Vec<TemplateId> = Vec::new();
|
||||
for (id, markers) in MARKERS {
|
||||
if markers.iter().any(|marker| dir.join(marker).exists()) && !hits.contains(&id) {
|
||||
hits.push(id);
|
||||
for template in templates::all() {
|
||||
let markers = template.detect_files();
|
||||
if !markers.is_empty()
|
||||
&& markers.iter().any(|marker| dir.join(marker).exists())
|
||||
&& !hits.contains(&template.id())
|
||||
{
|
||||
hits.push(template.id());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +64,7 @@ pub fn detect(dir: &Path) -> Detection {
|
||||
}
|
||||
|
||||
if single_script(dir).is_some() {
|
||||
Detection::Single(TemplateId::Shell)
|
||||
Detection::Single(TemplateId::SHELL)
|
||||
} else {
|
||||
Detection::Empty
|
||||
}
|
||||
@@ -176,15 +172,15 @@ mod tests {
|
||||
#[test]
|
||||
fn marker_files_map_to_templates() {
|
||||
let cases = [
|
||||
("Cargo.toml", TemplateId::Rust),
|
||||
("pyproject.toml", TemplateId::Python),
|
||||
("setup.py", TemplateId::Python),
|
||||
("setup.cfg", TemplateId::Python),
|
||||
("meson.build", TemplateId::Meson),
|
||||
("CMakeLists.txt", TemplateId::Cmake),
|
||||
("configure.ac", TemplateId::Autotools),
|
||||
("go.mod", TemplateId::Go),
|
||||
("Makefile", TemplateId::Makefile),
|
||||
("Cargo.toml", TemplateId::RUST),
|
||||
("pyproject.toml", TemplateId::PYTHON),
|
||||
("setup.py", TemplateId::PYTHON),
|
||||
("setup.cfg", TemplateId::PYTHON),
|
||||
("meson.build", TemplateId::MESON),
|
||||
("CMakeLists.txt", TemplateId::CMAKE),
|
||||
("configure.ac", TemplateId::AUTOTOOLS),
|
||||
("go.mod", TemplateId::GO),
|
||||
("Makefile", TemplateId::MAKEFILE),
|
||||
];
|
||||
for (marker, expected) in cases {
|
||||
let dir = tempdir().unwrap();
|
||||
@@ -200,21 +196,21 @@ mod tests {
|
||||
touch(dir.path(), "Makefile");
|
||||
assert_eq!(
|
||||
detect(dir.path()),
|
||||
Detection::Ambiguous(vec![TemplateId::Rust, TemplateId::Makefile])
|
||||
Detection::Ambiguous(vec![TemplateId::RUST, TemplateId::MAKEFILE])
|
||||
);
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
touch(dir.path(), "pyproject.toml");
|
||||
touch(dir.path(), "setup.py");
|
||||
// Both markers map to the same template: one hit, not ambiguous.
|
||||
assert_eq!(detect(dir.path()), Detection::Single(TemplateId::Python));
|
||||
assert_eq!(detect(dir.path()), Detection::Single(TemplateId::PYTHON));
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
touch(dir.path(), "meson.build");
|
||||
touch(dir.path(), "CMakeLists.txt");
|
||||
assert_eq!(
|
||||
detect(dir.path()),
|
||||
Detection::Ambiguous(vec![TemplateId::Meson, TemplateId::Cmake])
|
||||
Detection::Ambiguous(vec![TemplateId::MESON, TemplateId::CMAKE])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -223,12 +219,12 @@ mod tests {
|
||||
// .sh extension.
|
||||
let dir = tempdir().unwrap();
|
||||
touch(dir.path(), "run.sh");
|
||||
assert_eq!(detect(dir.path()), Detection::Single(TemplateId::Shell));
|
||||
assert_eq!(detect(dir.path()), Detection::Single(TemplateId::SHELL));
|
||||
|
||||
// Shebang without extension.
|
||||
let dir = tempdir().unwrap();
|
||||
std::fs::write(dir.path().join("run"), "#!/usr/bin/env python3\n").unwrap();
|
||||
assert_eq!(detect(dir.path()), Detection::Single(TemplateId::Shell));
|
||||
assert_eq!(detect(dir.path()), Detection::Single(TemplateId::SHELL));
|
||||
|
||||
// Two scripts: not exactly one, nothing recognized.
|
||||
let dir = tempdir().unwrap();
|
||||
|
||||
+19
-19
@@ -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();
|
||||
|
||||
|
||||
+51
-76
@@ -19,74 +19,60 @@ use crate::new::licenses;
|
||||
use crate::new::origin::{Forge, GitOrigin};
|
||||
use crate::new::templates;
|
||||
|
||||
/// Build systems / project kinds `pkh new` knows about.
|
||||
/// Build systems / project kinds `pkh new` knows about: a lightweight id
|
||||
/// wrapping the stable CLI string (the `--lang` value).
|
||||
///
|
||||
/// The identifiers are stable CLI surface: `--lang` accepts every variant,
|
||||
/// and every variant has a template implementation registered in
|
||||
/// [`crate::new::templates`].
|
||||
/// The templates themselves are defined by the per-template manifests
|
||||
/// under `data/templates/<id>/` (see [`crate::new::templates`]): label,
|
||||
/// detection markers, policy metadata and file bodies all live there, and
|
||||
/// every id below must have a manifest registered — the registry is built
|
||||
/// from exactly these constants and a consistency test keeps the two in
|
||||
/// lockstep.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum TemplateId {
|
||||
/// Rust project (`Cargo.toml`)
|
||||
Rust,
|
||||
/// Python project (`pyproject.toml` / `setup.py` / `setup.cfg`)
|
||||
Python,
|
||||
/// C/C++ with Meson (`meson.build`)
|
||||
Meson,
|
||||
/// C/C++ with CMake (`CMakeLists.txt`)
|
||||
Cmake,
|
||||
/// C/C++ with Autotools (`configure.ac`)
|
||||
Autotools,
|
||||
/// Go module (`go.mod`)
|
||||
Go,
|
||||
/// Shell script / single interpreted file
|
||||
Shell,
|
||||
/// Generic Makefile-based project
|
||||
Makefile,
|
||||
/// Metapackage / empty base (no build system)
|
||||
Empty,
|
||||
}
|
||||
pub struct TemplateId(&'static str);
|
||||
|
||||
impl TemplateId {
|
||||
/// Every template id, in the order offered by the wizard language menu.
|
||||
pub fn all() -> [TemplateId; 9] {
|
||||
[
|
||||
TemplateId::Rust,
|
||||
TemplateId::Python,
|
||||
TemplateId::Meson,
|
||||
TemplateId::Cmake,
|
||||
TemplateId::Autotools,
|
||||
TemplateId::Go,
|
||||
TemplateId::Shell,
|
||||
TemplateId::Makefile,
|
||||
TemplateId::Empty,
|
||||
]
|
||||
/// Rust project (`Cargo.toml`).
|
||||
pub const RUST: TemplateId = TemplateId("rust");
|
||||
/// Python project (`pyproject.toml` / `setup.py` / `setup.cfg`).
|
||||
pub const PYTHON: TemplateId = TemplateId("python");
|
||||
/// C/C++ with Meson (`meson.build`).
|
||||
pub const MESON: TemplateId = TemplateId("meson");
|
||||
/// C/C++ with CMake (`CMakeLists.txt`).
|
||||
pub const CMAKE: TemplateId = TemplateId("cmake");
|
||||
/// C/C++ with Autotools (`configure.ac`).
|
||||
pub const AUTOTOOLS: TemplateId = TemplateId("autotools");
|
||||
/// Go module (`go.mod`).
|
||||
pub const GO: TemplateId = TemplateId("go");
|
||||
/// Shell script / single interpreted file.
|
||||
pub const SHELL: TemplateId = TemplateId("shell");
|
||||
/// Generic Makefile-based project.
|
||||
pub const MAKEFILE: TemplateId = TemplateId("makefile");
|
||||
/// Metapackage / empty base (no build system).
|
||||
pub const EMPTY: TemplateId = TemplateId("empty");
|
||||
|
||||
/// Every template id, in the order offered by the wizard language menu
|
||||
/// (the registry order of the manifests).
|
||||
pub fn all() -> &'static [TemplateId] {
|
||||
templates::ids()
|
||||
}
|
||||
|
||||
/// Canonical CLI identifier of this template.
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
TemplateId::Rust => "rust",
|
||||
TemplateId::Python => "python",
|
||||
TemplateId::Meson => "meson",
|
||||
TemplateId::Cmake => "cmake",
|
||||
TemplateId::Autotools => "autotools",
|
||||
TemplateId::Go => "go",
|
||||
TemplateId::Shell => "shell",
|
||||
TemplateId::Makefile => "makefile",
|
||||
TemplateId::Empty => "empty",
|
||||
}
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Parse a CLI identifier, accepting exactly the canonical spellings.
|
||||
/// Parse a CLI identifier, accepting exactly the registered spellings.
|
||||
pub fn parse(s: &str) -> Result<TemplateId, String> {
|
||||
TemplateId::all()
|
||||
.into_iter()
|
||||
Self::all()
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|id| id.as_str() == s)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Unknown language/template '{}'. Supported values are: {}.",
|
||||
s,
|
||||
TemplateId::all()
|
||||
Self::all()
|
||||
.iter()
|
||||
.map(|id| id.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
@@ -96,26 +82,15 @@ impl TemplateId {
|
||||
}
|
||||
|
||||
/// Human-readable menu label of this template, as offered by the wizard
|
||||
/// language question (and reused in the summary screen).
|
||||
/// language question (and reused in the summary screen): the
|
||||
/// manifest's `label`.
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
TemplateId::Rust => "Rust (Cargo.toml)",
|
||||
TemplateId::Python => "Python (pyproject.toml / setup.py)",
|
||||
TemplateId::Meson => "C/C++ (Meson)",
|
||||
TemplateId::Cmake => "C/C++ (CMake)",
|
||||
TemplateId::Autotools => "C/C++ (Autotools)",
|
||||
TemplateId::Go => "Go module",
|
||||
TemplateId::Shell => "Shell script / single interpreted file",
|
||||
TemplateId::Makefile => "Generic (Makefile)",
|
||||
TemplateId::Empty => "Metapackage / empty base (no build system)",
|
||||
}
|
||||
templates::get(*self).map(|t| t.label()).unwrap_or(self.0)
|
||||
}
|
||||
|
||||
/// The template whose menu label (or CLI identifier) is `label`.
|
||||
pub fn from_label(label: &str) -> Option<TemplateId> {
|
||||
TemplateId::all()
|
||||
.into_iter()
|
||||
.find(|id| id.display_name() == label || id.as_str() == label)
|
||||
templates::from_label(label)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,7 +625,7 @@ pub async fn resolve(cli: NewCli) -> Result<NewOptions, String> {
|
||||
SourceDir::Skeleton => {
|
||||
if cli.defaults {
|
||||
log::info!("No language given, --defaults picks the 'empty' template");
|
||||
Some(TemplateId::Empty)
|
||||
Some(TemplateId::EMPTY)
|
||||
} else {
|
||||
missing.push(format!(
|
||||
"--lang <{}|...> (no language given and there is nothing \
|
||||
@@ -695,7 +670,7 @@ pub async fn resolve(cli: NewCli) -> Result<NewOptions, String> {
|
||||
the 'empty' template",
|
||||
dir.display()
|
||||
);
|
||||
Some(TemplateId::Empty)
|
||||
Some(TemplateId::EMPTY)
|
||||
} else {
|
||||
missing.push(
|
||||
"--lang <id> (could not detect a build system; \
|
||||
@@ -845,7 +820,7 @@ pub async fn resolve(cli: NewCli) -> Result<NewOptions, String> {
|
||||
|
||||
Ok(NewOptions {
|
||||
name,
|
||||
template: template.unwrap_or(TemplateId::Empty),
|
||||
template: template.unwrap_or(TemplateId::EMPTY),
|
||||
source_dir,
|
||||
upstream_version,
|
||||
revision,
|
||||
@@ -992,7 +967,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn template_ids_roundtrip() {
|
||||
for id in TemplateId::all() {
|
||||
for id in TemplateId::all().iter().copied() {
|
||||
assert_eq!(TemplateId::parse(id.as_str()).unwrap(), id);
|
||||
// Every id resolves from its menu label too, and labels are
|
||||
// unique.
|
||||
@@ -1001,9 +976,9 @@ mod tests {
|
||||
assert!(TemplateId::parse("cobol").is_err());
|
||||
assert_eq!(
|
||||
TemplateId::from_label("Rust (Cargo.toml)"),
|
||||
Some(TemplateId::Rust)
|
||||
Some(TemplateId::RUST)
|
||||
);
|
||||
assert_eq!(TemplateId::from_label("rust"), Some(TemplateId::Rust));
|
||||
assert_eq!(TemplateId::from_label("rust"), Some(TemplateId::RUST));
|
||||
assert_eq!(TemplateId::from_label("nope"), None);
|
||||
}
|
||||
|
||||
@@ -1099,7 +1074,7 @@ mod tests {
|
||||
// The full version round-trips through DebianVersion.
|
||||
let opts = NewOptions {
|
||||
name: "t".into(),
|
||||
template: TemplateId::Empty,
|
||||
template: TemplateId::EMPTY,
|
||||
source_dir: SourceDir::Here,
|
||||
upstream_version: "0.1.0".into(),
|
||||
revision: 1,
|
||||
@@ -1301,7 +1276,7 @@ mod tests {
|
||||
let opts = resolve(cli).await.unwrap();
|
||||
|
||||
assert_eq!(opts.name, "my-tool");
|
||||
assert_eq!(opts.template, TemplateId::Empty);
|
||||
assert_eq!(opts.template, TemplateId::EMPTY);
|
||||
assert!(matches!(opts.source_dir, SourceDir::Path(_)));
|
||||
assert_eq!(opts.upstream_version, "0.1.0");
|
||||
assert_eq!(opts.revision, 1);
|
||||
@@ -1342,7 +1317,7 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
let opts = resolve(cli).await.unwrap();
|
||||
assert_eq!(opts.template, TemplateId::Go);
|
||||
assert_eq!(opts.template, TemplateId::GO);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
+33
-32
@@ -139,7 +139,7 @@ async fn run_wizard(mut cli: NewCli) -> Result<NewOptions, Box<dyn Error>> {
|
||||
LanguageChoice::Ask(preselected) => {
|
||||
let menu = language_menu(&[]);
|
||||
let default = preselected
|
||||
.unwrap_or(TemplateId::Empty)
|
||||
.unwrap_or(TemplateId::EMPTY)
|
||||
.display_name()
|
||||
.to_string();
|
||||
let id = select_template(&menu, &default)?;
|
||||
@@ -166,7 +166,7 @@ async fn run_wizard(mut cli: NewCli) -> Result<NewOptions, Box<dyn Error>> {
|
||||
// The rust toolchain pin of the packaged project does not travel into
|
||||
// the chroot build: surface it now so a too-old pin is not a surprise
|
||||
// when `pkh deb` compiles with the distribution's rustc.
|
||||
let toolchain_pin = if template == TemplateId::Rust {
|
||||
let toolchain_pin = if template == TemplateId::RUST {
|
||||
probe.as_ref().and_then(|p| p.toolchain_pin.clone())
|
||||
} else {
|
||||
None
|
||||
@@ -390,7 +390,7 @@ async fn run_wizard(mut cli: NewCli) -> Result<NewOptions, Box<dyn Error>> {
|
||||
// same `validate_command` bar as `resolve` applies (which also
|
||||
// requires a non-empty answer), so an unusable probe is withheld and
|
||||
// invalid input re-asks here instead of failing late in `resolve`.
|
||||
if cli.command.is_none() && template != TemplateId::Empty {
|
||||
if cli.command.is_none() && template != TemplateId::EMPTY {
|
||||
let default = probe
|
||||
.as_ref()
|
||||
.and_then(|p| p.command.clone())
|
||||
@@ -469,7 +469,7 @@ async fn run_wizard(mut cli: NewCli) -> Result<NewOptions, Box<dyn Error>> {
|
||||
}
|
||||
|
||||
// 13. Metapackage Depends (empty template only).
|
||||
if template == TemplateId::Empty && cli.depends.is_empty() {
|
||||
if template == TemplateId::EMPTY && cli.depends.is_empty() {
|
||||
let validate = |answer: &str| options::validate_depends(answer).map(|_| ());
|
||||
let answer = ask_text(
|
||||
"Depends (metapackage, comma-separated, blank for an empty base)",
|
||||
@@ -504,7 +504,7 @@ async fn run_wizard(mut cli: NewCli) -> Result<NewOptions, Box<dyn Error>> {
|
||||
// The meson/cmake opt-in question of the spec's template table: does the
|
||||
// build resolve libraries through pkg-config? The project files prefill
|
||||
// the default (dependency() / pkg_check_modules calls found).
|
||||
if matches!(template, TemplateId::Meson | TemplateId::Cmake)
|
||||
if matches!(template, TemplateId::MESON | TemplateId::CMAKE)
|
||||
&& prompt::confirm(
|
||||
"Does the build resolve libraries through pkg-config (add it to Build-Depends)?",
|
||||
pkg_config_hint(&detect_dir, template),
|
||||
@@ -514,7 +514,7 @@ async fn run_wizard(mut cli: NewCli) -> Result<NewOptions, Box<dyn Error>> {
|
||||
}
|
||||
|
||||
// Wizard-only extras (default off).
|
||||
if template != TemplateId::Empty
|
||||
if template != TemplateId::EMPTY
|
||||
&& prompt::confirm(
|
||||
"Add an autopkgtest smoke test (debian/tests/control)?",
|
||||
false,
|
||||
@@ -686,8 +686,8 @@ fn same_directory(a: &std::path::Path, b: &std::path::Path) -> bool {
|
||||
/// CMakeLists.txt).
|
||||
fn pkg_config_hint(dir: &std::path::Path, template: TemplateId) -> bool {
|
||||
let (file, needles): (&str, &[&str]) = match template {
|
||||
TemplateId::Meson => ("meson.build", &["dependency("]),
|
||||
TemplateId::Cmake => (
|
||||
TemplateId::MESON => ("meson.build", &["dependency("]),
|
||||
TemplateId::CMAKE => (
|
||||
"CMakeLists.txt",
|
||||
&[
|
||||
"pkg_check_modules",
|
||||
@@ -727,7 +727,8 @@ fn language_menu(candidates: &[TemplateId]) -> Vec<String> {
|
||||
.copied()
|
||||
.chain(
|
||||
TemplateId::all()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|id| !candidates.contains(id)),
|
||||
)
|
||||
.map(|id| id.display_name().to_string())
|
||||
@@ -1033,12 +1034,12 @@ pub fn summary_text(opts: &NewOptions, toolchain_pin: Option<&str>) -> String {
|
||||
" debian/control Source + 1 binary (Architecture: {})",
|
||||
template.architecture(opts)
|
||||
));
|
||||
if opts.template == TemplateId::Empty {
|
||||
if opts.template == TemplateId::EMPTY {
|
||||
// The Depends list is the payload of the metapackage flavor.
|
||||
if !opts.depends.is_empty() {
|
||||
lines.push(format!(" Depends {}", opts.depends.join(", ")));
|
||||
}
|
||||
} else if opts.template == TemplateId::Rust {
|
||||
} else if opts.template == TemplateId::RUST {
|
||||
// Nothing is vendored yet at this point: only announce that the
|
||||
// generation will attempt it.
|
||||
lines.push(
|
||||
@@ -1094,7 +1095,7 @@ pub fn summary_text(opts: &NewOptions, toolchain_pin: Option<&str>) -> String {
|
||||
lines.push(format!(" + {} (new skeleton)", names.join(", ")));
|
||||
}
|
||||
}
|
||||
if opts.template == TemplateId::Rust && templates::find_on_path("cargo").is_none() {
|
||||
if opts.template == TemplateId::RUST && templates::find_on_path("cargo").is_none() {
|
||||
lines.push(
|
||||
" ! cargo not found on PATH: dependencies cannot be vendored at \
|
||||
scaffold time; the package will not build until you run \
|
||||
@@ -1162,7 +1163,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn language_menu_lists_candidates_first() {
|
||||
let menu = language_menu(&[Tid::Makefile, Tid::Rust]);
|
||||
let menu = language_menu(&[Tid::MAKEFILE, Tid::RUST]);
|
||||
assert_eq!(menu[0], "Generic (Makefile)");
|
||||
assert_eq!(menu[1], "Rust (Cargo.toml)");
|
||||
// The remaining seven follow in registry order, no duplicates.
|
||||
@@ -1179,8 +1180,8 @@ mod tests {
|
||||
#[test]
|
||||
fn language_choice_flag_wins_over_detection() {
|
||||
let detections = [
|
||||
Detection::Single(Tid::Rust),
|
||||
Detection::Ambiguous(vec![Tid::Rust, Tid::Python]),
|
||||
Detection::Single(Tid::RUST),
|
||||
Detection::Ambiguous(vec![Tid::RUST, Tid::PYTHON]),
|
||||
Detection::Empty,
|
||||
];
|
||||
for detection in &detections {
|
||||
@@ -1201,21 +1202,21 @@ mod tests {
|
||||
#[test]
|
||||
fn language_choice_without_flag_follows_detection() {
|
||||
assert_eq!(
|
||||
language_choice(None, &Detection::Single(Tid::Rust), true),
|
||||
LanguageChoice::Detected(Tid::Rust)
|
||||
language_choice(None, &Detection::Single(Tid::RUST), true),
|
||||
LanguageChoice::Detected(Tid::RUST)
|
||||
);
|
||||
// Skeleton run: ask, preselecting the detected ecosystem.
|
||||
assert_eq!(
|
||||
language_choice(None, &Detection::Single(Tid::Rust), false),
|
||||
LanguageChoice::Ask(Some(Tid::Rust))
|
||||
language_choice(None, &Detection::Single(Tid::RUST), false),
|
||||
LanguageChoice::Ask(Some(Tid::RUST))
|
||||
);
|
||||
assert_eq!(
|
||||
language_choice(
|
||||
None,
|
||||
&Detection::Ambiguous(vec![Tid::Go, Tid::Python]),
|
||||
&Detection::Ambiguous(vec![Tid::GO, Tid::PYTHON]),
|
||||
true
|
||||
),
|
||||
LanguageChoice::Ambiguous(vec![Tid::Go, Tid::Python])
|
||||
LanguageChoice::Ambiguous(vec![Tid::GO, Tid::PYTHON])
|
||||
);
|
||||
// Nothing detected: plain menu, the empty template preselected.
|
||||
assert_eq!(
|
||||
@@ -1327,12 +1328,12 @@ mod tests {
|
||||
#[test]
|
||||
fn summary_screen_shows_format_and_orig_origin() {
|
||||
// Native skeleton: the format row, no orig row.
|
||||
let text = summary_text(&opts(Tid::Shell), None);
|
||||
let text = summary_text(&opts(Tid::SHELL), None);
|
||||
assert!(text.contains("debian/source/format 3.0 (native)"), "{text}");
|
||||
assert!(!text.contains("orig tarball"), "{text}");
|
||||
|
||||
// Quilt over an existing project: both rows.
|
||||
let mut quilt = opts(Tid::Shell);
|
||||
let mut quilt = opts(Tid::SHELL);
|
||||
quilt.source_dir = options::SourceDir::Here;
|
||||
quilt.source_format = options::SourceFormat::Quilt;
|
||||
quilt.orig = Some(options::OrigOrigin::GitArchive {
|
||||
@@ -1360,7 +1361,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn summary_screen_skeleton() {
|
||||
let text = summary_text(&opts(Tid::Makefile), None);
|
||||
let text = summary_text(&opts(Tid::MAKEFILE), None);
|
||||
assert!(
|
||||
text.contains("mytool 0.1.0-1 · builds for ubuntu/resolute"),
|
||||
"{text}"
|
||||
@@ -1390,7 +1391,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn summary_screen_metapackage_shows_depends() {
|
||||
let mut o = opts(Tid::Empty);
|
||||
let mut o = opts(Tid::EMPTY);
|
||||
o.depends = vec!["hello".to_string(), "hello-data (>= 1.0)".to_string()];
|
||||
o.source_dir = options::SourceDir::Here;
|
||||
let text = summary_text(&o, None);
|
||||
@@ -1406,7 +1407,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn summary_screen_release_and_extras() {
|
||||
let mut o = opts(Tid::Shell);
|
||||
let mut o = opts(Tid::SHELL);
|
||||
o.release = true;
|
||||
o.autopkgtest = true;
|
||||
o.watch = Some("version=4\n".to_string());
|
||||
@@ -1424,7 +1425,7 @@ mod tests {
|
||||
/// yet (regression: it claimed "(vendored)" before generating).
|
||||
#[test]
|
||||
fn summary_screen_rust_does_not_presume_vendoring() {
|
||||
let text = summary_text(&opts(Tid::Rust), None);
|
||||
let text = summary_text(&opts(Tid::RUST), None);
|
||||
assert!(
|
||||
text.contains("cargo build --release --offline (vendored at generation)"),
|
||||
"{text}"
|
||||
@@ -1436,17 +1437,17 @@ mod tests {
|
||||
/// (rust template only), flagged as ignored by the chroot build.
|
||||
#[test]
|
||||
fn summary_screen_shows_the_toolchain_pin() {
|
||||
let text = summary_text(&opts(Tid::Rust), Some("1.98.0"));
|
||||
let text = summary_text(&opts(Tid::RUST), Some("1.98.0"));
|
||||
assert!(
|
||||
text.contains("rust-toolchain 1.98.0 (ignored by the chroot build)"),
|
||||
"{text}"
|
||||
);
|
||||
|
||||
// No pin, no row.
|
||||
assert!(!summary_text(&opts(Tid::Rust), None).contains("rust-toolchain"));
|
||||
assert!(!summary_text(&opts(Tid::RUST), None).contains("rust-toolchain"));
|
||||
// A pin under a template other than rust is not shown either (the
|
||||
// pin only matters for a cargo build).
|
||||
assert!(!summary_text(&opts(Tid::Go), Some("1.98.0")).contains("rust-toolchain"));
|
||||
assert!(!summary_text(&opts(Tid::GO), Some("1.98.0")).contains("rust-toolchain"));
|
||||
}
|
||||
|
||||
/// The git-init question is only asked when a git init would actually
|
||||
@@ -1595,7 +1596,7 @@ mod tests {
|
||||
|
||||
// The initial pass over dir A.
|
||||
let (detection_a, probe_a) = detect_and_probe(dir_a.path());
|
||||
assert_eq!(detection_a, Detection::Single(Tid::Rust));
|
||||
assert_eq!(detection_a, Detection::Single(Tid::RUST));
|
||||
let probe_a = probe_a.expect("dir A is a rust project");
|
||||
assert_eq!(probe_a.name.as_deref(), Some("alpha"));
|
||||
assert_eq!(probe_a.version.as_deref(), Some("0.1.0"));
|
||||
@@ -1604,7 +1605,7 @@ mod tests {
|
||||
// The user chose dir B instead: the refreshed probe comes from B,
|
||||
// never from A.
|
||||
let (detection_b, probe_b) = detect_and_probe(dir_b.path());
|
||||
assert_eq!(detection_b, Detection::Single(Tid::Rust));
|
||||
assert_eq!(detection_b, Detection::Single(Tid::RUST));
|
||||
let probe_b = probe_b.expect("dir B is a rust project");
|
||||
assert_eq!(probe_b.name.as_deref(), Some("beta"));
|
||||
assert_eq!(probe_b.version.as_deref(), Some("2.9.9"));
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
//! The `autotools` template: a C project with a `configure.ac` built through
|
||||
//! debhelper's auto-detection (dh runs `autoreconf` itself when it finds
|
||||
//! `configure.ac`, debhelper ≥ 10 — no override needed).
|
||||
//!
|
||||
//! The metadata is manifest data; the logic half here is the `AC_INIT`
|
||||
//! probe, the GNU-gettext detection (appending `gettext` to the manifest's
|
||||
//! Build-Depends) and — until the bodies move into the manifest's `files:`
|
||||
//! list — the `configure.ac`/`Makefile.am`/`hello.c` skeleton.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use super::meson::hello_c;
|
||||
use super::{OutputFile, ProbeResult, Template, source_dir_of};
|
||||
use crate::new::options::{NewOptions, TemplateId};
|
||||
use super::{OutputFile, ProbeResult, TemplateHooks, source_dir_of};
|
||||
use crate::new::options::NewOptions;
|
||||
|
||||
/// C/C++ with Autotools (`configure.ac`).
|
||||
pub struct Autotools;
|
||||
/// The logic half of the autotools template.
|
||||
pub struct Hooks;
|
||||
|
||||
impl Template for Autotools {
|
||||
fn id(&self) -> TemplateId {
|
||||
TemplateId::Autotools
|
||||
}
|
||||
/// The autotools template's hooks, registered in the template registry.
|
||||
pub static HOOKS: Hooks = Hooks;
|
||||
|
||||
impl TemplateHooks for Hooks {
|
||||
/// A minimal `configure.ac`, the matching `Makefile.am` and `hello.c`.
|
||||
/// The first source build runs `autoreconf` (integrated in the dh
|
||||
/// sequence), so no generated configure script is committed.
|
||||
fn skeleton(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
fn skeleton_files(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
vec![
|
||||
OutputFile::new(
|
||||
"configure.ac",
|
||||
@@ -47,25 +51,14 @@ impl Template for Autotools {
|
||||
]
|
||||
}
|
||||
|
||||
/// No extra debian/ files: plain `dh $@` auto-detects `configure.ac`.
|
||||
fn debian(&self, _opts: &NewOptions) -> Vec<OutputFile> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn build_depends(&self, opts: &NewOptions) -> Vec<String> {
|
||||
let mut deps = vec![
|
||||
"autoconf".to_string(),
|
||||
"automake".to_string(),
|
||||
"libtool".to_string(),
|
||||
];
|
||||
/// A packaged `configure.ac` that sets up GNU gettext (the
|
||||
/// `AM_GNU_GETTEXT` macro, see [`uses_gettext`]) appends `gettext` to
|
||||
/// the manifest's Build-Depends.
|
||||
fn build_depends(&self, opts: &NewOptions, mut base: Vec<String>) -> Vec<String> {
|
||||
if uses_gettext(opts) {
|
||||
deps.push("gettext".to_string());
|
||||
base.push("gettext".to_string());
|
||||
}
|
||||
deps
|
||||
}
|
||||
|
||||
fn architecture(&self, _opts: &NewOptions) -> &'static str {
|
||||
"any"
|
||||
base
|
||||
}
|
||||
|
||||
/// Package name and version from the `AC_INIT` macro of `configure.ac`.
|
||||
@@ -109,13 +102,13 @@ fn uses_gettext(opts: &NewOptions) -> bool {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::new::options::{License, SourceDir};
|
||||
use crate::new::options::{License, SourceDir, TemplateId};
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn opts() -> NewOptions {
|
||||
NewOptions {
|
||||
name: "mytool".into(),
|
||||
template: TemplateId::Autotools,
|
||||
template: TemplateId::AUTOTOOLS,
|
||||
source_dir: SourceDir::Skeleton,
|
||||
upstream_version: "0.1.0".into(),
|
||||
revision: 1,
|
||||
@@ -141,7 +134,7 @@ mod tests {
|
||||
#[test]
|
||||
fn autotools_template_shape() {
|
||||
let o = opts();
|
||||
let template = super::super::get(TemplateId::Autotools).unwrap();
|
||||
let template = super::super::get(TemplateId::AUTOTOOLS).unwrap();
|
||||
|
||||
assert_eq!(template.architecture(&o), "any");
|
||||
assert_eq!(
|
||||
@@ -177,7 +170,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn autotools_probe_reads_ac_init() {
|
||||
let template = super::super::get(TemplateId::Autotools).unwrap();
|
||||
let template = super::super::get(TemplateId::AUTOTOOLS).unwrap();
|
||||
|
||||
// Bracketed form (the generated skeleton's own shape).
|
||||
let dir = tempdir().unwrap();
|
||||
@@ -222,7 +215,7 @@ mod tests {
|
||||
let o = opts();
|
||||
assert!(!uses_gettext(&o));
|
||||
assert!(
|
||||
!super::super::get(TemplateId::Autotools)
|
||||
!super::super::get(TemplateId::AUTOTOOLS)
|
||||
.unwrap()
|
||||
.build_depends(&o)
|
||||
.contains(&"gettext".to_string())
|
||||
@@ -241,7 +234,7 @@ mod tests {
|
||||
};
|
||||
assert!(uses_gettext(&o));
|
||||
assert!(
|
||||
super::super::get(TemplateId::Autotools)
|
||||
super::super::get(TemplateId::AUTOTOOLS)
|
||||
.unwrap()
|
||||
.build_depends(&o)
|
||||
.contains(&"gettext".to_string())
|
||||
|
||||
+24
-32
@@ -1,27 +1,31 @@
|
||||
//! The `cmake` template: a C/C++ project built with CMake through the
|
||||
//! debhelper cmake buildsystem.
|
||||
//!
|
||||
//! The metadata is manifest data; the logic half here is the `project()`
|
||||
//! probe, the wizard's pkg-config opt-in (appended to the manifest's
|
||||
//! Build-Depends) and — until the bodies move into the manifest's `files:`
|
||||
//! list — the `CMakeLists.txt`/`hello.c` skeleton.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use super::meson::hello_c;
|
||||
use super::{OutputFile, ProbeResult, Template};
|
||||
use crate::new::options::{NewOptions, TemplateId};
|
||||
use super::{ProbeResult, TemplateHooks};
|
||||
use crate::new::options::NewOptions;
|
||||
|
||||
/// C/C++ with CMake (`CMakeLists.txt`).
|
||||
pub struct Cmake;
|
||||
/// The logic half of the cmake template.
|
||||
pub struct Hooks;
|
||||
|
||||
impl Template for Cmake {
|
||||
fn id(&self) -> TemplateId {
|
||||
TemplateId::Cmake
|
||||
}
|
||||
/// The cmake template's hooks, registered in the template registry.
|
||||
pub static HOOKS: Hooks = Hooks;
|
||||
|
||||
impl TemplateHooks for Hooks {
|
||||
/// A minimal `CMakeLists.txt` (project declaration + one installed
|
||||
/// executable) and the classic `hello.c`.
|
||||
fn skeleton(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
fn skeleton_files(&self, opts: &NewOptions) -> Vec<super::OutputFile> {
|
||||
vec![
|
||||
OutputFile::new(
|
||||
super::OutputFile::new(
|
||||
"CMakeLists.txt",
|
||||
format!(
|
||||
"cmake_minimum_required(VERSION 3.16)\n\
|
||||
@@ -38,26 +42,14 @@ impl Template for Cmake {
|
||||
]
|
||||
}
|
||||
|
||||
/// No extra debian/ files: debhelper's cmake buildsystem handles the
|
||||
/// configure/build/install steps.
|
||||
fn debian(&self, _opts: &NewOptions) -> Vec<OutputFile> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn build_depends(&self, opts: &NewOptions) -> Vec<String> {
|
||||
let mut deps = vec!["cmake".to_string()];
|
||||
/// The wizard's pkg-config opt-in ([`NewOptions::pkg_config`], offered
|
||||
/// when the project's build file hints at `pkg_check_modules` usage)
|
||||
/// adds `pkg-config` to the manifest's Build-Depends.
|
||||
fn build_depends(&self, opts: &NewOptions, mut base: Vec<String>) -> Vec<String> {
|
||||
if opts.pkg_config {
|
||||
deps.push("pkg-config".to_string());
|
||||
base.push("pkg-config".to_string());
|
||||
}
|
||||
deps
|
||||
}
|
||||
|
||||
fn architecture(&self, _opts: &NewOptions) -> &'static str {
|
||||
"any"
|
||||
}
|
||||
|
||||
fn rules_dh_line(&self) -> String {
|
||||
"dh $@ --buildsystem=cmake".to_string()
|
||||
base
|
||||
}
|
||||
|
||||
/// Project name and version from the `project(<name> VERSION …)`
|
||||
@@ -84,13 +76,13 @@ impl Template for Cmake {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::new::options::{License, SourceDir};
|
||||
use crate::new::options::{License, SourceDir, TemplateId};
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn opts() -> NewOptions {
|
||||
NewOptions {
|
||||
name: "mytool".into(),
|
||||
template: TemplateId::Cmake,
|
||||
template: TemplateId::CMAKE,
|
||||
source_dir: SourceDir::Skeleton,
|
||||
upstream_version: "0.1.0".into(),
|
||||
revision: 1,
|
||||
@@ -116,7 +108,7 @@ mod tests {
|
||||
#[test]
|
||||
fn cmake_template_shape() {
|
||||
let o = opts();
|
||||
let template = super::super::get(TemplateId::Cmake).unwrap();
|
||||
let template = super::super::get(TemplateId::CMAKE).unwrap();
|
||||
|
||||
assert_eq!(template.architecture(&o), "any");
|
||||
assert_eq!(template.build_depends(&o), vec!["cmake".to_string()]);
|
||||
@@ -144,7 +136,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cmake_probe_reads_project_declaration() {
|
||||
let template = super::super::get(TemplateId::Cmake).unwrap();
|
||||
let template = super::super::get(TemplateId::CMAKE).unwrap();
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
std::fs::write(
|
||||
|
||||
+13
-18
@@ -5,22 +5,23 @@
|
||||
//! **metapackage** flavor (the canonical `Architecture: all`, nothing
|
||||
//! compiled, the Depends list *is* the payload shape), while an empty list
|
||||
//! selects the **empty base** — pure `dh $@` plumbing as a starting point
|
||||
//! for hand-written rules.
|
||||
//! for hand-written rules. Both flavors are pure manifest data except for
|
||||
//! the stub `README` of the skeleton, which lives here until its static
|
||||
//! body moves into the manifest's `files:` list.
|
||||
|
||||
use super::{OutputFile, Template};
|
||||
use super::{OutputFile, TemplateHooks};
|
||||
use crate::new::options::NewOptions;
|
||||
|
||||
/// Metapackage / empty base (no build system).
|
||||
pub struct Empty;
|
||||
/// The logic half of the empty template.
|
||||
pub struct Hooks;
|
||||
|
||||
impl Template for Empty {
|
||||
fn id(&self) -> crate::new::options::TemplateId {
|
||||
crate::new::options::TemplateId::Empty
|
||||
}
|
||||
/// The empty template's hooks, registered in the template registry.
|
||||
pub static HOOKS: Hooks = Hooks;
|
||||
|
||||
/// No upstream files; just a stub `README` marking the tree as
|
||||
impl TemplateHooks for Hooks {
|
||||
/// No upstream files beyond a stub `README` marking the tree as
|
||||
/// intentionally empty.
|
||||
fn skeleton(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
fn skeleton_files(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
vec![OutputFile::new(
|
||||
"README",
|
||||
format!(
|
||||
@@ -30,12 +31,6 @@ impl Template for Empty {
|
||||
),
|
||||
)]
|
||||
}
|
||||
|
||||
/// No extra debian/ files: the metapackage `Depends` list is carried by
|
||||
/// [`NewOptions::depends`] into the common `debian/control` rendering.
|
||||
fn debian(&self, _opts: &NewOptions) -> Vec<OutputFile> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -46,7 +41,7 @@ mod tests {
|
||||
fn opts(depends: Vec<String>) -> NewOptions {
|
||||
NewOptions {
|
||||
name: "metapkg".into(),
|
||||
template: TemplateId::Empty,
|
||||
template: TemplateId::EMPTY,
|
||||
source_dir: SourceDir::Skeleton,
|
||||
upstream_version: "0.1.0".into(),
|
||||
revision: 1,
|
||||
@@ -71,7 +66,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn empty_template_shape() {
|
||||
let template = super::super::get(TemplateId::Empty).unwrap();
|
||||
let template = super::super::get(TemplateId::EMPTY).unwrap();
|
||||
|
||||
// Metapackage flavor: the depends list travels in the options.
|
||||
let o = opts(vec!["hello".into(), "hello-data (>= 1.0)".into()]);
|
||||
|
||||
+25
-38
@@ -1,26 +1,28 @@
|
||||
//! The `go` template: a Go module built through dh-golang.
|
||||
//!
|
||||
//! The source stanza carries `XS-Go-Import-Path`, probed from the `module`
|
||||
//! line of `go.mod` when the packaged tree has one, defaulting to the
|
||||
//! package name (fresh skeletons embed the package name in their own
|
||||
//! `go.mod`).
|
||||
//! The source stanza carries `XS-Go-Import-Path`, declared as the
|
||||
//! `{go_import_path}` placeholder by the manifest and filled from the
|
||||
//! `module` line of `go.mod` when the packaged tree has one, defaulting to
|
||||
//! the package name (fresh skeletons embed the package name in their own
|
||||
//! `go.mod`). The module line is also the probe of an existing project; the
|
||||
//! skeleton bodies stay here until they move into the manifest's `files:`
|
||||
//! list.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use super::{OutputFile, Template, source_dir_of};
|
||||
use crate::new::options::{NewOptions, TemplateId};
|
||||
use super::{OutputFile, ProbeResult, TemplateHooks, source_dir_of};
|
||||
use crate::new::options::NewOptions;
|
||||
|
||||
/// Go module (`go.mod`).
|
||||
pub struct Go;
|
||||
/// The logic half of the go template.
|
||||
pub struct Hooks;
|
||||
|
||||
impl Template for Go {
|
||||
fn id(&self) -> TemplateId {
|
||||
TemplateId::Go
|
||||
}
|
||||
/// The go template's hooks, registered in the template registry.
|
||||
pub static HOOKS: Hooks = Hooks;
|
||||
|
||||
impl TemplateHooks for Hooks {
|
||||
/// A stdlib-only `main.go` (no archive dependencies needed to build) and
|
||||
/// the matching `go.mod` whose module path is the package name.
|
||||
fn skeleton(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
fn skeleton_files(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
vec![
|
||||
OutputFile::new(
|
||||
"go.mod",
|
||||
@@ -49,36 +51,21 @@ impl Template for Go {
|
||||
]
|
||||
}
|
||||
|
||||
/// No extra debian/ files: dh-golang drives the build.
|
||||
fn debian(&self, _opts: &NewOptions) -> Vec<OutputFile> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn build_depends(&self, _opts: &NewOptions) -> Vec<String> {
|
||||
vec!["golang-any".to_string(), "dh-golang".to_string()]
|
||||
}
|
||||
|
||||
fn architecture(&self, _opts: &NewOptions) -> &'static str {
|
||||
"any"
|
||||
}
|
||||
|
||||
fn rules_dh_line(&self) -> String {
|
||||
"dh $@ --buildsystem=golang".to_string()
|
||||
}
|
||||
|
||||
fn source_fields(&self, opts: &NewOptions) -> Vec<(String, String)> {
|
||||
vec![("XS-Go-Import-Path".to_string(), import_path(opts))]
|
||||
/// The `{go_import_path}` value of the manifest's `XS-Go-Import-Path`
|
||||
/// source field (see [`import_path`]).
|
||||
fn context(&self, opts: &NewOptions) -> Vec<(String, String)> {
|
||||
vec![("go_import_path".to_string(), import_path(opts))]
|
||||
}
|
||||
|
||||
/// Name (and default command) from the `module` line of `go.mod`: the
|
||||
/// last path segment is the conventional binary/package name.
|
||||
fn probe(&self, dir: &Path) -> Option<super::ProbeResult> {
|
||||
fn probe(&self, dir: &Path) -> Option<ProbeResult> {
|
||||
let module = read_module_line(dir)?;
|
||||
let name = module.rsplit('/').next()?.to_string();
|
||||
if name.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(super::ProbeResult {
|
||||
Some(ProbeResult {
|
||||
name: Some(name.clone()),
|
||||
command: Some(name),
|
||||
..Default::default()
|
||||
@@ -114,13 +101,13 @@ fn read_module_line(dir: &Path) -> Option<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::new::options::{License, SourceDir};
|
||||
use crate::new::options::{License, SourceDir, TemplateId};
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn opts(source_dir: SourceDir) -> NewOptions {
|
||||
NewOptions {
|
||||
name: "mytool".into(),
|
||||
template: TemplateId::Go,
|
||||
template: TemplateId::GO,
|
||||
source_dir,
|
||||
upstream_version: "0.1.0".into(),
|
||||
revision: 1,
|
||||
@@ -146,7 +133,7 @@ mod tests {
|
||||
#[test]
|
||||
fn go_template_shape() {
|
||||
let o = opts(SourceDir::Skeleton);
|
||||
let template = super::super::get(TemplateId::Go).unwrap();
|
||||
let template = super::super::get(TemplateId::GO).unwrap();
|
||||
|
||||
assert_eq!(template.architecture(&o), "any");
|
||||
assert_eq!(
|
||||
@@ -180,7 +167,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let template = super::super::get(TemplateId::Go).unwrap();
|
||||
let template = super::super::get(TemplateId::GO).unwrap();
|
||||
let probe = template.probe(dir.path()).expect("probe result");
|
||||
assert_eq!(probe.name.as_deref(), Some("mytool"));
|
||||
assert_eq!(probe.command.as_deref(), Some("mytool"));
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
//! The `makefile` template: a generic project driven by a plain `Makefile`.
|
||||
//! The `makefile` template: a generic project driven by a plain Makefile.
|
||||
//!
|
||||
//! debhelper's makefile buildsystem runs `make` for the build and
|
||||
//! `make install DESTDIR=...` when the Makefile carries an `install:` target
|
||||
//! debhelper's makefile buildsystem runs `make` for the build and `make
|
||||
//! install DESTDIR=...` when the Makefile carries an `install:` target
|
||||
//! (missing targets are skipped gracefully), so plain `dh $@` plumbing is
|
||||
//! enough here.
|
||||
//! enough here. The metadata is manifest data; the logic half here is the
|
||||
//! phony-`install:` heuristic advising the user whether `dh_auto_install`
|
||||
//! will run `make install`, plus (until their bodies move into the
|
||||
//! manifest's `files:` list) the `hello.c`/`Makefile` skeleton and its
|
||||
//! `debian/install` mapping.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use super::{OutputFile, Template, source_dir_of};
|
||||
use crate::new::options::{NewOptions, SourceDir, TemplateId};
|
||||
use super::meson::hello_c;
|
||||
use super::{OutputFile, TemplateHooks, source_dir_of};
|
||||
use crate::new::options::{NewOptions, SourceDir};
|
||||
|
||||
/// Generic Makefile-based project.
|
||||
pub struct Makefile;
|
||||
/// The logic half of the makefile template.
|
||||
pub struct Hooks;
|
||||
|
||||
impl Template for Makefile {
|
||||
fn id(&self) -> TemplateId {
|
||||
TemplateId::Makefile
|
||||
}
|
||||
/// The makefile template's hooks, registered in the template registry.
|
||||
pub static HOOKS: Hooks = Hooks;
|
||||
|
||||
impl TemplateHooks for Hooks {
|
||||
/// A `hello.c` plus a `Makefile` with `all`/`install`/`clean` targets;
|
||||
/// `install` honors `DESTDIR` and copies the binary to
|
||||
/// `$(DESTDIR)/usr/bin/`.
|
||||
fn skeleton(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
fn skeleton_files(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
vec![
|
||||
super::meson::hello_c(opts),
|
||||
hello_c(opts),
|
||||
OutputFile::new(
|
||||
"Makefile",
|
||||
format!(
|
||||
@@ -55,7 +59,7 @@ impl Template for Makefile {
|
||||
/// existing tree the Makefile is probed instead (no `debian/install` is
|
||||
/// emitted there — the source-relative mapping of an unknown artifact is
|
||||
/// only the project's to write, and `make install` already ran).
|
||||
fn debian(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
fn debian_files(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
match &opts.source_dir {
|
||||
SourceDir::Skeleton => {
|
||||
vec![OutputFile::new(
|
||||
@@ -82,14 +86,6 @@ impl Template for Makefile {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_depends(&self, _opts: &NewOptions) -> Vec<String> {
|
||||
vec!["build-essential".to_string()]
|
||||
}
|
||||
|
||||
fn architecture(&self, _opts: &NewOptions) -> &'static str {
|
||||
"any"
|
||||
}
|
||||
}
|
||||
|
||||
/// The name of the phony `install:` target of the Makefile at `path`, when
|
||||
@@ -119,13 +115,13 @@ pub fn phony_install_target(path: &Path) -> Option<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::new::options::{License, SourceDir};
|
||||
use crate::new::options::{License, SourceDir, TemplateId};
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn opts(source_dir: SourceDir) -> NewOptions {
|
||||
NewOptions {
|
||||
name: "mytool".into(),
|
||||
template: TemplateId::Makefile,
|
||||
template: TemplateId::MAKEFILE,
|
||||
source_dir,
|
||||
upstream_version: "0.1.0".into(),
|
||||
revision: 1,
|
||||
@@ -151,7 +147,7 @@ mod tests {
|
||||
#[test]
|
||||
fn makefile_template_shape() {
|
||||
let o = opts(SourceDir::Skeleton);
|
||||
let template = super::super::get(TemplateId::Makefile).unwrap();
|
||||
let template = super::super::get(TemplateId::MAKEFILE).unwrap();
|
||||
|
||||
assert_eq!(template.architecture(&o), "any");
|
||||
assert_eq!(
|
||||
|
||||
+25
-32
@@ -1,24 +1,29 @@
|
||||
//! The `meson` template: a C/C++ project built with Meson through the
|
||||
//! debhelper meson buildsystem.
|
||||
//!
|
||||
//! The metadata is manifest data; the logic half here is the `project()`
|
||||
//! probe, the wizard's pkg-config opt-in (appended to the manifest's
|
||||
//! Build-Depends) and — until the bodies move into the manifest's `files:`
|
||||
//! list — the `meson.build`/`hello.c` skeleton. [`hello_c`] is the shared
|
||||
//! placeholder of the C/C++ skeletons.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use super::{OutputFile, ProbeResult, Template};
|
||||
use crate::new::options::{NewOptions, TemplateId};
|
||||
use super::{OutputFile, ProbeResult, TemplateHooks};
|
||||
use crate::new::options::NewOptions;
|
||||
|
||||
/// C/C++ with Meson (`meson.build`).
|
||||
pub struct Meson;
|
||||
/// The logic half of the meson template.
|
||||
pub struct Hooks;
|
||||
|
||||
impl Template for Meson {
|
||||
fn id(&self) -> TemplateId {
|
||||
TemplateId::Meson
|
||||
}
|
||||
/// The meson template's hooks, registered in the template registry.
|
||||
pub static HOOKS: Hooks = Hooks;
|
||||
|
||||
impl TemplateHooks for Hooks {
|
||||
/// A minimal `meson.build` (project declaration + one installed
|
||||
/// executable) and the classic `hello.c`.
|
||||
fn skeleton(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
fn skeleton_files(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
vec![
|
||||
OutputFile::new(
|
||||
"meson.build",
|
||||
@@ -37,26 +42,14 @@ impl Template for Meson {
|
||||
]
|
||||
}
|
||||
|
||||
/// No extra debian/ files: debhelper's meson buildsystem handles the
|
||||
/// configure/build/install steps.
|
||||
fn debian(&self, _opts: &NewOptions) -> Vec<OutputFile> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn build_depends(&self, opts: &NewOptions) -> Vec<String> {
|
||||
let mut deps = vec!["meson".to_string()];
|
||||
/// The wizard's pkg-config opt-in ([`NewOptions::pkg_config`], offered
|
||||
/// when the project's build file hints at `dependency(` usage) adds
|
||||
/// `pkg-config` to the manifest's Build-Depends.
|
||||
fn build_depends(&self, opts: &NewOptions, mut base: Vec<String>) -> Vec<String> {
|
||||
if opts.pkg_config {
|
||||
deps.push("pkg-config".to_string());
|
||||
base.push("pkg-config".to_string());
|
||||
}
|
||||
deps
|
||||
}
|
||||
|
||||
fn architecture(&self, _opts: &NewOptions) -> &'static str {
|
||||
"any"
|
||||
}
|
||||
|
||||
fn rules_dh_line(&self) -> String {
|
||||
"dh $@ --buildsystem=meson".to_string()
|
||||
base
|
||||
}
|
||||
|
||||
/// Project name and version from the `project('name', version: …)`
|
||||
@@ -98,13 +91,13 @@ pub(super) fn hello_c(opts: &NewOptions) -> OutputFile {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::new::options::{License, SourceDir};
|
||||
use crate::new::options::{License, SourceDir, TemplateId};
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn opts() -> NewOptions {
|
||||
NewOptions {
|
||||
name: "mytool".into(),
|
||||
template: TemplateId::Meson,
|
||||
template: TemplateId::MESON,
|
||||
source_dir: SourceDir::Skeleton,
|
||||
upstream_version: "0.1.0".into(),
|
||||
revision: 1,
|
||||
@@ -130,7 +123,7 @@ mod tests {
|
||||
#[test]
|
||||
fn meson_template_shape() {
|
||||
let o = opts();
|
||||
let template = super::super::get(TemplateId::Meson).unwrap();
|
||||
let template = super::super::get(TemplateId::MESON).unwrap();
|
||||
|
||||
assert_eq!(template.architecture(&o), "any");
|
||||
assert_eq!(template.build_depends(&o), vec!["meson".to_string()]);
|
||||
@@ -155,7 +148,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn meson_probe_reads_project_declaration() {
|
||||
let template = super::super::get(TemplateId::Meson).unwrap();
|
||||
let template = super::super::get(TemplateId::MESON).unwrap();
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
std::fs::write(
|
||||
@@ -181,7 +174,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn pkg_config_opt_in_extends_build_depends() {
|
||||
let template = super::super::get(TemplateId::Meson).unwrap();
|
||||
let template = super::super::get(TemplateId::MESON).unwrap();
|
||||
let mut o = opts();
|
||||
assert_eq!(template.build_depends(&o), vec!["meson".to_string()]);
|
||||
o.pkg_config = true;
|
||||
|
||||
+908
-214
File diff suppressed because it is too large
Load Diff
+49
-46
@@ -4,17 +4,25 @@
|
||||
//! …`) with a deliberately minimal line-oriented reader (see
|
||||
//! [`read_pyproject`]) — pkh has no TOML dependency, and only a handful of
|
||||
//! keys matter here. A bare `setup.py`/`setup.cfg` project falls back to
|
||||
//! setuptools without the `pybuild-plugin-pyproject` helper.
|
||||
//! setuptools without the `pybuild-plugin-pyproject` helper. The manifest's
|
||||
//! Build-Depends/architecture are the fresh-skeleton baseline the hooks
|
||||
//! here resolve for an existing project (backend package, pyproject
|
||||
//! presence, C-extension hints); the skeleton bodies stay in Rust until
|
||||
//! they move into the manifest's `files:` list (the module name of
|
||||
//! `[project.scripts]` is derived, not substituted).
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use super::{OutputFile, ProbeResult, Template, source_dir_of};
|
||||
use crate::new::options::{NewOptions, TemplateId};
|
||||
use super::{OutputFile, ProbeResult, TemplateHooks, source_dir_of};
|
||||
use crate::new::options::NewOptions;
|
||||
|
||||
/// Python project (`pyproject.toml` / `setup.py` / `setup.cfg`).
|
||||
pub struct Python;
|
||||
/// The logic half of the python template.
|
||||
pub struct Hooks;
|
||||
|
||||
/// The python template's hooks, registered in the template registry.
|
||||
pub static HOOKS: Hooks = Hooks;
|
||||
|
||||
/// The PEP 517 backend of a project.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -113,14 +121,10 @@ fn c_extension_hints(opts: &NewOptions) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
impl Template for Python {
|
||||
fn id(&self) -> TemplateId {
|
||||
TemplateId::Python
|
||||
}
|
||||
|
||||
impl TemplateHooks for Hooks {
|
||||
/// A minimal setuptools-based `pyproject.toml` with one console script,
|
||||
/// plus the one-module package providing it.
|
||||
fn skeleton(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
fn skeleton_files(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
let module = module_name(opts);
|
||||
vec![
|
||||
OutputFile::new(
|
||||
@@ -160,39 +164,36 @@ impl Template for Python {
|
||||
]
|
||||
}
|
||||
|
||||
/// No extra debian/ files: pybuild installs the package and its console
|
||||
/// entry points (under `/usr/bin`) automatically.
|
||||
fn debian(&self, _opts: &NewOptions) -> Vec<OutputFile> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn build_depends(&self, opts: &NewOptions) -> Vec<String> {
|
||||
let mut deps = vec!["dh-python".to_string(), "python3-all".to_string()];
|
||||
/// Amend the manifest's Build-Depends (the fresh-skeleton baseline)
|
||||
/// with what only the packaged project decides: C-extension hints add
|
||||
/// `python3-all-dev` next to `python3-all`, a bare `setup.py` project
|
||||
/// drops the pyproject plugin, and the actual backend package replaces
|
||||
/// the skeleton's setuptools entry.
|
||||
fn build_depends(&self, opts: &NewOptions, mut base: Vec<String>) -> Vec<String> {
|
||||
if c_extension_hints(opts) {
|
||||
deps.push("python3-all-dev".to_string());
|
||||
let at = base
|
||||
.iter()
|
||||
.position(|dep| dep == "python3-all")
|
||||
.map_or(base.len(), |at| at + 1);
|
||||
base.insert(at, "python3-all-dev".to_string());
|
||||
}
|
||||
if uses_pyproject(opts) {
|
||||
deps.push("pybuild-plugin-pyproject".to_string());
|
||||
if !uses_pyproject(opts) {
|
||||
base.retain(|dep| dep != "pybuild-plugin-pyproject");
|
||||
}
|
||||
deps.push(backend(opts).package().to_string());
|
||||
deps
|
||||
// The backend package: the skeleton baseline ends in the generated
|
||||
// setuptools backend, which the packaged project's own replaces.
|
||||
let backend_package = backend(opts).package().to_string();
|
||||
match base.iter().position(|dep| dep == "python3-setuptools") {
|
||||
Some(at) => base[at] = backend_package,
|
||||
None => base.push(backend_package),
|
||||
}
|
||||
base
|
||||
}
|
||||
|
||||
/// `all` unless the project hints at compiled C extensions.
|
||||
fn architecture(&self, opts: &NewOptions) -> &'static str {
|
||||
if c_extension_hints(opts) {
|
||||
"any"
|
||||
} else {
|
||||
"all"
|
||||
}
|
||||
}
|
||||
|
||||
fn rules_dh_line(&self) -> String {
|
||||
"dh $@ --with python3 --buildsystem=pybuild".to_string()
|
||||
}
|
||||
|
||||
fn source_fields(&self, _opts: &NewOptions) -> Vec<(String, String)> {
|
||||
Vec::new()
|
||||
/// `any` instead of the manifest's `all` when the project hints at
|
||||
/// compiled C extensions.
|
||||
fn architecture(&self, opts: &NewOptions) -> Option<&'static str> {
|
||||
c_extension_hints(opts).then_some("any")
|
||||
}
|
||||
|
||||
/// Metadata from the `[project]` section of `pyproject.toml` (name,
|
||||
@@ -339,13 +340,13 @@ fn split_key_value(line: &str) -> Option<(&str, String)> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::new::options::{License, SourceDir};
|
||||
use crate::new::options::{License, SourceDir, TemplateId};
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn opts(source_dir: SourceDir) -> NewOptions {
|
||||
NewOptions {
|
||||
name: "mytool".into(),
|
||||
template: TemplateId::Python,
|
||||
template: TemplateId::PYTHON,
|
||||
source_dir,
|
||||
upstream_version: "0.1.0".into(),
|
||||
revision: 1,
|
||||
@@ -375,7 +376,7 @@ mod tests {
|
||||
#[test]
|
||||
fn python_skeleton_shape() {
|
||||
let o = opts(SourceDir::Skeleton);
|
||||
let template = super::super::get(TemplateId::Python).unwrap();
|
||||
let template = super::super::get(TemplateId::PYTHON).unwrap();
|
||||
|
||||
assert_eq!(template.architecture(&o), "all");
|
||||
assert!(template.debian(&o).is_empty());
|
||||
@@ -420,7 +421,7 @@ mod tests {
|
||||
assert_eq!(module_name(&o), "my_tool");
|
||||
|
||||
// The skeleton module path and the pyproject script agree.
|
||||
let template = super::super::get(TemplateId::Python).unwrap();
|
||||
let template = super::super::get(TemplateId::PYTHON).unwrap();
|
||||
let skeleton = template.skeleton(&o);
|
||||
assert!(skeleton.iter().any(|f| f.path == "my_tool/__init__.py"));
|
||||
assert!(skeleton.iter().any(|f| {
|
||||
@@ -447,7 +448,7 @@ mod tests {
|
||||
// Unknown values fall back to setuptools.
|
||||
assert_eq!(backend_of_value("mystery.backend"), Backend::Setuptools);
|
||||
|
||||
let template = super::super::get(TemplateId::Python).unwrap();
|
||||
let template = super::super::get(TemplateId::PYTHON).unwrap();
|
||||
|
||||
// No source dir (skeleton): setuptools + pyproject plugin.
|
||||
assert_eq!(backend(&opts(SourceDir::Skeleton)), Backend::Setuptools);
|
||||
@@ -507,7 +508,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn c_extension_hints_flip_architecture_and_deps() {
|
||||
let template = super::super::get(TemplateId::Python).unwrap();
|
||||
let template = super::super::get(TemplateId::PYTHON).unwrap();
|
||||
|
||||
// pyo3 in Cargo.toml.
|
||||
let dir = tempdir().unwrap();
|
||||
@@ -520,6 +521,7 @@ mod tests {
|
||||
assert!(c_extension_hints(&o));
|
||||
let deps = template.build_depends(&o);
|
||||
assert!(deps.contains(&"python3-all-dev".to_string()), "{deps:?}");
|
||||
assert_eq!(template.architecture(&o), "any");
|
||||
|
||||
// ext_modules in setup.py.
|
||||
let dir = tempdir().unwrap();
|
||||
@@ -537,11 +539,12 @@ mod tests {
|
||||
let o = source_opts(dir.path());
|
||||
assert!(!c_extension_hints(&o));
|
||||
assert_eq!(template.build_depends(&o).len(), 4); // dh-python, python3-all, plugin, setuptools
|
||||
assert_eq!(template.architecture(&o), "all");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_reads_project_and_scripts() {
|
||||
let template = super::super::get(TemplateId::Python).unwrap();
|
||||
let template = super::super::get(TemplateId::PYTHON).unwrap();
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
std::fs::write(
|
||||
|
||||
+34
-86
@@ -9,16 +9,24 @@
|
||||
//! scaffold continues with a loud warning and reports
|
||||
//! [`ScaffoldOutcome::vendoring_failed`] to the flow — the package will not
|
||||
//! build until the user vendors manually.
|
||||
//!
|
||||
//! The metadata is manifest data (`data/templates/rust/manifest.yml`),
|
||||
//! including the `debian/rules` overrides of `rules.extra.tpl`; the logic
|
||||
//! half here fills that template's `{locked}`/`{artifact}` placeholders,
|
||||
//! renders the crate-name-sanitized skeleton and runs the vendoring.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{OutputFile, ProbeResult, ScaffoldOutcome, Template, find_on_path, source_dir_of};
|
||||
use crate::new::options::{NewOptions, SourceDir, TemplateId};
|
||||
use super::{OutputFile, ProbeResult, ScaffoldOutcome, TemplateHooks, find_on_path, source_dir_of};
|
||||
use crate::new::options::{NewOptions, SourceDir};
|
||||
|
||||
/// Rust project (`Cargo.toml`).
|
||||
pub struct Rust;
|
||||
/// The logic half of the rust template.
|
||||
pub struct Hooks;
|
||||
|
||||
/// The rust template's hooks, registered in the template registry.
|
||||
pub static HOOKS: Hooks = Hooks;
|
||||
|
||||
/// The source-replacement configuration, used when `cargo vendor` did not
|
||||
/// print one itself (old cargo versions, empty output).
|
||||
@@ -30,13 +38,9 @@ fn crate_name(opts: &NewOptions) -> String {
|
||||
opts.name.replace(['+', '.'], "_")
|
||||
}
|
||||
|
||||
impl Template for Rust {
|
||||
fn id(&self) -> TemplateId {
|
||||
TemplateId::Rust
|
||||
}
|
||||
|
||||
impl TemplateHooks for Hooks {
|
||||
/// A zero-dependency `Cargo.toml` and the matching `src/main.rs`.
|
||||
fn skeleton(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
fn skeleton_files(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
vec![
|
||||
OutputFile::new(
|
||||
"Cargo.toml",
|
||||
@@ -65,82 +69,26 @@ impl Template for Rust {
|
||||
]
|
||||
}
|
||||
|
||||
/// No extra debian/ files: the vendored build lives entirely in the
|
||||
/// rules overrides.
|
||||
fn debian(&self, _opts: &NewOptions) -> Vec<OutputFile> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn build_depends(&self, _opts: &NewOptions) -> Vec<String> {
|
||||
vec!["cargo:native".to_string(), "rustc:native".to_string()]
|
||||
}
|
||||
|
||||
/// Root `.gitignore` entries contributed in every mode: the vendoring
|
||||
/// hook runs unconditionally for this template, so `vendor/` and
|
||||
/// `.cargo/config.toml` exist (or will exist) in every scaffolded rust
|
||||
/// tree and must stay out of the repository. `.cargo/config.toml` is
|
||||
/// the subtle one: it points cargo at `vendor/`, so committing it would
|
||||
/// break plain `cargo build` for anyone cloning the repository without
|
||||
/// the vendored sources.
|
||||
fn gitignore_entries(&self, _opts: &NewOptions) -> Vec<String> {
|
||||
vec!["vendor/".to_string(), ".cargo/config.toml".to_string()]
|
||||
}
|
||||
|
||||
fn architecture(&self, _opts: &NewOptions) -> &'static str {
|
||||
"any"
|
||||
}
|
||||
|
||||
/// The vendored build overrides. `--locked` is used only when the
|
||||
/// packaged tree already carries a `Cargo.lock` (fresh skeletons have
|
||||
/// none yet — the vendoring hook patches the flag in once `cargo vendor`
|
||||
/// created it, see [`patch_rules_locked`]); omitting it is always safe.
|
||||
/// The built artifact of a skeleton is named after its crate (a
|
||||
/// sanitized package name) and installed under the command name.
|
||||
///
|
||||
/// `dh_update_autotools_config` is overridden away: crates embedding C
|
||||
/// sources (e.g. `-sys` crates shipping `config.sub`/`config.guess`)
|
||||
/// carry per-file cargo checksums, and debhelper refreshing those files
|
||||
/// with the system's newer copies would break `cargo build --offline`.
|
||||
/// `dh_clean` gets `-X Cargo.toml.orig` for the same reason: it treats
|
||||
/// every vendored `Cargo.toml.orig` as a patch backup and deletes it,
|
||||
/// which breaks the checksums on any build without a warm cache.
|
||||
fn rules_extra(&self, opts: &NewOptions) -> String {
|
||||
/// The `{locked}`/`{artifact}` values of `rules.extra.tpl`: `--locked`
|
||||
/// is used only when the packaged tree already carries a `Cargo.lock`
|
||||
/// (fresh skeletons have none yet — the vendoring hook patches the flag
|
||||
/// in once `cargo vendor` created it); the built artifact of a skeleton
|
||||
/// is named after its crate (a sanitized package name), an existing
|
||||
/// project's under the (probed or answered) command.
|
||||
fn context(&self, opts: &NewOptions) -> Vec<(String, String)> {
|
||||
let locked = if lockfile_present(opts) {
|
||||
" --locked"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
// The skeleton's cargo artifact is the crate name; for an existing
|
||||
// project the (probed or answered) command names the binary.
|
||||
let artifact = match opts.source_dir {
|
||||
SourceDir::Skeleton => crate_name(opts),
|
||||
_ => opts.command.clone(),
|
||||
};
|
||||
format!(
|
||||
"override_dh_auto_build:\n\
|
||||
\tcargo build --release --offline{locked}\n\
|
||||
\n\
|
||||
override_dh_auto_install:\n\
|
||||
\tinstall -Dm755 target/release/{artifact} debian/{name}/usr/bin/{command}\n\
|
||||
\n\
|
||||
override_dh_auto_test:\n\
|
||||
\tcargo test --release --offline{locked}\n\
|
||||
\n\
|
||||
override_dh_update_autotools_config:\n\
|
||||
\n\
|
||||
override_dh_clean:\n\
|
||||
\t# dh_clean unlinks `*.orig` patch backups, but vendored crates\n\
|
||||
\t# ship files like `Cargo.toml.orig` that cargo's per-file\n\
|
||||
\t# checksums require on cold builds (chroots, Launchpad).\n\
|
||||
\tdh_clean -X .orig\n\
|
||||
\n\
|
||||
override_dh_auto_clean:\n\
|
||||
\tcargo clean\n",
|
||||
locked = locked,
|
||||
artifact = artifact,
|
||||
command = opts.command,
|
||||
name = opts.name,
|
||||
)
|
||||
vec![
|
||||
("locked".to_string(), locked.to_string()),
|
||||
("artifact".to_string(), artifact),
|
||||
]
|
||||
}
|
||||
|
||||
/// Name, version, description, homepage, license and first binary from
|
||||
@@ -501,14 +449,14 @@ fn parse_toolchain_channel(content: &str) -> Option<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::new::options::{License, SourceDir};
|
||||
use crate::new::options::{License, SourceDir, TemplateId};
|
||||
use serial_test::serial;
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn opts(source_dir: SourceDir) -> NewOptions {
|
||||
NewOptions {
|
||||
name: "mytool".into(),
|
||||
template: TemplateId::Rust,
|
||||
template: TemplateId::RUST,
|
||||
source_dir,
|
||||
upstream_version: "0.1.0".into(),
|
||||
revision: 1,
|
||||
@@ -534,7 +482,7 @@ mod tests {
|
||||
#[test]
|
||||
fn rust_template_shape() {
|
||||
let o = opts(SourceDir::Skeleton);
|
||||
let template = super::super::get(TemplateId::Rust).unwrap();
|
||||
let template = super::super::get(TemplateId::RUST).unwrap();
|
||||
|
||||
assert_eq!(template.architecture(&o), "any");
|
||||
assert_eq!(
|
||||
@@ -568,7 +516,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn rules_use_locked_only_with_lockfile() {
|
||||
let template = super::super::get(TemplateId::Rust).unwrap();
|
||||
let template = super::super::get(TemplateId::RUST).unwrap();
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
std::fs::write(dir.path().join("Cargo.toml"), "[package]\nname = \"x\"\n").unwrap();
|
||||
@@ -598,7 +546,7 @@ mod tests {
|
||||
command: "mytool".into(),
|
||||
..opts(SourceDir::Skeleton)
|
||||
};
|
||||
let template = super::super::get(TemplateId::Rust).unwrap();
|
||||
let template = super::super::get(TemplateId::RUST).unwrap();
|
||||
|
||||
let skeleton = template.skeleton(&o);
|
||||
let cargo_toml = skeleton
|
||||
@@ -618,7 +566,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn probe_reads_cargo_toml_lines() {
|
||||
let template = super::super::get(TemplateId::Rust).unwrap();
|
||||
let template = super::super::get(TemplateId::RUST).unwrap();
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
std::fs::write(
|
||||
@@ -670,7 +618,7 @@ mod tests {
|
||||
std::fs::create_dir_all(dir.path().join("src")).unwrap();
|
||||
std::fs::write(dir.path().join("src/main.rs"), "fn main() {}\n").unwrap();
|
||||
|
||||
let template = super::super::get(TemplateId::Rust).unwrap();
|
||||
let template = super::super::get(TemplateId::RUST).unwrap();
|
||||
let probe = template.probe(dir.path()).expect("probe result");
|
||||
assert_eq!(probe.name.as_deref(), Some("metaprobe"));
|
||||
assert_eq!(probe.version.as_deref(), Some("0.9.0"));
|
||||
@@ -687,7 +635,7 @@ mod tests {
|
||||
fn post_write_vendors_skeleton() {
|
||||
let dir = tempdir().unwrap();
|
||||
let o = opts(SourceDir::Skeleton);
|
||||
let template = super::super::get(TemplateId::Rust).unwrap();
|
||||
let template = super::super::get(TemplateId::RUST).unwrap();
|
||||
for file in template.skeleton(&o) {
|
||||
let path = dir.path().join(&file.path);
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
@@ -881,7 +829,7 @@ mod tests {
|
||||
/// no pin file leaves the field empty.
|
||||
#[test]
|
||||
fn probe_reports_the_toolchain_pin() {
|
||||
let template = super::super::get(TemplateId::Rust).unwrap();
|
||||
let template = super::super::get(TemplateId::RUST).unwrap();
|
||||
let cargo_toml = "[package]\nname = \"pinned\"\nversion = \"1.0.0\"\n";
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
|
||||
+18
-12
@@ -1,22 +1,28 @@
|
||||
//! The `shell` template: a single interpreted script installed to
|
||||
//! `/usr/bin` with plain `dh $@` plumbing.
|
||||
//!
|
||||
//! The data half lives in `data/templates/shell/manifest.yml`; the logic
|
||||
//! half here is the single-script probe pre-filling the wizard answers from
|
||||
//! the script file name (the same heuristic [`crate::new::detect`] bases
|
||||
//! its shell detection on) plus, for now, the skeleton script and its
|
||||
//! skeleton-only `debian/install` mapping (the manifest `files:` list takes
|
||||
//! those over once their bodies are static data).
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use super::{OutputFile, ProbeResult, Template};
|
||||
use super::{OutputFile, ProbeResult, TemplateHooks};
|
||||
use crate::new::options::{self, NewOptions, SourceDir};
|
||||
|
||||
/// Shell script / single interpreted file.
|
||||
pub struct Shell;
|
||||
/// The logic half of the shell template.
|
||||
pub struct Hooks;
|
||||
|
||||
impl Template for Shell {
|
||||
fn id(&self) -> crate::new::options::TemplateId {
|
||||
crate::new::options::TemplateId::Shell
|
||||
}
|
||||
/// The shell template's hooks, registered in the template registry.
|
||||
pub static HOOKS: Hooks = Hooks;
|
||||
|
||||
impl TemplateHooks for Hooks {
|
||||
/// A minimal executable script named after the command, with a `#!/bin/sh`
|
||||
/// shebang and an `echo` placeholder.
|
||||
fn skeleton(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
fn skeleton_files(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
vec![OutputFile::executable(
|
||||
format!("{}.sh", opts.command),
|
||||
format!(
|
||||
@@ -32,7 +38,7 @@ impl Template for Shell {
|
||||
/// Only for the skeleton mode: when packaging an existing tree the
|
||||
/// generated mapping would reference the non-existent skeleton script,
|
||||
/// so the user writes their own install file instead.
|
||||
fn debian(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
fn debian_files(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
if !matches!(opts.source_dir, SourceDir::Skeleton) {
|
||||
return Vec::new();
|
||||
}
|
||||
@@ -65,7 +71,7 @@ mod tests {
|
||||
fn opts() -> NewOptions {
|
||||
NewOptions {
|
||||
name: "mytool".into(),
|
||||
template: TemplateId::Shell,
|
||||
template: TemplateId::SHELL,
|
||||
source_dir: SourceDir::Skeleton,
|
||||
upstream_version: "0.1.0".into(),
|
||||
revision: 1,
|
||||
@@ -91,7 +97,7 @@ mod tests {
|
||||
#[test]
|
||||
fn shell_template_shape() {
|
||||
let o = opts();
|
||||
let template = super::super::get(TemplateId::Shell).unwrap();
|
||||
let template = super::super::get(TemplateId::SHELL).unwrap();
|
||||
|
||||
assert_eq!(template.architecture(&o), "all");
|
||||
assert!(template.build_depends(&o).is_empty());
|
||||
@@ -111,7 +117,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn shell_probe_reads_script_file_name() {
|
||||
let template = super::super::get(TemplateId::Shell).unwrap();
|
||||
let template = super::super::get(TemplateId::SHELL).unwrap();
|
||||
|
||||
// The .sh extension is stripped, the stem sanitized into a package
|
||||
// name and command.
|
||||
|
||||
+1
-1
@@ -126,7 +126,7 @@ mod tests {
|
||||
fn opts() -> NewOptions {
|
||||
NewOptions {
|
||||
name: "mytool".into(),
|
||||
template: TemplateId::Shell,
|
||||
template: TemplateId::SHELL,
|
||||
source_dir: SourceDir::Skeleton,
|
||||
upstream_version: "0.1.0".into(),
|
||||
revision: 1,
|
||||
|
||||
Reference in New Issue
Block a user