summaryrefslogtreecommitdiffstats
path: root/crates/rebel-resolve/src/context.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/rebel-resolve/src/context.rs')
-rw-r--r--crates/rebel-resolve/src/context.rs549
1 files changed, 549 insertions, 0 deletions
diff --git a/crates/rebel-resolve/src/context.rs b/crates/rebel-resolve/src/context.rs
new file mode 100644
index 0000000..996d981
--- /dev/null
+++ b/crates/rebel-resolve/src/context.rs
@@ -0,0 +1,549 @@
+use std::{
+ borrow::Cow,
+ cmp::Ordering,
+ collections::{HashMap, HashSet},
+ fmt::Display,
+ hash::Hash,
+ ops::Index,
+ rc::Rc,
+ result,
+};
+
+use rebel_common::{
+ error::{self, Contextualizable},
+ string_hash::ArchiveHash,
+ types::TaskIDRef,
+};
+
+use crate::{
+ args::*,
+ paths,
+ pin::{self, Pins},
+ task::*,
+};
+
+#[derive(Debug, Clone, Copy)]
+pub enum ErrorKind<'a> {
+ TaskNotFound,
+ InvalidArgument(&'a str),
+ InvalidArgRef(&'a str),
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Error<'a> {
+ pub task: TaskIDRef<'a>,
+ pub kind: ErrorKind<'a>,
+}
+
+impl<'a> Display for Error<'a> {
+ 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<'a> From<Error<'a>> for error::Error {
+ fn from(err: Error) -> Self {
+ error::Error::new(err)
+ }
+}
+
+pub type Result<'a, T> = result::Result<T, Error<'a>>;
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+pub struct TaskRef<'ctx> {
+ pub id: TaskIDRef<'ctx>,
+ 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 version_arg = match self.args.get("version") {
+ 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(version) = version_arg {
+ write!(f, "#{}", version)?;
+ }
+ write!(f, "::{}", self.id.task)?;
+
+ if host_arg.is_some() || target_arg.is_some() {
+ write!(f, "@")?;
+ }
+
+ if let Some(host) = host_arg {
+ write!(f, "{}", host.short)?;
+ }
+ if let Some(target) = target_arg {
+ write!(f, ":{}", target.short)?;
+ }
+ Ok(())
+ }
+}
+
+#[derive(Clone, Debug, 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::TASK_SYSROOT.to_string(),
+ cross_compile: format!("{}/bin/{}-", plat_from.prefix, plat_to.gnu_triplet),
+ }
+ };
+ Some(plat_rel)
+}
+
+#[derive(Debug)]
+pub struct Context {
+ platforms: HashMap<String, Arg>,
+ globals: TaskArgs,
+ tasks: HashMap<String, HashMap<String, Vec<TaskDef>>>,
+ rootfs: (ArchiveHash, String),
+}
+
+impl Context {
+ pub fn new(
+ mut tasks: HashMap<String, HashMap<String, Vec<TaskDef>>>,
+ pins: Pins,
+ ) -> error::Result<Self> {
+ let platforms: HashMap<_, _> = [
+ arg(
+ "build",
+ 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",
+ Platform {
+ short: "aarch64".to_string(),
+ gnu_triplet: "aarch64-linux-gnu".to_string(),
+ karch: "arm64".to_string(),
+ prefix: "/usr".to_string(),
+ },
+ ),
+ ]
+ .into_iter()
+ .collect();
+
+ let globals = TaskArgs::from_iter([
+ ("build".to_string(), platforms["build"].clone()),
+ arg("workdir", paths::TASK_WORKDIR.to_string()),
+ arg("dldir", paths::TASK_DLDIR.to_string()),
+ arg("destdir", paths::TASK_DESTDIR.to_string()),
+ arg("sysroot", paths::TASK_SYSROOT.to_string()),
+ ]);
+ let (rootfs, rootfs_provides) =
+ Context::handle_pins(pins).context("Failed to process pin list")?;
+
+ Context::add_rootfs_tasks(&mut tasks, rootfs_provides, &globals)
+ .context("Failed to determine rootfs-provided tasks from pin list")?;
+
+ Ok(Context {
+ platforms,
+ globals,
+ tasks,
+ rootfs,
+ })
+ }
+
+ fn handle_pins(pins: Pins) -> error::Result<((ArchiveHash, String), Vec<pin::Provides>)> {
+ let mut ret = None;
+
+ for (name, pin) in pins {
+ if pin.is_rootfs {
+ if ret.is_some() {
+ return Err(error::Error::new("Multiple is-rootfs pins"));
+ }
+ let hash = pin.hash.context("is-rootfs pin without hash")?;
+
+ ret = Some(((hash, name), pin.provides));
+ }
+ }
+
+ ret.context("No is-rootfs pins")
+ }
+
+ fn add_rootfs_tasks(
+ tasks: &mut HashMap<String, HashMap<String, Vec<TaskDef>>>,
+ provides: Vec<pin::Provides>,
+ globals: &TaskArgs,
+ ) -> error::Result<()> {
+ let build = globals.get("build").unwrap();
+
+ for pin::Provides {
+ recipe,
+ task,
+ output,
+ args,
+ } in provides
+ {
+ let mut task_def = TaskDef::default();
+
+ if let Some(host) = args.host {
+ if host != "build" {
+ return Err(error::Error::new(format!("Invalid host value '{}'", host)));
+ }
+ task_def.args.insert("host".to_string(), build.into());
+ task_def.arg_match.set("host", Some(build));
+ }
+
+ if let Some(target) = args.target {
+ if target != "build" {
+ return Err(error::Error::new(format!(
+ "Invalid target value '{}'",
+ target
+ )));
+ }
+ task_def.args.insert("target".to_string(), build.into());
+ task_def.arg_match.set("target", Some(build));
+ }
+
+ for output_entry in output {
+ task_def
+ .output
+ .insert(output_entry.to_string(), Output::default());
+ }
+
+ task_def.priority = i32::MAX;
+
+ tasks
+ .entry(recipe)
+ .or_default()
+ .entry(task)
+ .or_default()
+ .push(task_def);
+ }
+
+ Ok(())
+ }
+
+ pub fn get_rootfs(&self) -> &(ArchiveHash, String) {
+ &self.rootfs
+ }
+
+ fn match_task(task: &TaskDef, args: &TaskArgs) -> bool {
+ task.arg_match
+ .iter()
+ .all(|(key, value)| args.get(key) == Some(value))
+ }
+
+ fn compare_tasks(task1: &TaskDef, task2: &TaskDef) -> Ordering {
+ task1
+ .priority
+ .cmp(&task2.priority)
+ .then(deb_version::compare_versions(
+ task1.meta.version.as_deref().unwrap_or_default(),
+ task2.meta.version.as_deref().unwrap_or_default(),
+ ))
+ }
+
+ fn select_task<'ctx>(tasks: &'ctx [TaskDef], args: &TaskArgs) -> Option<&'ctx TaskDef> {
+ tasks
+ .iter()
+ .filter(|task| Self::match_task(task, args))
+ .max_by(|task1, task2| Self::compare_tasks(task1, task2))
+ }
+
+ fn get_by_ref(&self, id: TaskIDRef) -> Option<&[TaskDef]> {
+ Some(self.tasks.get(id.recipe)?.get(id.task)?)
+ }
+
+ fn get_with_args<'a>(&self, id: TaskIDRef<'a>, args: &TaskArgs) -> Result<'a, &TaskDef> {
+ self.get_by_ref(id)
+ .and_then(|tasks| Self::select_task(tasks, args))
+ .ok_or(Error {
+ task: id,
+ kind: ErrorKind::TaskNotFound,
+ })
+ }
+
+ pub fn get<'a>(&self, task: &TaskRef<'a>) -> Result<'a, &TaskDef> {
+ self.get_with_args(task.id, task.args.as_ref())
+ }
+
+ fn task_ref<'ctx>(&'ctx self, id: TaskIDRef<'ctx>, args: &TaskArgs) -> Result<TaskRef> {
+ let task_def = self.get_with_args(id, args)?;
+
+ 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("basename", Some(task_def.meta.basename.clone()));
+ new_args.set("recipename", Some(task_def.meta.recipename.clone()));
+ new_args.set("recipe", Some(task_def.meta.recipe.clone()));
+ new_args.set("name", Some(task_def.meta.name.clone()));
+ new_args.set("version", task_def.meta.version.clone());
+
+ Ok(TaskRef {
+ id,
+ args: Rc::new(new_args),
+ })
+ }
+
+ pub fn lookup(
+ &self,
+ id: TaskIDRef,
+ host: Option<&str>,
+ target: Option<&str>,
+ ) -> error::Result<TaskRef> {
+ let (ctx_recipe, recipe_tasks) = self
+ .tasks
+ .get_key_value(id.recipe)
+ .with_context(|| format!("Task {} not found", id))?;
+ let (ctx_task, _) = recipe_tasks
+ .get_key_value(id.task)
+ .with_context(|| format!("Task {} not found", id))?;
+ let ctx_id = TaskIDRef {
+ recipe: ctx_recipe,
+ task: ctx_task,
+ };
+
+ let mut args = self.globals.clone();
+
+ if let Some(host) = host {
+ let plat = self
+ .platforms
+ .get(host)
+ .with_context(|| format!("Platform '{}' not found", host))?;
+ args.set("host", Some(plat));
+ args.set("target", Some(plat));
+ }
+ if let Some(target) = target {
+ let plat = self
+ .platforms
+ .get(target)
+ .with_context(|| format!("Platform '{}' not found", target))?;
+ args.set("target", Some(plat));
+ }
+
+ let task_ref = self
+ .task_ref(ctx_id, &args)
+ .with_context(|| format!("Failed to instantiate task {}", id))?;
+
+ Ok(task_ref)
+ }
+
+ fn map_args<'ctx, 'args>(
+ task: TaskIDRef<'ctx>,
+ 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 parent_ref<'ctx>(
+ &'ctx self,
+ dep_of: TaskIDRef<'ctx>,
+ dep: &'ctx ParentDep,
+ args: &TaskArgs,
+ ) -> Result<TaskRef> {
+ let id = dep.dep.id(dep_of.recipe);
+ let mapped_args = Context::map_args(id, &dep.dep.args, args, false)?;
+ self.task_ref(id, mapped_args.as_ref())
+ }
+
+ pub fn output_ref<'ctx>(
+ &'ctx self,
+ dep_of: TaskIDRef<'ctx>,
+ dep: &'ctx OutputDep,
+ args: &TaskArgs,
+ build_dep: bool,
+ ) -> Result<OutputRef<'ctx>> {
+ let id = dep.dep.id(dep_of.recipe);
+ let mapped_args = Context::map_args(id, &dep.dep.args, args, build_dep)?;
+ Ok(OutputRef {
+ task: self.task_ref(id, mapped_args.as_ref())?,
+ output: &dep.output,
+ })
+ }
+
+ pub fn get_parent_depend<'ctx>(
+ &'ctx self,
+ task_ref: &TaskRef<'ctx>,
+ ) -> Result<Option<TaskRef>> {
+ let task = self.get(task_ref)?;
+ let Some(parent) = &task.parent else {
+ return Ok(None);
+ };
+ Some(self.parent_ref(task_ref.id, parent, &task_ref.args)).transpose()
+ }
+
+ fn ancestor_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_parent_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.ancestor_iter(task_ref) {
+ let current_ref = current?;
+ let task = self.get(&current_ref)?;
+ let entries = task
+ .build_depends
+ .iter()
+ .filter(|dep| allow_noinherit || !dep.noinherit)
+ .map(|dep| self.output_ref(task_ref.id, dep, &current_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.ancestor_iter(task_ref) {
+ let current_ref = current?;
+ let task = self.get(&current_ref)?;
+ let entries = task
+ .depends
+ .iter()
+ .filter(|dep| allow_noinherit || !dep.noinherit)
+ .map(|dep| self.output_ref(task_ref.id, dep, &current_ref.args, false))
+ .collect::<Result<Vec<_>>>()?;
+ ret.extend(entries);
+
+ allow_noinherit = false;
+ }
+
+ Ok(ret)
+ }
+}
+
+impl Index<&TaskRef<'_>> for Context {
+ type Output = TaskDef;
+
+ fn index(&self, index: &TaskRef) -> &TaskDef {
+ self.get(index).expect("Invalid TaskRef")
+ }
+}