use crate::declare_detector;
use crate::diagnostic::{Action, Diagnostic, FilesystemAction};
use crate::{FixerError, FixerPreferences, LintianIssue, Visibility};
use debian_workspace::Workspace;
use std::path::{Path, PathBuf};
const SUBSTITUTIONS: [(&str, &str); 2] = [
("$(DEB_BUILD_OPTS)", "$(DEB_BUILD_OPTIONS)"),
("${DEB_BUILD_OPTS}", "${DEB_BUILD_OPTIONS}"),
];
const DESCRIPTION: &str =
"debian/rules refers to $(DEB_BUILD_OPTS) instead of $(DEB_BUILD_OPTIONS).";
const LABEL: &str = "Use DEB_BUILD_OPTIONS rather than DEB_BUILD_OPTS in debian/rules.";
pub fn detect(
ws: &dyn Workspace,
_preferences: &FixerPreferences,
) -> Result<Vec<Diagnostic>, FixerError> {
let bytes = match ws.read_file(Path::new("debian/rules"))? {
Some(b) => b,
None => return Ok(Vec::new()),
};
let Ok(content) = std::str::from_utf8(&bytes) else {
return Ok(Vec::new());
};
if ws.parsed_rules().is_err() {
return Ok(Vec::new());
}
let actions: Vec<Action> = SUBSTITUTIONS
.iter()
.filter(|(from, _)| {
content
.lines()
.filter(|line| !line.trim_start().starts_with('#'))
.any(|line| line.contains(from))
})
.map(|(from, to)| {
Action::Filesystem(FilesystemAction::Substitute {
file: PathBuf::from("debian/rules"),
from: (*from).to_string(),
to: (*to).to_string(),
})
})
.collect();
if actions.is_empty() {
return Ok(Vec::new());
}
let issue = LintianIssue::source_with_info(
"debian-rules-uses-deb-build-opts",
Visibility::Warning,
vec!["[debian/rules]".to_string()],
);
Ok(vec![Diagnostic::with_actions(
issue,
DESCRIPTION,
LABEL,
actions,
)])
}
declare_detector! {
name: "debian-rules-uses-deb-build-opts",
tags: ["debian-rules-uses-deb-build-opts"],
triggers: [debian_workspace::Trigger::File("debian/rules")],
detect: |ws, prefs| detect(ws, prefs),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::detector::Detector;
use crate::{FixerPreferences, Version};
use std::fs;
use tempfile::TempDir;
fn run_apply(base: &Path) -> Result<crate::FixerResult, FixerError> {
let version: Version = "1.0".parse().unwrap();
let adapter = DetectorImpl;
{
let ws = debian_workspace::fs_workspace::FsWorkspace::new(
base,
Some("test".into()),
Some(version.clone()),
);
adapter.apply(&ws, &FixerPreferences::default())
}
}
fn write_rules(base: &Path, contents: &str) -> PathBuf {
let debian = base.join("debian");
fs::create_dir(&debian).unwrap();
let path = debian.join("rules");
fs::write(&path, contents).unwrap();
path
}
#[test]
fn test_replace_paren_form() {
let tmp = TempDir::new().unwrap();
let path = write_rules(
tmp.path(),
"#!/usr/bin/make -f\n\nifneq (,$(filter noopt,$(DEB_BUILD_OPTS)))\nCFLAGS += -O0\nendif\n\n%:\n\tdh $@\n",
);
let result = run_apply(tmp.path()).unwrap();
assert_eq!(result.description, LABEL);
assert_eq!(
fs::read_to_string(&path).unwrap(),
"#!/usr/bin/make -f\n\nifneq (,$(filter noopt,$(DEB_BUILD_OPTIONS)))\nCFLAGS += -O0\nendif\n\n%:\n\tdh $@\n",
);
}
#[test]
fn test_replace_brace_form() {
let tmp = TempDir::new().unwrap();
let path = write_rules(
tmp.path(),
"#!/usr/bin/make -f\n\nOPTS = ${DEB_BUILD_OPTS}\n\n%:\n\tdh $@\n",
);
run_apply(tmp.path()).unwrap();
assert_eq!(
fs::read_to_string(&path).unwrap(),
"#!/usr/bin/make -f\n\nOPTS = ${DEB_BUILD_OPTIONS}\n\n%:\n\tdh $@\n",
);
}
#[test]
fn test_multiple_occurrences() {
let tmp = TempDir::new().unwrap();
let path = write_rules(
tmp.path(),
"#!/usr/bin/make -f\n\nA = $(DEB_BUILD_OPTS)\nB = $(DEB_BUILD_OPTS)\n\n%:\n\tdh $@\n",
);
run_apply(tmp.path()).unwrap();
assert_eq!(
fs::read_to_string(&path).unwrap(),
"#!/usr/bin/make -f\n\nA = $(DEB_BUILD_OPTIONS)\nB = $(DEB_BUILD_OPTIONS)\n\n%:\n\tdh $@\n",
);
}
#[test]
fn test_both_forms() {
let tmp = TempDir::new().unwrap();
let path = write_rules(
tmp.path(),
"#!/usr/bin/make -f\n\nA = $(DEB_BUILD_OPTS)\nB = ${DEB_BUILD_OPTS}\n\n%:\n\tdh $@\n",
);
let result = run_apply(tmp.path()).unwrap();
assert_eq!(result.fixed_lintian_issues.len(), 1);
assert_eq!(
fs::read_to_string(&path).unwrap(),
"#!/usr/bin/make -f\n\nA = $(DEB_BUILD_OPTIONS)\nB = ${DEB_BUILD_OPTIONS}\n\n%:\n\tdh $@\n",
);
}
#[test]
fn test_comment_only_not_flagged() {
let tmp = TempDir::new().unwrap();
let original =
"#!/usr/bin/make -f\n\n# historically used $(DEB_BUILD_OPTS) here\n\n%:\n\tdh $@\n";
let path = write_rules(tmp.path(), original);
assert!(matches!(run_apply(tmp.path()), Err(FixerError::NoChanges)));
assert_eq!(fs::read_to_string(&path).unwrap(), original);
}
#[test]
fn test_comment_cleaned_up_alongside_real_use() {
let tmp = TempDir::new().unwrap();
let path = write_rules(
tmp.path(),
"#!/usr/bin/make -f\n\n# see $(DEB_BUILD_OPTS) above\nA = $(DEB_BUILD_OPTS)\n\n%:\n\tdh $@\n",
);
run_apply(tmp.path()).unwrap();
assert_eq!(
fs::read_to_string(&path).unwrap(),
"#!/usr/bin/make -f\n\n# see $(DEB_BUILD_OPTIONS) above\nA = $(DEB_BUILD_OPTIONS)\n\n%:\n\tdh $@\n",
);
}
#[test]
fn test_already_correct() {
let tmp = TempDir::new().unwrap();
write_rules(
tmp.path(),
"#!/usr/bin/make -f\n\nA = $(DEB_BUILD_OPTIONS)\n\n%:\n\tdh $@\n",
);
assert!(matches!(run_apply(tmp.path()), Err(FixerError::NoChanges)));
}
#[test]
fn test_no_rules_file() {
let tmp = TempDir::new().unwrap();
assert!(matches!(run_apply(tmp.path()), Err(FixerError::NoChanges)));
}
}