Add recursive Parsing of makefiles

This commit is contained in:
Till Wegmueller 2022-03-28 20:27:15 -03:00
parent 09635558ae
commit bf9d0289a7
3 changed files with 155 additions and 27 deletions

View file

@ -75,13 +75,14 @@ fn diff_component(component_path: impl AsRef<Path>) -> Result<()> {
fn show_component_info<P: AsRef<Path>>(component_path: P) -> Result<()> { fn show_component_info<P: AsRef<Path>>(component_path: P) -> Result<()> {
let makefile_path = component_path.as_ref().join("Makefile"); let makefile_path = component_path.as_ref().join("Makefile");
let makefile = Makefile::parse_file(&makefile_path)?; let initial_makefile = Makefile::parse_single_file(&makefile_path)?;
let makefile = initial_makefile.parse_all()?;
let mut name = String::new(); let mut name = String::new();
if let Some(var) = makefile.get("COMPONENT_NAME") { if let Some(var) = makefile.get("COMPONENT_NAME") {
println!("Name: {}", var.replace("\n", "\n\t")); println!("Name: {}", var.replace("\n", "\n\t"));
if let Some(component_name) = makefile.get_first("COMPONENT_NAME") { if let Some(component_name) = makefile.get_first_value_of_variable_by_name("COMPONENT_NAME") {
name = component_name.clone(); name = component_name.clone();
} }
} }

View file

@ -8,8 +8,10 @@ extern crate pest_derive;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use std::collections::HashMap; use std::collections::{HashMap};
use std::fs::read_to_string; use std::env;
use std::io::Error as IOError;
use std::fs::{read_to_string, canonicalize};
use pest::iterators::{Pairs}; use pest::iterators::{Pairs};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use pest::Parser; use pest::Parser;
@ -21,11 +23,31 @@ use regex::Regex;
#[grammar = "makefile.pest"] #[grammar = "makefile.pest"]
struct MakefileParser; struct MakefileParser;
#[derive(Debug, Default, PartialEq, Clone)] #[derive(Debug, Default, Clone)]
pub struct Makefile { pub struct Makefile {
pub variables: HashMap<String, Vec<String>>, path: PathBuf,
// pub includes: Vec<String>, variables: HashMap<String, MakefileVariable>,
// pub targets: HashMap<String, String>, includes: Vec<String>,
// targets: HashMap<String, String>,
}
#[derive(Debug, Default, PartialEq, Clone)]
pub struct MakefileVariable {
pub key: String,
pub values: Vec<String>,
pub mode: VariableMode,
}
#[derive(Debug, PartialEq, Clone)]
pub enum VariableMode {
Add,
Set
}
impl Default for VariableMode {
fn default() -> Self {
Self::Set
}
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -34,21 +56,66 @@ pub enum ParserError {
MakefileReadError { MakefileReadError {
file: PathBuf, file: PathBuf,
reason: anyhow::Error, reason: anyhow::Error,
} },
#[error("could not find include {0}")]
IncludeNotFound(String, #[source] IOError),
} }
impl Makefile { impl Makefile {
pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Self> { pub fn parse_single_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let content = read_to_string(&path)?; let content = read_to_string(&path)?;
parse_string(content).map_err(|err| let mut m = parse_string(content).map_err(|err|
anyhow!(ParserError::MakefileReadError{file: path.as_ref().to_path_buf(), reason: anyhow!(err)}) anyhow!(ParserError::MakefileReadError{file: path.as_ref().to_path_buf(), reason: anyhow!(err)})
) )?;
m.path = path.as_ref().into();
Ok(m)
} }
pub fn parse_string(content: String) -> Result<Self> { pub fn parse_string(content: String) -> Result<Self> {
parse_string(content) parse_string(content)
} }
pub fn parse_all(&self) -> Result<Self> {
let includes = self.parse_included_makefiles()?;
let mut final_makefile = Self::default();
for incl in includes {
if incl.has_includes() {
let final_include = incl.parse_all()?;
final_makefile.merge(&final_include)?;
} else {
final_makefile.merge(&incl)?;
}
}
final_makefile.merge(self)?;
Ok(final_makefile)
}
pub fn merge(&mut self, other: &Self) -> Result<()> {
for (key, var) in other.variables.clone() {
match var.mode {
VariableMode::Add => {
if let Some(v) = self.variables.get(&key) {
let mut new_variable = v.clone();
for val in var.values {
new_variable.values.push(val);
}
new_variable.mode = var.mode;
self.variables.insert(key, new_variable);
} else {
self.variables.insert(key, var);
}
}
VariableMode::Set => {
self.variables.insert(key, var);
}
}
}
Ok(())
}
pub fn get(&self, var_name: &str) -> Option<String> { pub fn get(&self, var_name: &str) -> Option<String> {
if let Some(var) = self.variables.get(var_name) { if let Some(var) = self.variables.get(var_name) {
let vars_resolved = self.resolve_nested_variables(var); let vars_resolved = self.resolve_nested_variables(var);
@ -58,12 +125,48 @@ impl Makefile {
} }
} }
fn resolve_nested_variables(&self, var: &Vec<String>) -> Vec<String> { pub fn get_includes(&self) -> Option<Vec<String>> {
if self.includes.len() > 0 {
Some(self.includes.clone())
} else {
None
}
}
pub fn has_includes(&self) -> bool {
self.includes.len() > 0
}
pub fn parse_included_makefiles(&self) -> Result<Vec<Self>> {
if let Some(includes) = self.get_includes() {
let mut included_makefiles: Vec<Makefile> = Vec::new();
let dir_of_makefile = self.path.parent();
if let Some(d) = dir_of_makefile {
env::set_current_dir(d)?;
} else {
env::set_current_dir("/")?;
};
for incl in includes {
let incl_path = canonicalize(&incl).map_err(|err|
anyhow!(ParserError::IncludeNotFound(incl, err)))?;
let m = Self::parse_single_file(incl_path)?;
included_makefiles.push(m);
}
Ok(included_makefiles)
} else {
Ok(Vec::new())
}
}
fn resolve_nested_variables(&self, var: &MakefileVariable) -> Vec<String> {
// Make a mutable copy of the variables so we can replace nested variables with their final strings // Make a mutable copy of the variables so we can replace nested variables with their final strings
let mut vars_copy = var.clone(); let mut vars_copy = var.values.clone();
// Logic to resolve all the nested Variables when we access them. // Logic to resolve all the nested Variables when we access them.
for (i, maybe_nested_var) in var.iter().enumerate() { for (i, maybe_nested_var) in var.values.iter().enumerate() {
lazy_static! { lazy_static! {
static ref VARRE: Regex = Regex::new(r"(?P<var_name>\$\(.+?\))").unwrap(); static ref VARRE: Regex = Regex::new(r"(?P<var_name>\$\(.+?\))").unwrap();
} }
@ -81,7 +184,7 @@ impl Makefile {
vars_copy vars_copy
} }
pub fn get_first(&self, var_name: &str) -> Option<String> { pub fn get_first_value_of_variable_by_name(&self, var_name: &str) -> Option<String> {
if let Some(var) = self.variables.get(var_name) { if let Some(var) = self.variables.get(var_name) {
let vars_resolved = self.resolve_nested_variables(var); let vars_resolved = self.resolve_nested_variables(var);
Some(vars_resolved.first().unwrap().clone()) Some(vars_resolved.first().unwrap().clone())
@ -125,7 +228,9 @@ fn parse_makefile(pairs: Pairs<crate::Rule>, m: &mut Makefile) -> Result<()> {
parse_variable(p.into_inner(), m)?; parse_variable(p.into_inner(), m)?;
} }
Rule::comment_string => (), Rule::comment_string => (),
Rule::include => (), Rule::include => {
parse_include(p.into_inner(), m)?;
},
Rule::target => (), Rule::target => (),
Rule::define => { Rule::define => {
parse_define(p.into_inner(), m)?; parse_define(p.into_inner(), m)?;
@ -138,17 +243,29 @@ fn parse_makefile(pairs: Pairs<crate::Rule>, m: &mut Makefile) -> Result<()> {
Ok(()) Ok(())
} }
fn parse_include(include_pair: Pairs<crate::Rule>, m: &mut Makefile) -> Result<()> {
for p in include_pair {
match p.as_rule() {
Rule::variable_value => {
m.includes.push(p.as_str().to_string());
}
_ => panic!("unexpected rule {:?} inside include rule expected variable_value", p.as_rule())
}
}
Ok(())
}
fn parse_define(define_pair: Pairs<crate::Rule>, m: &mut Makefile) -> Result<()> { fn parse_define(define_pair: Pairs<crate::Rule>, m: &mut Makefile) -> Result<()> {
let mut var = (String::new(), Vec::<String>::new()); let mut var = (String::new(), MakefileVariable::default());
for p in define_pair { for p in define_pair {
match p.as_rule() { match p.as_rule() {
Rule::variable_name => { Rule::variable_name => {
var.0 = p.as_str().to_string(); var.0 = p.as_str().to_string();
} }
Rule::define_value => { Rule::define_value => {
var.1.push(p.as_str().to_string()); var.1.values.push(p.as_str().to_string());
} }
_ => panic!("unexpected rule {:?} inside makefile rule expected variable_name, define_value", p.as_rule()), _ => panic!("unexpected rule {:?} inside define rule expected variable_name, define_value", p.as_rule()),
} }
} }
m.variables.insert(var.0, var.1); m.variables.insert(var.0, var.1);
@ -157,20 +274,30 @@ fn parse_define(define_pair: Pairs<crate::Rule>, m: &mut Makefile) -> Result<()>
} }
fn parse_variable(variable_pair: Pairs<crate::Rule>, m: &mut Makefile) -> Result<()> { fn parse_variable(variable_pair: Pairs<crate::Rule>, m: &mut Makefile) -> Result<()> {
let mut var = (String::new(), Vec::<String>::new()); let mut var = (String::new(), MakefileVariable::default());
for p in variable_pair { for p in variable_pair {
match p.as_rule() { match p.as_rule() {
Rule::variable_name => { Rule::variable_name => {
var.0 = p.as_str().to_string(); var.0 = p.as_str().to_string();
} }
Rule::variable_set => (), Rule::variable_set => {
var.1.mode = VariableMode::Set
},
Rule::variable_add => { Rule::variable_add => {
if m.variables.contains_key(&var.0) { var.1.mode = VariableMode::Add
var.1 = m.variables.get(&var.0).unwrap().clone()
}
} }
Rule::variable_value => { Rule::variable_value => {
var.1.push(p.as_str().to_string()); match var.1.mode {
VariableMode::Add => {
if m.variables.contains_key(&var.0) {
var.1 = m.variables.get(&var.0).unwrap().clone()
}
var.1.values.push(p.as_str().to_string());
}
VariableMode::Set => {
var.1.values.push(p.as_str().to_string());
}
}
} }
_ => panic!("unexpected rule {:?} inside makefile rule expected variable_name, variable_set, variable_add, variable_value", p.as_rule()), _ => panic!("unexpected rule {:?} inside makefile rule expected variable_name, variable_set, variable_add, variable_value", p.as_rule()),
} }

View file

@ -16,7 +16,7 @@ comment_character = _{
~ ANY // then consume one character ~ ANY // then consume one character
} }
variable_name_character = { UPPERCASE | ASCII_DIGIT | "_" } variable_name_character = { UPPERCASE | ASCII_DIGIT | "_" | "." }
variable_name = @{ variable_name_character* } variable_name = @{ variable_name_character* }
variable_value_character = { variable_value_character = {
!NEWLINE !NEWLINE