Skip to content

Commit

Permalink
feat: rules export shell exports (#591)
Browse files Browse the repository at this point in the history
  • Loading branch information
kaspar030 authored Dec 22, 2024
2 parents 8f03706 + 592b1d3 commit b6f1f82
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 75 deletions.
56 changes: 54 additions & 2 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ struct YamlContext {
env: Option<Env>,
selects: Option<Vec<String>>,
disables: Option<Vec<String>>,
rules: Option<Vec<Rule>>,
rules: Option<Vec<YamlRule>>,
var_options: Option<im::HashMap<String, MergeOption>>,
tasks: Option<HashMap<String, YamlTask>>,
#[serde(default = "default_as_false", alias = "buildable")]
Expand Down Expand Up @@ -210,6 +210,58 @@ struct YamlModuleEnv {
global: Option<Env>,
}

#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(deny_unknown_fields)]
pub struct YamlRule {
pub name: String,
pub cmd: String,

pub help: Option<String>,

#[serde(rename = "in")]
pub in_: Option<String>,
pub out: Option<String>,
pub context: Option<String>,
pub options: Option<HashMap<String, String>>,
pub gcc_deps: Option<String>,
pub rspfile: Option<String>,
pub rspfile_content: Option<String>,
pub pool: Option<String>,
pub description: Option<String>,
pub export: Option<Vec<StringOrMapString>>,

#[serde(default = "default_as_false")]
pub always: bool,

#[serde(rename = "meta")]
_meta: Option<Value>,
}

impl From<YamlRule> for Rule {
//TODO: use deserialize_with as only the export field needs special handling
fn from(yaml_rule: YamlRule) -> Self {
Rule {
always: yaml_rule.always,
cmd: yaml_rule.cmd,
context: yaml_rule.context,

name: yaml_rule.name,
help: yaml_rule.help,
in_: yaml_rule.in_,
out: yaml_rule.out,
options: yaml_rule.options,
gcc_deps: yaml_rule.gcc_deps,
rspfile: yaml_rule.rspfile,
rspfile_content: yaml_rule.rspfile_content,
pool: yaml_rule.pool,
description: yaml_rule.description,
export: yaml_rule
.export
.map(|s| s.iter().map(|s| s.clone().into()).collect_vec()),
}
}
}

#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(deny_unknown_fields)]
pub struct YamlTask {
Expand Down Expand Up @@ -436,7 +488,7 @@ pub fn load(
if let Some(rules) = &context.rules {
context_.rules = Some(IndexMap::new());
for rule in rules {
let mut rule = rule.clone();
let mut rule: Rule = rule.clone().into();
rule.context = Some(context_name.clone());
context_
.rules
Expand Down
9 changes: 2 additions & 7 deletions src/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use itertools::Itertools;
use serde::{Deserialize, Serialize};

use super::{ninja::NinjaBuildBuilder, Module, Rule};
use crate::nested_env::{self, IfMissing};
use camino::{Utf8Path, Utf8PathBuf};

pub mod source {
Expand Down Expand Up @@ -101,9 +100,7 @@ impl Download {
None => panic!("missing {} rule for module {}", rulename, module.name),
};

let expanded = nested_env::expand_eval(&download_rule.cmd, env, IfMissing::Ignore)?;

let ninja_download_rule = download_rule.to_ninja().command(expanded).build().unwrap();
let ninja_download_rule = download_rule.to_ninja(env).unwrap();

// "srcdir" is filled in data.rs
let srcdir = module.srcdir.as_ref().unwrap();
Expand Down Expand Up @@ -145,9 +142,7 @@ impl Download {
None => panic!("missing {} rule for module {}", rulename, module.name),
};

let expanded = nested_env::expand_eval(&patch_rule.cmd, env, IfMissing::Ignore).unwrap();

let ninja_patch_rule = patch_rule.to_ninja().command(expanded).build().unwrap();
let ninja_patch_rule = patch_rule.to_ninja(env).unwrap();

// "srcdir" is filled in data.rs
let srcdir = module.srcdir.as_ref().unwrap();
Expand Down
21 changes: 7 additions & 14 deletions src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -830,13 +830,12 @@ fn configure_build(
)
})?;

let expanded =
nested_env::expand_eval(&rule.cmd, &flattened_env, IfMissing::Empty)
.with_context(|| format!("while expanding cmd \"{}\"", rule.cmd))
.with_context(|| format!("rule \"{}\"", rule.name))
.with_context(|| format!("module \"{}\"", module.name))?;
let rule = rule
.to_ninja(&flattened_env)
.with_context(|| format!("while expanding cmd \"{}\"", rule.cmd))
.with_context(|| format!("rule \"{}\"", rule.name))
.with_context(|| format!("module \"{}\"", module.name))?;

let rule = rule.to_ninja().command(expanded).build().unwrap().named();
ninja_entries.insert(format!("{rule}"));
rule
});
Expand Down Expand Up @@ -935,15 +934,9 @@ fn configure_build(
Ok(*rule)
}

fn render_rule_with<'a>(rule: &'a Rule, env: &im::HashMap<&String, String>) -> NinjaRule<'a> {
let expanded = nested_env::expand_eval(&rule.cmd, env, IfMissing::Empty).unwrap();

rule.to_ninja().command(expanded).build().unwrap().named()
}

// linking
{
let ninja_link_rule = render_rule_with(get_rule("LINK", rules)?, &global_env_flattened);
let ninja_link_rule = get_rule("LINK", rules)?.to_ninja(&global_env_flattened)?;
// build ninja link target
let ninja_link_build = NinjaBuildBuilder::from_rule(&ninja_link_rule)
.inputs(objects)
Expand All @@ -964,7 +957,7 @@ fn configure_build(
new_outfile.set_extension(rule.out.as_ref().ok_or_else(|| {
anyhow!("POST_LINK rule has no \"out\" extension configured")
})?);
let post_link_rule = render_rule_with(rule, &global_env_flattened);
let post_link_rule = rule.to_ninja(&global_env_flattened)?;
let post_link_build = NinjaBuildBuilder::from_rule(&post_link_rule)
.input(outfile)
.out(new_outfile.as_path())
Expand Down
4 changes: 3 additions & 1 deletion src/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod context_bag;
mod dependency;
mod module;
mod rule;
mod shared;
mod task;

pub use blockallow::BlockAllow;
Expand All @@ -12,4 +13,5 @@ pub use context_bag::{ContextBag, IsAncestor};
pub use dependency::Dependency;
pub use module::{CustomBuild, Module};
pub use rule::Rule;
pub use task::{Task, TaskError, VarExportSpec};
pub use shared::VarExportSpec;
pub use task::{Task, TaskError};
18 changes: 10 additions & 8 deletions src/model/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ use std::collections::HashMap;
use std::convert::From;
use std::hash::{Hash, Hasher};

use serde_yaml::Value;

use crate::serde_bool_helpers::default_as_false;

#[derive(Debug, Serialize, Deserialize, Eq, Clone)]
Expand All @@ -25,17 +23,16 @@ pub struct Rule {
pub rspfile_content: Option<String>,
pub pool: Option<String>,
pub description: Option<String>,
pub export: Option<Vec<VarExportSpec>>,

#[serde(default = "default_as_false")]
pub always: bool,

#[serde(rename = "meta")]
_meta: Option<Value>,
}

impl Rule {
pub fn to_ninja(&self) -> NinjaRuleBuilder {
self.into()
pub fn to_ninja(&self, env: &im::HashMap<&String, String>) -> anyhow::Result<NinjaRule> {
let ninja_rule: NinjaRuleBuilder = self.into();
Ok(ninja_rule.build().unwrap().expand(env)?.named())
}

/// get rule description
Expand Down Expand Up @@ -64,22 +61,27 @@ impl PartialEq for Rule {
}
}

use crate::ninja::{NinjaRuleBuilder, NinjaRuleDeps};
use crate::ninja::{NinjaRule, NinjaRuleBuilder, NinjaRuleDeps};

use super::VarExportSpec;

impl<'a> From<&'a Rule> for crate::ninja::NinjaRuleBuilder<'a> {
fn from(rule: &'a Rule) -> Self {
let mut builder = NinjaRuleBuilder::default();
builder
.name(Cow::from(&rule.name))
.description(Some(rule.description()))
.command(&rule.cmd)
.rspfile(rule.rspfile.as_deref().map(Cow::from))
.rspfile_content(rule.rspfile_content.as_deref().map(Cow::from))
.pool(rule.pool.as_deref().map(Cow::from))
.always(rule.always)
.export(&rule.export)
.deps(match &rule.gcc_deps {
None => NinjaRuleDeps::None,
Some(s) => NinjaRuleDeps::GCC(s.into()),
});

builder
}
}
45 changes: 45 additions & 0 deletions src/model/shared.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use crate::nested_env;

#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct VarExportSpec {
pub variable: String,
pub content: Option<String>,
}

impl VarExportSpec {
pub fn apply_env(&self, env: &im::HashMap<&String, String>) -> Self {
let content = if let Some(content) = self.content.as_ref() {
content.clone()
} else {
format!("${{{}}}", self.variable)
};

let content =
Some(nested_env::expand_eval(content, env, nested_env::IfMissing::Empty).unwrap());

Self {
variable: self.variable.clone(),
content,
}
}

pub(crate) fn expand(
export: Option<&Vec<VarExportSpec>>,
env: &im::HashMap<&String, String>,
) -> Option<Vec<VarExportSpec>> {
// what this does is, apply the env to the format as given by "export:"
//
// e.g., assuming `FOO=value` and FOOBAR=`other_value`:
// ```yaml
//
// export:
// - FOO
// - BAR: bar
// - FOOBAR: ${foobar}
// ```
//
// ... to export `FOO=value`, `BAR=bar` and `FOOBAR=other_value`.

export.map(|exports| exports.iter().map(|entry| entry.apply_env(env)).collect())
}
}
43 changes: 3 additions & 40 deletions src/model/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use crate::nested_env;
use crate::serde_bool_helpers::{default_as_false, default_as_true};
use crate::IGNORE_SIGINT;

use super::shared::VarExportSpec;

#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(deny_unknown_fields)]
pub struct Task {
Expand All @@ -29,30 +31,6 @@ pub enum TaskError {
RequiredModuleMissing { module: String },
}

#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct VarExportSpec {
pub variable: String,
pub content: Option<String>,
}

impl VarExportSpec {
fn apply_env(&self, env: &im::HashMap<&String, String>) -> Self {
let content = if let Some(content) = self.content.as_ref() {
content.clone()
} else {
format!("${{{}}}", self.variable)
};

let content =
Some(nested_env::expand_eval(content, env, nested_env::IfMissing::Empty).unwrap());

Self {
variable: self.variable.clone(),
content,
}
}
}

impl Task {
pub fn build_app(&self) -> bool {
self.build
Expand Down Expand Up @@ -140,21 +118,6 @@ impl Task {
}

fn expand_export(&self, env: &im::HashMap<&String, String>) -> Option<Vec<VarExportSpec>> {
// what this does is, apply the env to the format as given by "export:"
//
// e.g., assuming `FOO=value` and FOOBAR=`other_value`:
// ```yaml
//
// export:
// - FOO
// - BAR: bar
// - FOOBAR: ${foobar}
// ```
//
// ... to export `FOO=value`, `BAR=bar` and `FOOBAR=other_value`.

self.export
.as_ref()
.map(|exports| exports.iter().map(|entry| entry.apply_env(env)).collect())
VarExportSpec::expand(self.export.as_ref(), env)
}
}
32 changes: 29 additions & 3 deletions src/ninja/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,24 @@ use std::process::{Command, ExitStatus, Stdio};
use camino::{Utf8Path, Utf8PathBuf};
use indexmap::IndexMap;

use crate::model::VarExportSpec;
use crate::nested_env::{self, IfMissing};

#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub enum NinjaRuleDeps {
#[default]
None,
GCC(String),
}

#[derive(Builder, Debug, PartialEq, Eq, Clone)]
#[derive(Builder, Clone)]
#[builder(setter(into))]
pub struct NinjaRule<'a> {
pub name: Cow<'a, str>,
command: Cow<'a, str>,
description: Option<Cow<'a, str>>,
#[builder(setter(into, strip_option), default = "None")]
env: Option<&'a IndexMap<String, String>>,
#[builder(default = "None")]
export: Option<&'a Vec<VarExportSpec>>,
#[builder(default = "NinjaRuleDeps::None")]
deps: NinjaRuleDeps,
#[builder(default = "None")]
Expand Down Expand Up @@ -93,6 +96,29 @@ impl<'a> NinjaRule<'a> {
self.name = Cow::from(name);
self
}

pub(crate) fn expand(mut self, env: &im::HashMap<&String, String>) -> anyhow::Result<Self> {
let mut command = String::with_capacity(self.command.len());

// handle "export" statements
let export = VarExportSpec::expand(self.export, env);
if let Some(export) = &export {
for entry in export {
let VarExportSpec { variable, content } = entry;
if let Some(val) = content {
command.push_str(variable);
command.push_str("=\"");
command.push_str(val);
command.push_str("\" && ");
}
}
}

let expanded = nested_env::expand_eval(&self.command, env, IfMissing::Empty)?;
command.push_str(&expanded);
self.command = command.into();
Ok(self)
}
}

impl Hash for NinjaRule<'_> {
Expand Down

0 comments on commit b6f1f82

Please sign in to comment.