summaryrefslogtreecommitdiffstats
path: root/crates/executor/src/context.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/executor/src/context.rs')
-rw-r--r--crates/executor/src/context.rs446
1 files changed, 446 insertions, 0 deletions
diff --git a/crates/executor/src/context.rs b/crates/executor/src/context.rs
new file mode 100644
index 0000000..7a46e8d
--- /dev/null
+++ b/crates/executor/src/context.rs
@@ -0,0 +1,446 @@
+use std::{
+ borrow::Cow,
+ collections::{HashMap, HashSet},
+ fmt::Display,
+ hash::Hash,
+ iter::FromIterator,
+ ops::Index,
+ rc::Rc,
+ result,
+};
+
+use lazy_static::lazy_static;
+use regex::Regex;
+use serde::Serialize;
+
+use common::{
+ error::{self, Contextualizable},
+ types::TaskID,
+};
+use runner::paths;
+
+use crate::{
+ args::{self, arg, Arg, ArgMapping, ArgType, PlatformRelation, TaskArgs},
+ task::*,
+};
+
+#[derive(Debug, Clone, Copy)]
+pub enum ErrorKind<'ctx> {
+ TaskNotFound,
+ InvalidArgument(&'ctx str),
+ InvalidArgRef(&'ctx str),
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Error<'ctx> {
+ pub task: &'ctx TaskID,
+ pub kind: ErrorKind<'ctx>,
+}
+
+impl<'ctx> Display for Error<'ctx> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let Error { task, kind } = self;
+ match kind {
+ ErrorKind::TaskNotFound => write!(f, "Task '{}' not found", task),
+ ErrorKind::InvalidArgument(arg) => write!(
+ f,
+ "Invalid or missing argument '{}' for task '{}'",
+ arg, task
+ ),
+ ErrorKind::InvalidArgRef(arg) => write!(
+ f,
+ "Invalid reference for argument '{}' of task '{}'",
+ arg, task
+ ),
+ }
+ }
+}
+
+impl<'ctx> From<Error<'ctx>> for error::Error {
+ fn from(err: Error) -> Self {
+ error::Error::new(err)
+ }
+}
+
+pub type Result<'ctx, T> = result::Result<T, Error<'ctx>>;
+
+#[derive(Clone, Debug, Serialize, PartialEq, Eq, Hash)]
+pub struct TaskRef<'ctx> {
+ pub id: &'ctx TaskID,
+ pub args: Rc<TaskArgs>,
+}
+
+impl<'ctx> Display for TaskRef<'ctx> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ if !f.alternate() {
+ return self.id.fmt(f);
+ }
+
+ let pv_arg = match self.args.get("pv") {
+ Some(Arg::String(s)) => Some(s),
+ _ => None,
+ };
+ let host_arg = match self.args.get("host") {
+ Some(Arg::Platform(platform)) => Some(platform),
+ _ => None,
+ };
+ let target_arg = match self.args.get("target") {
+ Some(Arg::Platform(platform)) => Some(platform),
+ _ => None,
+ };
+
+ write!(f, "{}", self.id.recipe)?;
+ if let Some(pv) = pv_arg {
+ write!(f, "-{}", pv)?;
+ }
+ write!(f, ":{}", self.id.task)?;
+
+ if let Some(host) = host_arg {
+ write!(f, "@{}", host.short)?;
+ }
+ if let Some(target) = target_arg {
+ write!(f, "/{}", target.short)?;
+ }
+ Ok(())
+ }
+}
+
+#[derive(Clone, Debug, Serialize, PartialEq, Eq, Hash)]
+pub struct OutputRef<'ctx> {
+ pub task: TaskRef<'ctx>,
+ pub output: &'ctx str,
+}
+
+fn platform_relation(args: &TaskArgs, from: &str, to: &str) -> Option<PlatformRelation> {
+ let plat_from = match args.get(from)? {
+ Arg::Platform(plat) => plat,
+ _ => return None,
+ };
+ let plat_to = match args.get(to)? {
+ Arg::Platform(plat) => plat,
+ _ => return None,
+ };
+
+ let plat_rel = if plat_from == plat_to {
+ PlatformRelation {
+ is_same: true,
+ sysroot: "".to_string(),
+ cross_compile: "".to_string(),
+ }
+ } else {
+ PlatformRelation {
+ is_same: false,
+ sysroot: paths::abs(paths::TASK_SYSROOT),
+ cross_compile: format!("{}/bin/{}-", plat_from.prefix, plat_to.gnu_triplet),
+ }
+ };
+ Some(plat_rel)
+}
+
+#[derive(Debug)]
+pub struct Context {
+ platforms: HashMap<String, args::Arg>,
+ globals: TaskArgs,
+ tasks: HashMap<TaskID, TaskDef>,
+}
+
+impl Context {
+ pub fn new(tasks: HashMap<TaskID, TaskDef>) -> Self {
+ let platforms: HashMap<_, _> = IntoIterator::into_iter([
+ arg(
+ "build",
+ args::Platform {
+ short: "build".to_string(),
+ gnu_triplet: "x86_64-linux-gnu".to_string(),
+ karch: "x86_64".to_string(),
+ prefix: "/opt/toolchain".to_string(),
+ },
+ ),
+ arg(
+ "aarch64",
+ args::Platform {
+ short: "aarch64".to_string(),
+ gnu_triplet: "aarch64-linux-gnu".to_string(),
+ karch: "arm64".to_string(),
+ prefix: "/usr".to_string(),
+ },
+ ),
+ ])
+ .collect();
+
+ let globals = TaskArgs::from_iter([
+ ("build".to_string(), platforms["build"].clone()),
+ arg("workdir", paths::abs(paths::TASK_WORKDIR)),
+ arg("dldir", paths::abs(paths::TASK_DLDIR)),
+ arg("destdir", paths::abs(paths::TASK_DESTDIR)),
+ arg("sysroot", paths::abs(paths::TASK_SYSROOT)),
+ ]);
+
+ Context {
+ platforms,
+ globals,
+ tasks,
+ }
+ }
+
+ pub fn get<'ctx>(&'ctx self, id: &'ctx TaskID) -> Result<&TaskDef> {
+ self.tasks.get(id).ok_or(Error {
+ task: id,
+ kind: ErrorKind::TaskNotFound,
+ })
+ }
+
+ fn task_ref<'ctx>(&'ctx self, id: &'ctx TaskID, args: &TaskArgs) -> Result<TaskRef> {
+ let task_def = self.get(id)?;
+
+ let mut arg_def: HashMap<_, _> = task_def.args.iter().map(|(k, &v)| (k, v)).collect();
+ for (key, arg) in &self.globals {
+ // TODO: Handle conflicts between explicit args and globals
+ arg_def.insert(key, ArgType::from(arg));
+ }
+
+ let mut new_args = TaskArgs::default();
+
+ for (key, typ) in arg_def {
+ if let Some(arg) = args.get(key) {
+ if ArgType::from(arg) == typ {
+ new_args.set(key, Some(arg));
+ continue;
+ }
+ }
+ return Err(Error {
+ task: id,
+ kind: ErrorKind::InvalidArgument(key),
+ });
+ }
+
+ let build_to_host = platform_relation(&new_args, "build", "host");
+ let host_to_target = platform_relation(&new_args, "host", "target");
+ let build_to_target = platform_relation(&new_args, "build", "target");
+
+ let cross_compile = build_to_host
+ .as_ref()
+ .map(|build_to_host| build_to_host.cross_compile.clone());
+
+ new_args.set("build_to_host", build_to_host);
+ new_args.set("host_to_target", host_to_target);
+ new_args.set("build_to_target", build_to_target);
+
+ new_args.set("cross_compile", cross_compile);
+
+ new_args.set("pn", Some(task_def.meta.name.clone()));
+ new_args.set("pv", task_def.meta.version.clone());
+
+ Ok(TaskRef {
+ id,
+ args: Rc::new(new_args),
+ })
+ }
+
+ pub fn parse<'ctx>(&'ctx self, s: &str) -> error::Result<TaskRef> {
+ lazy_static! {
+ static ref RE: Regex = Regex::new(
+ r"^(?P<recipe>[[:word:]-]+):(?P<task>[[:word:]-]+)(?:@(?P<host>[[:word:]-]+))?(?:/(?P<target>[[:word:]-]+))?$",
+ ).unwrap();
+ }
+
+ let cap = RE.captures(s).context("Invalid task syntax")?;
+
+ let recipe = cap["recipe"].to_string();
+ let task = cap["task"].to_string();
+
+ let id = TaskID { recipe, task };
+ let (ctx_id, _) = self
+ .tasks
+ .get_key_value(&id)
+ .with_context(|| format!("Task {}:{} not found", id.recipe, id.task))?;
+
+ let mut args = self.globals.clone();
+
+ if let Some(host) = cap.name("host") {
+ let plat = self
+ .platforms
+ .get(host.as_str())
+ .with_context(|| format!("Platform '{}' not found", host.as_str()))?;
+ args.set("host", Some(plat));
+ args.set("target", Some(plat));
+ }
+ if let Some(target) = cap.name("target") {
+ let plat = self
+ .platforms
+ .get(target.as_str())
+ .with_context(|| format!("Platform '{}' not found", target.as_str()))?;
+ args.set("target", Some(plat));
+ }
+
+ self.task_ref(ctx_id, &args)
+ .with_context(|| format!("Failed to instantiate task {}:{}", id.recipe, id.task))
+ }
+
+ fn map_args<'ctx, 'args>(
+ task: &'ctx TaskID,
+ mapping: &'ctx ArgMapping,
+ args: &'args TaskArgs,
+ build_dep: bool,
+ ) -> Result<'ctx, Cow<'args, TaskArgs>> {
+ if mapping.0.is_empty() && !build_dep {
+ return Ok(Cow::Borrowed(args));
+ }
+
+ let mut ret = args.clone();
+
+ if build_dep {
+ ret.set("host", args.get("build"));
+ ret.set("target", args.get("host"));
+ }
+
+ for (to, from) in &mapping.0 {
+ let value = args.get(from).ok_or(Error {
+ task,
+ kind: ErrorKind::InvalidArgRef(to),
+ })?;
+ ret.set(to, Some(value.clone()));
+ }
+
+ Ok(Cow::Owned(ret))
+ }
+
+ fn inherit_ref<'ctx>(&'ctx self, dep: &'ctx InheritDep, args: &TaskArgs) -> Result<TaskRef> {
+ let mapped_args = Context::map_args(&dep.dep.id, &dep.dep.args, args, false)?;
+ self.task_ref(&dep.dep.id, mapped_args.as_ref())
+ }
+
+ pub fn output_ref<'ctx>(
+ &'ctx self,
+ dep: &'ctx OutputDep,
+ args: &TaskArgs,
+ build_dep: bool,
+ ) -> Result<OutputRef<'ctx>> {
+ let mapped_args = Context::map_args(&dep.dep.id, &dep.dep.args, args, build_dep)?;
+ Ok(OutputRef {
+ task: self.task_ref(&dep.dep.id, mapped_args.as_ref())?,
+ output: &dep.output,
+ })
+ }
+
+ pub fn get_inherit_depend<'ctx>(
+ &'ctx self,
+ task_ref: &TaskRef<'ctx>,
+ ) -> Result<Option<TaskRef>> {
+ let task = self.get(task_ref.id)?;
+ let inherit = match &task.inherit {
+ Some(inherit) => inherit,
+ None => return Ok(None),
+ };
+ Some(self.inherit_ref(inherit, &task_ref.args)).transpose()
+ }
+
+ fn inherit_iter<'ctx>(
+ &'ctx self,
+ task_ref: &TaskRef<'ctx>,
+ ) -> impl Iterator<Item = Result<TaskRef>> {
+ struct Iter<'ctx>(&'ctx Context, Option<Result<'ctx, TaskRef<'ctx>>>);
+
+ impl<'ctx> Iterator for Iter<'ctx> {
+ type Item = Result<'ctx, TaskRef<'ctx>>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let task_ref = match self.1.take()? {
+ Ok(task_ref) => task_ref,
+ Err(err) => return Some(Err(err)),
+ };
+ self.1 = self.0.get_inherit_depend(&task_ref).transpose();
+ Some(Ok(task_ref))
+ }
+ }
+
+ Iter(self, Some(Ok(task_ref.clone())))
+ }
+
+ pub fn get_build_depends<'ctx>(
+ &'ctx self,
+ task_ref: &TaskRef<'ctx>,
+ ) -> Result<HashSet<OutputRef>> {
+ let mut ret = HashSet::new();
+ let mut allow_noinherit = true;
+
+ for current in self.inherit_iter(task_ref) {
+ let task_ref = current?;
+ let task = self.get(task_ref.id)?;
+ let entries = task
+ .build_depends
+ .iter()
+ .filter(|dep| allow_noinherit || !dep.noinherit)
+ .map(|dep| self.output_ref(dep, &task_ref.args, true))
+ .collect::<Result<Vec<_>>>()?;
+ ret.extend(entries);
+
+ allow_noinherit = false;
+ }
+
+ Ok(ret)
+ }
+
+ pub fn get_host_depends<'ctx>(
+ &'ctx self,
+ task_ref: &TaskRef<'ctx>,
+ ) -> Result<HashSet<OutputRef>> {
+ let mut ret = HashSet::new();
+ let mut allow_noinherit = true;
+
+ for current in self.inherit_iter(task_ref) {
+ let task_ref = current?;
+ let task = self.get(task_ref.id)?;
+ let entries = task
+ .depends
+ .iter()
+ .filter(|dep| allow_noinherit || !dep.noinherit)
+ .map(|dep| self.output_ref(dep, &task_ref.args, false))
+ .collect::<Result<Vec<_>>>()?;
+ ret.extend(entries);
+
+ allow_noinherit = false;
+ }
+
+ Ok(ret)
+ }
+
+ pub fn in_rootfs<'ctx>(&'ctx self, output: &OutputRef<'ctx>) -> bool {
+ let build = self.globals.get("build").unwrap();
+ if output.task.args.get("host") != Some(build) {
+ return false;
+ }
+ if let Some(target) = output.task.args.get("target") {
+ if target != build {
+ return false;
+ }
+ }
+
+ // TODO: Do not hardcode this
+ match (
+ output.task.id.recipe.as_str(),
+ output.task.id.task.as_str(),
+ output.output,
+ ) {
+ ("gmp", "install", "default") => true,
+ ("mpfr", "install", "default") => true,
+ ("mpc", "install", "default") => true,
+ ("zlib", "install", "default") => true,
+ ("binutils", "install", "default") => true,
+ ("gcc", "install", "default") => true,
+ ("libgcc", "install", "default") => true,
+ ("gcc-libs", "install", "default") => true,
+ ("linux-uapi-headers", "install", "default") => true,
+ ("glibc", "install", "default") => true,
+ _ => false,
+ }
+ }
+}
+
+impl Index<&TaskID> for Context {
+ type Output = TaskDef;
+
+ fn index(&self, index: &TaskID) -> &TaskDef {
+ self.tasks.get(index).expect("Invalid TaskID")
+ }
+}