new: add interactive wizard and remaining ecosystem templates
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
//! 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.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use super::{OutputFile, Template, source_dir_of};
|
||||
use crate::new::options::{NewOptions, SourceDir, TemplateId};
|
||||
|
||||
/// Generic Makefile-based project.
|
||||
pub struct Makefile;
|
||||
|
||||
impl Template for Makefile {
|
||||
fn id(&self) -> TemplateId {
|
||||
TemplateId::Makefile
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
vec![
|
||||
super::meson::hello_c(opts),
|
||||
OutputFile::new(
|
||||
"Makefile",
|
||||
format!(
|
||||
"CC ?= cc\n\
|
||||
CFLAGS ?= -O2 -Wall -Wextra\n\
|
||||
PREFIX ?= /usr\n\
|
||||
\n\
|
||||
all: {command}\n\
|
||||
\n\
|
||||
{command}: hello.c\n\
|
||||
\t$(CC) $(CFLAGS) -o $@ hello.c\n\
|
||||
\n\
|
||||
install: {command}\n\
|
||||
\tinstall -Dm755 {command} $(DESTDIR)$(PREFIX)/bin/{command}\n\
|
||||
\n\
|
||||
clean:\n\
|
||||
\trm -f {command}\n\
|
||||
\n\
|
||||
.PHONY: all install clean\n",
|
||||
command = opts.command,
|
||||
),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
/// `debian/install` mapping the built binary into `/usr/bin`, generated
|
||||
/// only when the packaged Makefile carries a phony `install:` target:
|
||||
/// for skeletons that is known by construction; when packaging an
|
||||
/// 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> {
|
||||
match &opts.source_dir {
|
||||
SourceDir::Skeleton => {
|
||||
vec![OutputFile::new(
|
||||
"debian/install",
|
||||
format!("{} usr/bin/{}\n", opts.command, opts.command),
|
||||
)]
|
||||
}
|
||||
_ => {
|
||||
if let Some(dir) = source_dir_of(opts)
|
||||
&& 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
/// 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};
|
||||
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(),
|
||||
native: false,
|
||||
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: hello.c + Makefile with all/install/clean, 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 (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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user