diff options
Diffstat (limited to 'src/makefile/lookup_internal.rs')
-rw-r--r-- | src/makefile/lookup_internal.rs | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/src/makefile/lookup_internal.rs b/src/makefile/lookup_internal.rs new file mode 100644 index 0000000..c0fc08b --- /dev/null +++ b/src/makefile/lookup_internal.rs @@ -0,0 +1,164 @@ +use eyre::{bail, eyre, Result}; +use std::cell::RefCell; +use std::path::Path; +use std::rc::Rc; + +use super::target::Target; + +#[derive(Clone)] +pub enum LookupInternal<'a> { + Partial { + targets: &'a Vec<&'a str>, + }, + Complete { + target: Option<&'a Target>, + get_target: &'a dyn Fn(&str) -> Result<Rc<RefCell<Target>>>, + }, +} + +impl<'a> LookupInternal<'a> { + pub fn new_partial(targets: &'a Vec<&str>) -> Self { + Self::Partial { targets } + } + + pub fn new( + target: Option<&'a Target>, + get_target: &'a dyn Fn(&str) -> Result<Rc<RefCell<Target>>>, + ) -> Self { + Self::Complete { target, get_target } + } + + pub fn lookup(&self, macro_name: &str) -> Result<String> { + let macro_pieces = match macro_name.chars().next() { + Some('@') => self.target_name()?, + Some('?') => self.newer_prerequisites()?, + Some('<') => self.inference_prerequisite()?, + Some('*') => self.target_stem()?, + #[cfg(feature = "full")] + Some('^') => self.all_prerequisites()?, + _ => bail!("unknown internal macro {}", macro_name), + }; + + let macro_pieces = if macro_name.ends_with('D') { + macro_pieces + .into_iter() + .map(|x| { + Path::new(&x) + .parent() + .ok_or_else(|| eyre!("no parent")) + .map(|x| x.to_string_lossy().into()) + }) + .collect::<Result<_, _>>()? + } else if macro_name.ends_with('F') { + macro_pieces + .into_iter() + .map(|x| { + Path::new(&x) + .file_name() + .ok_or_else(|| eyre!("no filename")) + .map(|x| x.to_string_lossy().into()) + }) + .collect::<Result<_, _>>()? + } else { + macro_pieces + }; + + Ok(macro_pieces.join(" ")) + } + + /// POSIX: The $@ shall evaluate to the full target name of the current target. + fn target_name(&self) -> Result<Vec<String>> { + match self { + Self::Partial { targets } => { + Ok(targets.iter().map(|target| target.to_string()).collect()) + } + Self::Complete { + target: Some(target), + .. + } => Ok(vec![target.name.clone()]), + Self::Complete { target: None, .. } => { + bail!("tried to expand internal macro with no target") + } + } + } + + /// POSIX: The $? macro shall evaluate to the list of prerequisites that are newer than the current target. + fn newer_prerequisites(&self) -> Result<Vec<String>> { + match self { + Self::Partial { .. } => bail!("can’t expand $? when target not defined"), + Self::Complete { + target: Some(target), + get_target, + } => Ok(target + .prerequisites + .iter() + .filter(|prereq| { + get_target(prereq) + .ok() + .and_then(|prereq| prereq.borrow().newer_than(target)) + .unwrap_or(false) + }) + .cloned() + .collect()), + Self::Complete { target: None, .. } => { + bail!("tried to expand internal macro with no target") + } + } + } + + /// POSIX: In an inference rule, the $< macro shall evaluate to the filename whose existence allowed the inference rule to be chosen for the target. In the .DEFAULT rule, the $< macro shall evaluate to the current target name. + /// + /// GNU: The name of the first prerequisite. + fn inference_prerequisite(&self) -> Result<Vec<String>> { + match self { + Self::Partial { .. } => bail!("can’t expand $< when target not defined"), + Self::Complete { + target: Some(target), + .. + } => { + // TODO check that exists_but_inferring_anyway won’t break this + Ok(vec![target + .prerequisites + .first() + .cloned() + .unwrap_or_default()]) + } + Self::Complete { target: None, .. } => { + bail!("tried to expand internal macro with no target") + } + } + } + + /// POSIX: The $* macro shall evaluate to the current target name with its suffix deleted. + fn target_stem(&self) -> Result<Vec<String>> { + match self { + Self::Partial { .. } => bail!("can’t expand $* when target not defined"), + Self::Complete { + target: Some(target), + .. + } => Ok(vec![target + .stem + .as_ref() + .unwrap_or(&target.name) + .to_owned()]), + Self::Complete { target: None, .. } => { + bail!("tried to expand internal macro with no target") + } + } + } + + /// GNU: The names of all the prerequisites. + #[cfg(feature = "full")] + fn all_prerequisites(&self) -> Result<Vec<String>> { + match self { + Self::Partial { .. } => bail!("can’t expand $^ when target not defined"), + Self::Complete { + target: Some(target), + .. + } => Ok(target.prerequisites.clone()), + Self::Complete { target: None, .. } => { + bail!("tried to expand internal macro with no target") + } + } + } +} |