Skip to content

Commit

Permalink
[make] full implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Wandalen committed Oct 16, 2024
1 parent 1ff9575 commit 564cff4
Show file tree
Hide file tree
Showing 34 changed files with 2,699 additions and 140 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ members = [
"display",
"file",
"fs",
"ftw",
"make",
"ftw",
"m4",
"m4/test-manager",
"gettext-rs",
Expand Down
3 changes: 2 additions & 1 deletion make/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ repository = "https://github.com/rustcoreutils/posixutils-rs.git"
[dependencies]
plib = { path = "../plib" }
clap.workspace = true
libc.workspace = true
gettext-rs.workspace = true

const_format = "0.2"
makefile-lossless = "0.1"
rowan = "0.15"

[[bin]]
name = "make"
Expand Down
97 changes: 96 additions & 1 deletion make/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
// SPDX-License-Identifier: MIT
//

use std::collections::{BTreeMap, BTreeSet};

/// Represents the configuration of the make utility
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Config {
Expand All @@ -18,16 +20,109 @@ pub struct Config {
pub silent: bool,
/// Whether to touch targets on execution
pub touch: bool,
/// Whether to replace macros within makefiles with envs
pub env_macros: bool,
/// Whether to quit without build
pub quit: bool,
/// Whether to keep going build targets and write info about errors stderr
pub keep_going: bool,
/// Whether to terminate on error
pub terminate: bool,
/// Whether to clear default_rules
pub clear: bool,
/// Whether to print macro definitions and target descriptions.
pub print: bool,
/// Whether to not delete interrupted files on async events.
pub precious: bool,

pub rules: BTreeMap<String, BTreeSet<String>>,
}

#[allow(clippy::derivable_impls)]
impl Default for Config {
fn default() -> Self {
Self {
ignore: false,
dry_run: false,
silent: false,
touch: false,
env_macros: false,
keep_going: false,
quit: false,
clear: false,
print: false,
precious: false,
terminate: true,
rules: BTreeMap::from([
(
".SUFFIXES".to_string(),
vec![
".o", ".c", ".y", ".l", ".a", ".sh", ".c~", ".y~", ".l~", ".sh~",
]
.into_iter()
.map(String::from)
.collect(),
),
(
".SCCS_GET".to_string(),
BTreeSet::from([String::from("sccs $(SCCSFLAGS) get $(SCCSGETFLAGS) $@")]),
),
(
".MACROS".to_string(),
vec![
"AR=ar",
"ARFLAGS=-rv",
"YACC=yacc",
"YFLAGS=",
"LEX=lex",
"LFLAGS=",
"LDFLAGS=",
"CC=c17",
"CFLAGS=-O 1",
"XSI GET=get",
"GFLAGS=",
"SCCSFLAGS=",
"SCCSGETFLAGS=-s",
]
.into_iter()
.map(String::from)
.collect(),
),
(
"SUFFIX RULES".to_string(),
[
// Single-Suffix Rules
".c: $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<",
".sh: cp $< $@",
".sh: chmod a+x $@",

// Double-Suffix Rules
".c.o: $(CC) $(CFLAGS) -c $<",
".y.o: $(YACC) $(YFLAGS) $<; $(CC) $(CFLAGS) -c y.tab.c; rm -f y.tab.c; mv y.tab.o $@",
".l.o: $(LEX) $(LFLAGS) $<; $(CC) $(CFLAGS) -c lex.yy.c; rm -f lex.yy.c; mv lex.yy.o $@",
".y.c: $(YACC) $(YFLAGS) $<; mv y.tab.c $@",
".l.c: $(LEX) $(LFLAGS) $<; mv lex.yy.c $@",
"XSI .c~.o: $(GET) $(GFLAGS) -p $< > $*.c; $(CC) $(CFLAGS) -c $*.c",
".y~.o: $(GET) $(GFLAGS) -p $< > $*.y; $(YACC) $(YFLAGS) $*.y; $(CC) $(CFLAGS) -c y.tab.c; rm -f y.tab.c; mv y.tab.o $@",
".l~.o: $(GET) $(GFLAGS) -p $< > $*.l; $(LEX) $(LFLAGS) $*.l; $(CC) $(CFLAGS) -c lex.yy.c; rm -f lex.yy.c; mv lex.yy.o $@",
".y~.c: $(GET) $(GFLAGS) -p $< > $*.y; $(YACC) $(YFLAGS) $*.y; mv y.tab.c $@",
".l~.c: $(GET) $(GFLAGS) -p $< > $*.l; $(LEX) $(LFLAGS) $*.l; mv lex.yy.c $@",
".c.a: $(CC) -c $(CFLAGS) $<; $(AR) $(ARFLAGS) $@ $*.o; rm -f $*.o",
]
.into_iter()
.map(String::from)
.collect::<BTreeSet<String>>(),
)
]),
}
}
}

impl Config {
/// Adds a new suffix to the `.SUFFIXES` rule.
pub fn add_suffix(&mut self, new_suffix: &str) {
self.rules
.entry(".SUFFIXES".to_string())
.or_default()
.insert(new_suffix.to_string());
}
}
32 changes: 18 additions & 14 deletions make/src/error_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,21 @@
use core::fmt;
use std::io;

use gettextrs::gettext;

use crate::parser::parse::ParseError;
use crate::special_target::Error;
use gettextrs::gettext;

/// Represents the error codes that can be returned by the make utility
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ErrorCode {
// Transparent
ExecutionError { exit_code: Option<i32> },
IoError(io::ErrorKind),
// for now just a string, in future `makefile_lossless::parse::ParseError` must be used (now it
// is private)
ParseError(String),
ParserError { constraint: ParseError },

// Specific
NoMakefile,
NotUpToDateError { target: String },
NoTarget { target: Option<String> },
NoRule { rule: String },
RecursivePrerequisite { origin: String },
Expand All @@ -38,19 +37,21 @@ impl From<ErrorCode> for i32 {
}
}

// todo: tests error codes
impl From<&ErrorCode> for i32 {
fn from(err: &ErrorCode) -> i32 {
use ErrorCode::*;

match err {
ExecutionError { .. } => 1,
IoError(_) => 2,
ParseError(_) => 3,
NoMakefile => 4,
NoTarget { .. } => 5,
NoRule { .. } => 6,
RecursivePrerequisite { .. } => 7,
SpecialTargetConstraintNotFulfilled { .. } => 8,
NotUpToDateError { .. } => 1,
ExecutionError { .. } => 2,
IoError(_) => 3,
ParserError { .. } => 4,
NoMakefile => 5,
NoTarget { .. } => 6,
NoRule { .. } => 7,
RecursivePrerequisite { .. } => 8,
SpecialTargetConstraintNotFulfilled { .. } => 9,
}
}
}
Expand All @@ -60,6 +61,9 @@ impl fmt::Display for ErrorCode {
use ErrorCode::*;

match self {
NotUpToDateError { target } => {
write!(f, "{}: {}", target, gettext("target is not up to date"))
}
ExecutionError { exit_code } => match exit_code {
Some(exit_code) => {
write!(f, "{}: {}", gettext("execution error"), exit_code)
Expand All @@ -75,7 +79,7 @@ impl fmt::Display for ErrorCode {
},
IoError(err) => write!(f, "{}: {}", gettext("io error"), err),
NoMakefile => write!(f, "{}", gettext("no makefile")),
ParseError(err) => write!(f, "{}: {}", gettext("parse error"), err),
ParserError { constraint } => write!(f, "{}: {}", gettext("parse error"), constraint),
NoTarget { target } => match target {
Some(target) => write!(f, "{} '{}'", gettext("no target"), target),
None => write!(f, "{}", gettext("no targets to execute")),
Expand Down
49 changes: 32 additions & 17 deletions make/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@

pub mod config;
pub mod error_code;
pub mod parser;
pub mod rule;
pub mod signal_handler;
pub mod special_target;

use std::{collections::HashSet, fs, time::SystemTime};
use std::{
collections::HashSet,
fs::{self},
time::SystemTime,
};

use makefile_lossless::{Makefile, VariableDefinition};
use parser::{Makefile, VariableDefinition};

use crate::special_target::InferenceTarget;
use config::Config;
use error_code::ErrorCode::{self, *};
use rule::{prerequisite::Prerequisite, target::Target, Rule};
Expand All @@ -29,12 +36,11 @@ const DEFAULT_SHELL: &str = "/bin/sh";

/// Represents the make utility with its data and configuration.
///
/// The only way to create a `Make` is from a `Makefile` and a `Config`.
/// The only way to create a Make is from a Makefile and a Config.
pub struct Make {
macros: Vec<VariableDefinition>,
rules: Vec<Rule>,
default_rule: Option<Rule>, // .DEFAULT

pub config: Config,
}

Expand All @@ -43,8 +49,8 @@ impl Make {
///
/// # Returns
///
/// - `Some(rule)` if a rule with the target exists.
/// - `None` if no rule with the target exists.
/// - Some(rule) if a rule with the target exists.
/// - None if no rule with the target exists.
fn rule_by_target_name(&self, target: impl AsRef<str>) -> Option<&Rule> {
self.rules
.iter()
Expand All @@ -59,9 +65,9 @@ impl Make {
/// Builds the target with the given name.
///
/// # Returns
/// - `Ok(true)` if the target was built.
/// - `Ok(false)` if the target was already up to date.
/// - `Err(_)` if any errors occur.
/// - Ok(true) if the target was built.
/// - Ok(false) if the target was already up to date.
/// - Err(_) if any errors occur.
pub fn build_target(&self, name: impl AsRef<str>) -> Result<bool, ErrorCode> {
let rule = match self.rule_by_target_name(&name) {
Some(rule) => rule,
Expand All @@ -82,9 +88,9 @@ impl Make {
/// Runs the given rule.
///
/// # Returns
/// - Ok(`true`) if the rule was run.
/// - Ok(`false`) if the rule was already up to date.
/// - `Err(_)` if any errors occur.
/// - Ok(true) if the rule was run.
/// - Ok(false) if the rule was already up to date.
/// - Err(_) if any errors occur.
fn run_rule_with_prerequisites(&self, rule: &Rule, target: &Target) -> Result<bool, ErrorCode> {
if self.are_prerequisites_recursive(target) {
return Err(RecursivePrerequisite {
Expand All @@ -93,15 +99,19 @@ impl Make {
}

let newer_prerequisites = self.get_newer_prerequisites(target);
if newer_prerequisites.is_empty() && get_modified_time(target).is_some() {
let mut up_to_date = newer_prerequisites.is_empty() && get_modified_time(target).is_some();
if rule.config.phony {
up_to_date = false;
}

if up_to_date {
return Ok(false);
}

for prerequisite in newer_prerequisites {
for prerequisite in &newer_prerequisites {
self.build_target(prerequisite)?;
}

rule.run(&self.config, &self.macros, target)?;
rule.run(&self.config, &self.macros, target, up_to_date)?;

Ok(true)
}
Expand Down Expand Up @@ -134,7 +144,7 @@ impl Make {
}

/// Checks if the target has recursive prerequisites.
/// Returns `true` if the target has recursive prerequisites.
/// Returns true if the target has recursive prerequisites.
fn are_prerequisites_recursive(&self, target: impl AsRef<str>) -> bool {
let mut visited = HashSet::from([target.as_ref()]);
let mut stack = HashSet::from([target.as_ref()]);
Expand Down Expand Up @@ -176,13 +186,18 @@ impl TryFrom<(Makefile, Config)> for Make {
fn try_from((makefile, config): (Makefile, Config)) -> Result<Self, Self::Error> {
let mut rules = vec![];
let mut special_rules = vec![];
let mut inference_rules = vec![];

for rule in makefile.rules() {
let rule = Rule::from(rule);
let Some(target) = rule.targets().next() else {
return Err(NoTarget { target: None });
};

if SpecialTarget::try_from(target.clone()).is_ok() {
special_rules.push(rule);
} else if InferenceTarget::try_from((target.clone(), config.clone())).is_ok() {
inference_rules.push(rule);
} else {
rules.push(rule);
}
Expand Down
Loading

0 comments on commit 564cff4

Please sign in to comment.