new: add interactive wizard and remaining ecosystem templates
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
//! 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).
|
||||
|
||||
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};
|
||||
|
||||
/// C/C++ with Autotools (`configure.ac`).
|
||||
pub struct Autotools;
|
||||
|
||||
impl Template for Autotools {
|
||||
fn id(&self) -> TemplateId {
|
||||
TemplateId::Autotools
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
vec![
|
||||
OutputFile::new(
|
||||
"configure.ac",
|
||||
format!(
|
||||
"AC_INIT([{name}], [{version}])\n\
|
||||
AM_INIT_AUTOMAKE([foreign])\n\
|
||||
AC_PROG_CC\n\
|
||||
AC_CONFIG_FILES([Makefile])\n\
|
||||
AC_OUTPUT\n",
|
||||
name = opts.name,
|
||||
version = opts.upstream_version,
|
||||
),
|
||||
),
|
||||
OutputFile::new(
|
||||
"Makefile.am",
|
||||
format!(
|
||||
"bin_PROGRAMS = {command}\n\
|
||||
{command}_SOURCES = hello.c\n",
|
||||
command = opts.command,
|
||||
),
|
||||
),
|
||||
hello_c(opts),
|
||||
]
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
];
|
||||
if uses_gettext(opts) {
|
||||
deps.push("gettext".to_string());
|
||||
}
|
||||
deps
|
||||
}
|
||||
|
||||
fn architecture(&self, _opts: &NewOptions) -> &'static str {
|
||||
"any"
|
||||
}
|
||||
|
||||
/// Package name and version from the `AC_INIT` macro of `configure.ac`.
|
||||
fn probe(&self, dir: &Path) -> Option<ProbeResult> {
|
||||
let content = std::fs::read_to_string(dir.join("configure.ac")).ok()?;
|
||||
static AC_INIT_REGEX: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
|
||||
// AC_INIT([name], [version]) — the autoconf quotes are optional.
|
||||
let regex = AC_INIT_REGEX.get_or_init(|| {
|
||||
Regex::new(
|
||||
r"AC_INIT\s*\(\s*(?:\[([^\]]*)\]|([^,\s\[]+))\s*,\s*(?:\[([^\]]*)\]|([^,\s\[]+))",
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
let caps = regex.captures(&content)?;
|
||||
let value = |bracketed: usize, bare: usize| {
|
||||
caps.get(bracketed)
|
||||
.or_else(|| caps.get(bare))
|
||||
.map(|v| v.as_str().trim().to_string())
|
||||
.filter(|v| !v.is_empty())
|
||||
};
|
||||
let name = value(1, 2)?;
|
||||
let version = value(3, 4);
|
||||
Some(ProbeResult {
|
||||
name: Some(name),
|
||||
version,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the packaged `configure.ac` sets up GNU gettext (`AM_GNU_GETTEXT`
|
||||
/// macro): those builds need the `gettext` package. Only meaningful when
|
||||
/// packaging an existing tree (the generated skeleton carries no gettext).
|
||||
fn uses_gettext(opts: &NewOptions) -> bool {
|
||||
source_dir_of(opts).is_some_and(|dir| {
|
||||
std::fs::read_to_string(dir.join("configure.ac"))
|
||||
.is_ok_and(|content| content.contains("AM_GNU_GETTEXT"))
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::new::options::{License, SourceDir};
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn opts() -> NewOptions {
|
||||
NewOptions {
|
||||
name: "mytool".into(),
|
||||
template: TemplateId::Autotools,
|
||||
source_dir: SourceDir::Skeleton,
|
||||
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 autotools_template_shape() {
|
||||
let o = opts();
|
||||
let template = super::super::get(TemplateId::Autotools).unwrap();
|
||||
|
||||
assert_eq!(template.architecture(&o), "any");
|
||||
assert_eq!(
|
||||
template.build_depends(&o),
|
||||
vec![
|
||||
"autoconf".to_string(),
|
||||
"automake".to_string(),
|
||||
"libtool".to_string()
|
||||
]
|
||||
);
|
||||
assert_eq!(template.rules_dh_line(), "dh $@");
|
||||
assert!(template.rules_extra(&o).is_empty());
|
||||
assert!(template.debian(&o).is_empty());
|
||||
|
||||
let skeleton = template.skeleton(&o);
|
||||
let configure = skeleton
|
||||
.iter()
|
||||
.find(|f| f.path == "configure.ac")
|
||||
.expect("configure.ac skeleton");
|
||||
assert!(
|
||||
configure
|
||||
.contents
|
||||
.starts_with("AC_INIT([mytool], [0.1.0])\n")
|
||||
);
|
||||
let makefile_am = skeleton
|
||||
.iter()
|
||||
.find(|f| f.path == "Makefile.am")
|
||||
.expect("Makefile.am skeleton");
|
||||
assert!(makefile_am.contents.contains("bin_PROGRAMS = mytool"));
|
||||
assert!(makefile_am.contents.contains("mytool_SOURCES = hello.c"));
|
||||
assert!(skeleton.iter().any(|f| f.path == "hello.c"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn autotools_probe_reads_ac_init() {
|
||||
let template = super::super::get(TemplateId::Autotools).unwrap();
|
||||
|
||||
// Bracketed form (the generated skeleton's own shape).
|
||||
let dir = tempdir().unwrap();
|
||||
std::fs::write(
|
||||
dir.path().join("configure.ac"),
|
||||
"AC_INIT([mytool], [0.1.0])\nAM_INIT_AUTOMAKE([foreign])\n",
|
||||
)
|
||||
.unwrap();
|
||||
let probe = template.probe(dir.path()).expect("probe result");
|
||||
assert_eq!(probe.name.as_deref(), Some("mytool"));
|
||||
assert_eq!(probe.version.as_deref(), Some("0.1.0"));
|
||||
|
||||
// Bare form with a bug-report address as the third argument.
|
||||
let dir = tempdir().unwrap();
|
||||
std::fs::write(
|
||||
dir.path().join("configure.ac"),
|
||||
"AC_INIT(mytool, 1.2.3, bugs@example.com)\n",
|
||||
)
|
||||
.unwrap();
|
||||
let probe = template.probe(dir.path()).expect("probe result");
|
||||
assert_eq!(probe.name.as_deref(), Some("mytool"));
|
||||
assert_eq!(probe.version.as_deref(), Some("1.2.3"));
|
||||
|
||||
// Name with spaces inside the brackets.
|
||||
let dir = tempdir().unwrap();
|
||||
std::fs::write(
|
||||
dir.path().join("configure.ac"),
|
||||
"AC_INIT([My Tool], [2.0])\n",
|
||||
)
|
||||
.unwrap();
|
||||
let probe = template.probe(dir.path()).expect("probe result");
|
||||
assert_eq!(probe.name.as_deref(), Some("My Tool"));
|
||||
|
||||
// No configure.ac: silent None.
|
||||
let dir = tempdir().unwrap();
|
||||
assert!(template.probe(dir.path()).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gettext_detected_in_configure_ac() {
|
||||
// Skeleton: the generated configure.ac has no gettext.
|
||||
let o = opts();
|
||||
assert!(!uses_gettext(&o));
|
||||
assert!(
|
||||
!super::super::get(TemplateId::Autotools)
|
||||
.unwrap()
|
||||
.build_depends(&o)
|
||||
.contains(&"gettext".to_string())
|
||||
);
|
||||
|
||||
// Existing tree with AM_GNU_GETTEXT: gettext joins Build-Depends.
|
||||
let dir = tempdir().unwrap();
|
||||
std::fs::write(
|
||||
dir.path().join("configure.ac"),
|
||||
"AC_INIT([mytool], [0.1.0])\nAM_GNU_GETTEXT([external])\n",
|
||||
)
|
||||
.unwrap();
|
||||
let o = NewOptions {
|
||||
source_dir: SourceDir::Path(dir.path().to_path_buf()),
|
||||
..opts()
|
||||
};
|
||||
assert!(uses_gettext(&o));
|
||||
assert!(
|
||||
super::super::get(TemplateId::Autotools)
|
||||
.unwrap()
|
||||
.build_depends(&o)
|
||||
.contains(&"gettext".to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user