use crate::fix_build::{BuildFixer, InterimError};
use crate::installer::{Error as InstallerError, InstallationScope, Installer};
use crate::session::Session;
use buildlog_consultant::problems::common::{
MinimumAutoconfTooOld, MissingAutoconfMacro, MissingGitIdentity, MissingGnulibDirectory,
MissingGoSumEntry, MissingSecretGpgKey,
};
use buildlog_consultant::Problem;
use std::io::{Seek, Write};
pub struct GnulibDirectoryFixer<'a> {
session: &'a dyn Session,
}
impl std::fmt::Debug for GnulibDirectoryFixer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GnulibDirectoryFixer").finish()
}
}
impl std::fmt::Display for GnulibDirectoryFixer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "GnulibDirectoryFixer")
}
}
impl<'a> GnulibDirectoryFixer<'a> {
pub fn new(session: &'a dyn Session) -> Self {
Self { session }
}
}
impl<'a> BuildFixer<InstallerError> for GnulibDirectoryFixer<'a> {
fn can_fix(&self, problem: &dyn Problem) -> bool {
problem
.as_any()
.downcast_ref::<MissingGnulibDirectory>()
.is_some()
}
fn fix(&self, _problem: &dyn Problem) -> Result<bool, InterimError<InstallerError>> {
self.session
.command(vec!["./gnulib.sh"])
.check_call()
.unwrap();
Ok(true)
}
}
pub struct GitIdentityFixer<'a> {
session: &'a dyn Session,
}
impl std::fmt::Debug for GitIdentityFixer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GitIdentityFixer").finish()
}
}
impl std::fmt::Display for GitIdentityFixer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "GitIdentityFixer")
}
}
impl<'a> GitIdentityFixer<'a> {
pub fn new(session: &'a dyn Session) -> Self {
Self { session }
}
}
impl<'a> BuildFixer<InstallerError> for GitIdentityFixer<'a> {
fn can_fix(&self, problem: &dyn Problem) -> bool {
problem
.as_any()
.downcast_ref::<MissingGitIdentity>()
.is_some()
}
fn fix(&self, _problem: &dyn Problem) -> Result<bool, InterimError<InstallerError>> {
for name in ["user.email", "user.name"] {
let output = std::process::Command::new("git")
.arg("config")
.arg("--global")
.arg(name)
.output()
.unwrap();
let value = String::from_utf8(output.stdout).unwrap();
self.session
.command(vec!["git", "config", "--global", name, &value])
.check_call()
.unwrap();
}
Ok(true)
}
}
pub struct SecretGpgKeyFixer<'a> {
session: &'a dyn Session,
}
impl std::fmt::Debug for SecretGpgKeyFixer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SecretGpgKeyFixer").finish()
}
}
impl std::fmt::Display for SecretGpgKeyFixer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SecretGpgKey")
}
}
impl<'a> SecretGpgKeyFixer<'a> {
pub fn new(session: &'a dyn Session) -> Self {
Self { session }
}
}
impl<'a> BuildFixer<InstallerError> for SecretGpgKeyFixer<'a> {
fn can_fix(&self, problem: &dyn Problem) -> bool {
problem
.as_any()
.downcast_ref::<MissingSecretGpgKey>()
.is_some()
}
fn fix(&self, _problem: &dyn Problem) -> Result<bool, InterimError<InstallerError>> {
let mut td = tempfile::tempfile().unwrap();
let script = br#"""Key-Type: 1
Key-Length: 4096
Subkey-Type: 1
Subkey-Length: 4096
Name-Real: Dummy Key for ognibuild
Name-Email: dummy@example.com
Expire-Date: 0
Passphrase: ""
"""#;
td.write_all(script).unwrap();
td.seek(std::io::SeekFrom::Start(0)).unwrap();
self.session
.command(vec!["gpg", "--gen-key", "--batch", "/dev/stdin"])
.stdin(td.into())
.check_call()
.unwrap();
Ok(true)
}
}
pub struct MinimumAutoconfFixer<'a> {
session: &'a dyn Session,
}
impl std::fmt::Debug for MinimumAutoconfFixer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MinimumAutoconfFixer").finish()
}
}
impl std::fmt::Display for MinimumAutoconfFixer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MinimumAutoconfFixer")
}
}
impl<'a> MinimumAutoconfFixer<'a> {
pub fn new(session: &'a dyn Session) -> Self {
Self { session }
}
}
impl<'a> BuildFixer<InstallerError> for MinimumAutoconfFixer<'a> {
fn can_fix(&self, problem: &dyn Problem) -> bool {
problem
.as_any()
.downcast_ref::<MinimumAutoconfTooOld>()
.is_some()
}
fn fix(&self, problem: &dyn Problem) -> Result<bool, InterimError<InstallerError>> {
let problem = problem
.as_any()
.downcast_ref::<MinimumAutoconfTooOld>()
.unwrap();
if let Some(name) = ["configure.ac", "configure.in"].into_iter().next() {
let p = self.session.external_path(std::path::Path::new(name));
let f = std::fs::File::open(&p).unwrap();
let buf = std::io::BufReader::new(f);
use std::io::BufRead;
let mut lines = buf.lines().map(|l| l.unwrap()).collect::<Vec<_>>();
let mut found = false;
for line in lines.iter_mut() {
let m = lazy_regex::regex_find!(r"AC_PREREQ\((.*)\)", &line);
if m.is_none() {
continue;
}
*line = format!("AC_PREREQ({})", problem.0);
found = true;
}
if !found {
lines.insert(0, format!("AC_PREREQ({})", problem.0));
}
std::fs::write(
self.session.external_path(std::path::Path::new(name)),
lines.concat(),
)
.unwrap();
return Ok(true);
}
Ok(false)
}
}
pub struct MissingGoSumEntryFixer<'a> {
session: &'a dyn Session,
}
impl std::fmt::Debug for MissingGoSumEntryFixer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MissingGoSumEntryFixer").finish()
}
}
impl std::fmt::Display for MissingGoSumEntryFixer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MissingGoSumEntryFixer")
}
}
impl<'a> MissingGoSumEntryFixer<'a> {
pub fn new(session: &'a dyn Session) -> Self {
Self { session }
}
}
impl<'a> BuildFixer<InstallerError> for MissingGoSumEntryFixer<'a> {
fn can_fix(&self, problem: &dyn Problem) -> bool {
problem
.as_any()
.downcast_ref::<MissingGoSumEntry>()
.is_some()
}
fn fix(&self, problem: &dyn Problem) -> Result<bool, InterimError<InstallerError>> {
let problem = problem
.as_any()
.downcast_ref::<MissingGoSumEntry>()
.unwrap();
self.session
.command(vec!["go", "mod", "download", &problem.package])
.check_call()
.unwrap();
Ok(true)
}
}
pub struct UnexpandedAutoconfMacroFixer<'a> {
session: &'a dyn Session,
installer: &'a dyn Installer,
}
impl std::fmt::Debug for UnexpandedAutoconfMacroFixer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UnexpandedAutoconfMacroFixer").finish()
}
}
impl std::fmt::Display for UnexpandedAutoconfMacroFixer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "UnexpandedAutoconfMacroFixer")
}
}
impl<'a> UnexpandedAutoconfMacroFixer<'a> {
pub fn new(session: &'a dyn Session, installer: &'a dyn Installer) -> Self {
Self { session, installer }
}
}
impl<'a> BuildFixer<InstallerError> for UnexpandedAutoconfMacroFixer<'a> {
fn can_fix(&self, problem: &dyn Problem) -> bool {
problem
.as_any()
.downcast_ref::<MissingAutoconfMacro>()
.is_some()
}
fn fix(&self, problem: &dyn Problem) -> Result<bool, InterimError<InstallerError>> {
let problem = problem
.as_any()
.downcast_ref::<MissingAutoconfMacro>()
.unwrap();
let dep = crate::dependencies::autoconf::AutoconfMacroDependency::new(&problem.r#macro);
self.installer
.install(&dep, InstallationScope::Global)
.unwrap();
self.session
.command(vec!["autoconf", "-f"])
.check_call()
.unwrap();
Ok(true)
}
}
pub struct InstallFixer<'a> {
installer: &'a dyn crate::installer::Installer,
scope: crate::installer::InstallationScope,
}
impl std::fmt::Debug for InstallFixer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InstallFixer").finish()
}
}
impl std::fmt::Display for InstallFixer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "upstream requirement fixer")
}
}
impl<'a> InstallFixer<'a> {
pub fn new(
installer: &'a dyn crate::installer::Installer,
scope: crate::installer::InstallationScope,
) -> Self {
Self { installer, scope }
}
}
impl<'a> BuildFixer<crate::installer::Error> for InstallFixer<'a> {
fn can_fix(&self, error: &dyn Problem) -> bool {
let req = crate::buildlog::problem_to_dependency(error);
req.is_some()
}
fn fix(&self, error: &dyn Problem) -> Result<bool, InterimError<crate::installer::Error>> {
let req = crate::buildlog::problem_to_dependency(error);
if let Some(req) = req {
match self.installer.install(req.as_ref(), self.scope) {
Ok(()) => {
log::debug!("Successfully installed dependency: {:?}", req);
Ok(true)
}
Err(crate::installer::Error::UnknownDependencyFamily) => {
log::warn!("Cannot install dependency from unknown family: {:?}", req);
Ok(false) }
Err(e) => {
log::error!("Failed to install dependency {:?}: {}", req, e);
Err(InterimError::Other(e))
}
}
} else {
Ok(false)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::installer::{InstallationScope, NullInstaller};
use buildlog_consultant::Problem;
use std::borrow::Cow;
struct UnknownProblem {
description: String,
}
impl Problem for UnknownProblem {
fn kind(&self) -> Cow<'_, str> {
Cow::Borrowed("unknown")
}
fn json(&self) -> serde_json::Value {
serde_json::json!({
"kind": "unknown",
"description": self.description
})
}
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
self
}
}
impl std::fmt::Display for UnknownProblem {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.description)
}
}
impl std::fmt::Debug for UnknownProblem {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "UnknownProblem({})", self.description)
}
}
#[test]
fn test_install_fixer_handles_unknown_problem() {
let installer = NullInstaller {};
let fixer = InstallFixer::new(&installer, InstallationScope::Global);
let problem = UnknownProblem {
description: "some unknown build problem".to_string(),
};
let result = fixer.fix(&problem);
assert!(result.is_ok());
assert_eq!(result.unwrap(), false);
}
#[test]
fn test_install_fixer_handles_unknown_dependency_family() {
use buildlog_consultant::problems::common::MissingCommand;
let installer = NullInstaller {};
let fixer = InstallFixer::new(&installer, InstallationScope::Global);
let problem = MissingCommand("nonexistent-command".to_string());
let result = fixer.fix(&problem);
assert!(result.is_ok());
assert_eq!(result.unwrap(), false);
}
}