Move the four templates' static file bodies into .tpl files under
data/templates/<id>/, referenced by their manifests' files: lists —
the shell skeleton script (executable, {command}-named) with its
skeleton-only debian/install mapping, the empty template's stub README,
the makefile hello.c/Makefile skeleton with its skeleton-only install
mapping, and go's go.mod/main.go skeleton (the go directive of go.mod
stays a literal: nothing about it is answer-derived).
The empty template ends up hookless — zero Rust, its registry entry
points at no hooks — and src/new/templates/empty.rs is deleted. The
shell and go hooks shrink to their probes (plus go's {go_import_path}
context value); the makefile hooks keep only the existing-tree hint
probing the packaged Makefile for a phony install: target, since that
heuristic reads the tree and cannot be data.
180 lines
6.8 KiB
Rust
180 lines
6.8 KiB
Rust
//! 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. Everything is manifest data
|
|
//! (`data/templates/makefile/manifest.yml`: the `hello.c`/`Makefile`
|
|
//! skeleton and the skeleton-only `debian/install` mapping) except the
|
|
//! hint logged here for an existing tree, which probes its Makefile for a
|
|
//! phony `install:` target to tell whether `dh_auto_install` will run
|
|
//! `make install` (a skeleton's target is known by construction, so
|
|
//! skeletons need no code).
|
|
|
|
use std::path::Path;
|
|
|
|
use super::TemplateHooks;
|
|
use crate::new::options::{NewOptions, SourceDir};
|
|
|
|
/// The logic half of the makefile template.
|
|
pub struct Hooks;
|
|
|
|
/// The makefile template's hooks, registered in the template registry.
|
|
pub static HOOKS: Hooks = Hooks;
|
|
|
|
impl TemplateHooks for Hooks {
|
|
/// No files of its own: the manifest carries the skeleton-only
|
|
/// `debian/install` mapping. When packaging an existing tree, probe
|
|
/// its Makefile for a phony `install:` target and say which install
|
|
/// step `dh_auto_install` will take (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_files(&self, opts: &NewOptions) -> Vec<super::OutputFile> {
|
|
if matches!(opts.source_dir, SourceDir::Skeleton) {
|
|
return Vec::new();
|
|
}
|
|
let dir = super::source_dir_of(opts);
|
|
if let Some(dir) = dir.as_deref()
|
|
&& phony_install_target(&dir.join("Makefile")).is_some()
|
|
{
|
|
log::info!(
|
|
"Makefile carries a phony 'install:' target: \
|
|
dh_auto_install will run 'make install DESTDIR=...'"
|
|
);
|
|
} else {
|
|
log::info!(
|
|
"Makefile has no phony 'install:' target: \
|
|
dh_auto_install will skip the install step; write a \
|
|
debian/install file to map build artifacts manually"
|
|
);
|
|
}
|
|
Vec::new()
|
|
}
|
|
}
|
|
|
|
/// The name of the phony `install:` target of the Makefile at `path`, when
|
|
/// there is one: an unindented `install:` rule whose name also appears in a
|
|
/// `.PHONY:` declaration. `None` when the file is missing or carries no such
|
|
/// target. (A deliberately minimal line-oriented heuristic, like the other
|
|
/// project-file readers of the templates.)
|
|
pub fn phony_install_target(path: &Path) -> Option<String> {
|
|
let content = std::fs::read_to_string(path).ok()?;
|
|
let mut has_install_rule = false;
|
|
let mut phony_mentions_install = false;
|
|
for line in content.lines() {
|
|
if let Some(phony) = line.strip_prefix(".PHONY:")
|
|
&& phony.split_whitespace().any(|target| target == "install")
|
|
{
|
|
phony_mentions_install = true;
|
|
}
|
|
// Target rules start at column 0; recipes are indented, and special
|
|
// targets, comments and directives are excluded by the prefix check.
|
|
if !line.starts_with(['\t', ' ', '.', '#']) && line.starts_with("install:") {
|
|
has_install_rule = true;
|
|
}
|
|
}
|
|
(has_install_rule && phony_mentions_install).then(|| "install".to_string())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::new::options::{License, SourceDir, TemplateId};
|
|
use tempfile::tempdir;
|
|
|
|
fn opts(source_dir: SourceDir) -> NewOptions {
|
|
NewOptions {
|
|
name: "mytool".into(),
|
|
template: TemplateId::MAKEFILE,
|
|
source_dir,
|
|
upstream_version: "0.1.0".into(),
|
|
revision: 1,
|
|
summary: "A tool".into(),
|
|
long_description: "A tool".into(),
|
|
homepage: None,
|
|
license: License::Mit,
|
|
command: "mytool".into(),
|
|
maintainer: ("Jane".into(), "jane@example.com".into()),
|
|
dist: "ubuntu".into(),
|
|
series: "resolute".into(),
|
|
release: false,
|
|
depends: Vec::new(),
|
|
source_format: crate::new::options::SourceFormat::Quilt,
|
|
orig: Some(crate::new::options::OrigOrigin::Snapshot),
|
|
git: true,
|
|
autopkgtest: false,
|
|
pkg_config: false,
|
|
watch: None,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn makefile_template_shape() {
|
|
let o = opts(SourceDir::Skeleton);
|
|
let template = super::super::get(TemplateId::MAKEFILE).unwrap();
|
|
|
|
assert_eq!(template.architecture(&o), "any");
|
|
assert_eq!(
|
|
template.build_depends(&o),
|
|
vec!["build-essential".to_string()]
|
|
);
|
|
assert_eq!(template.rules_dh_line(), "dh $@");
|
|
assert!(template.rules_extra(&o).is_empty());
|
|
|
|
// Skeleton (manifest data): hello.c + Makefile with all/install/clean
|
|
// targets, and the phony install target maps to debian/install.
|
|
let skeleton = template.skeleton(&o);
|
|
assert!(skeleton.iter().any(|f| f.path == "hello.c"));
|
|
let makefile = skeleton
|
|
.iter()
|
|
.find(|f| f.path == "Makefile")
|
|
.expect("Makefile skeleton");
|
|
assert!(makefile.contents.contains("all: mytool\n"));
|
|
assert!(
|
|
makefile
|
|
.contents
|
|
.contains("install -Dm755 mytool $(DESTDIR)$(PREFIX)/bin/mytool")
|
|
);
|
|
assert!(makefile.contents.contains(".PHONY: all install clean"));
|
|
assert!(
|
|
makefile
|
|
.contents
|
|
.contains("\t$(CC) $(CFLAGS) -o $@ hello.c")
|
|
);
|
|
|
|
let debian = template.debian(&o);
|
|
assert_eq!(debian.len(), 1);
|
|
assert_eq!(debian[0].path, "debian/install");
|
|
assert_eq!(debian[0].contents, "mytool usr/bin/mytool\n");
|
|
|
|
// Existing tree: nothing is emitted (the probe log only).
|
|
let o = opts(SourceDir::Here);
|
|
assert!(template.debian(&o).is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn phony_install_target_detection() {
|
|
let dir = tempdir().unwrap();
|
|
let path = dir.path().join("Makefile");
|
|
|
|
// Phony install target: detected.
|
|
std::fs::write(
|
|
&path,
|
|
"all:\n\t@echo\n\n.PHONY: all install clean\ninstall:\n\t@echo install\n",
|
|
)
|
|
.unwrap();
|
|
assert!(phony_install_target(&path).is_some());
|
|
|
|
// install: without .PHONY: not detected.
|
|
std::fs::write(&path, "all:\n\t@echo\ninstall:\n\t@echo install\n").unwrap();
|
|
assert!(phony_install_target(&path).is_none());
|
|
|
|
// .PHONY mentioning install but no install: rule: not detected.
|
|
std::fs::write(&path, ".PHONY: install\nall:\n\t@echo\n").unwrap();
|
|
assert!(phony_install_target(&path).is_none());
|
|
|
|
// Missing file.
|
|
assert!(phony_install_target(&dir.path().join("missing.mk")).is_none());
|
|
}
|
|
}
|