251 lines
8.4 KiB
Rust
251 lines
8.4 KiB
Rust
//! 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(),
|
|
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 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())
|
|
);
|
|
}
|
|
}
|