From 110029ce3b7f205cce01f7d74dd2c8777f860c31 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Tue, 6 Apr 2021 16:20:09 -0600 Subject: implement exported variables --- src/makefile/command_line.rs | 24 ++++++++----- src/makefile/input.rs | 22 ++++++++++-- src/makefile/macro.rs | 85 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 10 deletions(-) diff --git a/src/makefile/command_line.rs b/src/makefile/command_line.rs index ec2c115..1b4b4b4 100644 --- a/src/makefile/command_line.rs +++ b/src/makefile/command_line.rs @@ -1,16 +1,20 @@ use std::env; use std::fmt; -use std::io; use std::process::{Command, ExitStatus}; -use eyre::bail; +use eyre::{bail, Error}; -use crate::makefile::target::Target; -use crate::makefile::token::{Token, TokenString}; -use crate::makefile::Makefile; +use super::r#macro::Set as MacroSet; +use super::target::Target; +use super::token::{Token, TokenString}; +use super::Makefile; // inspired by python's subprocess module -fn execute_command_line(command_line: &str, ignore_errors: bool) -> Result { +fn execute_command_line( + command_line: &str, + ignore_errors: bool, + macros: &MacroSet, +) -> Result { let (program, args) = if cfg!(windows) { let cmd = env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".into()); let args = vec!["/c", command_line]; @@ -24,7 +28,11 @@ fn execute_command_line(command_line: &str, ignore_errors: bool) -> Result MakefileReader<'a, 'parent, R> { LineType::Macro => self.read_macro(line_tokens, line_number)?, LineType::Unknown => { if !line_tokens.is_empty() { + // TODO handle assignments here #[cfg(feature = "full")] if line_tokens.starts_with("export") { - log::error!("export directive not supported yet"); + let mut line_tokens = line_tokens; + line_tokens.strip_prefix("export"); + if line_tokens.is_empty() { + self.macros.exported = ExportConfig::all_but(); + } else { + let exported = self.expand_macros(&line_tokens)?; + self.macros.exported.add_all(exported.split_whitespace()); + } continue; } else if line_tokens.starts_with("unexport") { - log::error!("unexport directive not supported yet"); + let mut line_tokens = line_tokens; + line_tokens.strip_prefix("unexport"); + if line_tokens.is_empty() { + self.macros.exported = ExportConfig::only(); + } else { + let exported = self.expand_macros(&line_tokens)?; + self.macros.exported.remove_all(exported.split_whitespace()); + } continue; } bail!( @@ -423,6 +440,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { let (targets, not_targets) = line_tokens .split_once(':') .ok_or_else(|| eyre!("read_rule couldn't find a ':' on line {}", line_number))?; + // TODO handle rule-specific variables let targets = self.expand_macros(&targets)?; let targets = targets.split_whitespace().collect::>(); let (prerequisites, mut commands) = match not_targets.split_once(';') { diff --git a/src/makefile/macro.rs b/src/makefile/macro.rs index 2c8747c..5e7ea9c 100644 --- a/src/makefile/macro.rs +++ b/src/makefile/macro.rs @@ -1,6 +1,8 @@ #[cfg(feature = "full")] use std::cell::RefCell; use std::collections::HashMap; +#[cfg(feature = "full")] +use std::collections::HashSet; use std::env; use std::fmt; #[cfg(feature = "full")] @@ -33,6 +35,64 @@ pub trait LookupInternal: for<'a> Fn(&'a str) -> Result {} impl Fn(&'a str) -> Result> LookupInternal for F {} +#[cfg(feature = "full")] +#[derive(Clone)] +pub enum ExportConfig { + Only(HashSet), + AllBut(HashSet), +} + +#[cfg(feature = "full")] +impl ExportConfig { + pub fn all_but() -> Self { + Self::AllBut(HashSet::new()) + } + + pub fn only() -> Self { + Self::Only(HashSet::new()) + } + + pub fn add_all<'a, I: IntoIterator>(&mut self, iter: I) { + match self { + Self::Only(exported) => { + exported.extend(iter.into_iter().map(|x| x.to_owned())); + } + Self::AllBut(not_exported) => { + for added in iter { + not_exported.remove(added); + } + } + } + } + + pub fn remove_all<'a, I: IntoIterator>(&mut self, iter: I) { + match self { + Self::Only(exported) => { + for removed in iter { + exported.remove(removed); + } + } + Self::AllBut(not_exported) => { + not_exported.extend(iter.into_iter().map(|x| x.into())); + } + } + } + + fn same_type(&self) -> Self { + match self { + Self::Only(_) => Self::only(), + Self::AllBut(_) => Self::all_but(), + } + } + + fn should_export(&self, x: &str) -> bool { + match self { + Self::Only(exported) => exported.contains(x), + Self::AllBut(not_exported) => !not_exported.contains(x), + } + } +} + #[derive(Clone)] pub struct Set<'parent, 'lookup> { parent: Option<&'parent Set<'parent, 'lookup>>, @@ -40,6 +100,8 @@ pub struct Set<'parent, 'lookup> { lookup_internal: Option<&'lookup dyn LookupInternal>, #[cfg(feature = "full")] pub to_eval: Rc>>, + #[cfg(feature = "full")] + pub exported: ExportConfig, } impl<'parent, 'lookup> Set<'parent, 'lookup> { @@ -50,6 +112,8 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { lookup_internal: None, #[cfg(feature = "full")] to_eval: Rc::new(RefCell::new(Vec::new())), + #[cfg(feature = "full")] + exported: ExportConfig::only(), } } @@ -209,6 +273,8 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { lookup_internal: Some(lookup), #[cfg(feature = "full")] to_eval: Rc::clone(&self.to_eval), + #[cfg(feature = "full")] + exported: self.exported.same_type(), } } @@ -219,8 +285,27 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { lookup_internal: None, #[cfg(feature = "full")] to_eval: Rc::clone(&self.to_eval), + #[cfg(feature = "full")] + exported: self.exported.same_type(), } } + + #[cfg(feature = "full")] + pub fn resolve_exports(&self) -> Result> { + let own_exports = self + .data + .iter() + .filter(|(name, _)| self.exported.should_export(name)) + .map(|(name, value)| self.expand(&value.text).map(|text| (name.as_ref(), text))) + .collect::>>()?; + Ok(if let Some(parent) = self.parent { + let mut parent_exports = parent.resolve_exports()?; + parent_exports.extend(own_exports); + parent_exports + } else { + own_exports + }) + } } impl fmt::Display for Set<'_, '_> { -- cgit v1.2.3