From f7512f6c7d5afd2b5cfa7d485aed79c05c8f7741 Mon Sep 17 00:00:00 2001 From: thesayyn Date: Sun, 7 Dec 2025 21:01:27 -0800 Subject: [PATCH 1/3] lts --- .aspect/config.axl | 2 +- .aspect/user-task.axl | 13 ++++++--- .../src/engine/config/task_list/task_mut.rs | 17 +++++++----- crates/axl-runtime/src/engine/task.rs | 27 +++++++++++++++++++ 4 files changed, 49 insertions(+), 10 deletions(-) diff --git a/.aspect/config.axl b/.aspect/config.axl index 89fbd405d..c90490dcf 100644 --- a/.aspect/config.axl +++ b/.aspect/config.axl @@ -12,6 +12,6 @@ def config(ctx: ConfigContext): ctx.tasks.add(task2, name = "added_by_config", group = []) print("running config") for task in ctx.tasks: - print(task.name, task.group) + print(task.name, task.group, task.binding) pass diff --git a/.aspect/user-task.axl b/.aspect/user-task.axl index 513fa2c74..60088d0b1 100644 --- a/.aspect/user-task.axl +++ b/.aspect/user-task.axl @@ -13,11 +13,18 @@ def _impl2(ctx): print("I am an auto-discovered task in .aspect/user-task.axl") return 0 +bindings = record( + callback = field(typing.Callable[[int], None], lambda _: None), + test = field(int, 0) +) + +def untyped_bindings(): + return struct() + + user_task3 = task( group = ["user"], implementation = _impl, args = {}, - # config = config.type( - # callback = typing.Callable[[]], - # ) + binding = untyped_bindings ) diff --git a/crates/axl-runtime/src/engine/config/task_list/task_mut.rs b/crates/axl-runtime/src/engine/config/task_list/task_mut.rs index 22e859582..35351a19b 100644 --- a/crates/axl-runtime/src/engine/config/task_list/task_mut.rs +++ b/crates/axl-runtime/src/engine/config/task_list/task_mut.rs @@ -87,20 +87,25 @@ impl<'v> values::StarlarkValue<'v> for TaskMut<'v> { } fn get_attr(&self, attribute: &str, heap: &'v Heap) -> Option> { + eprintln!("{}", attribute == "binding"); match attribute { "name" => Some(heap.alloc_str(self.name.borrow().as_str()).to_value()), "group" => Some(heap.alloc(AllocList(self.group.borrow().iter()))), + "binding" => { + if let Some(task) = self.original.downcast_ref::() { + Some(task.binding()) + } else if let Some(task) = self.original.downcast_ref::() { + Some(task.binding()) + } else { + None + } + } _ => None, } } fn dir_attr(&self) -> Vec { - vec![ - "group".into(), - "name".into(), - "args".into(), - "config".into(), - ] + vec!["name".into(), "group".into(), "binding".into()] } } diff --git a/crates/axl-runtime/src/engine/task.rs b/crates/axl-runtime/src/engine/task.rs index 55bbc3362..8071f8351 100644 --- a/crates/axl-runtime/src/engine/task.rs +++ b/crates/axl-runtime/src/engine/task.rs @@ -5,13 +5,16 @@ use allocative::Allocative; use derive_more::Display; use starlark::collections::SmallMap; use starlark::environment::GlobalsBuilder; +use starlark::eval::Evaluator; use starlark::starlark_module; use starlark::starlark_simple_value; use starlark::typing::ParamIsRequired; use starlark::typing::ParamSpec; use starlark::values; use starlark::values::list::UnpackList; +use starlark::values::none::NoneOr; use starlark::values::none::NoneType; +use starlark::values::record::Record; use starlark::values::starlark_value; use starlark::values::typing::StarlarkCallableParamSpec; use starlark::values::NoSerialize; @@ -27,6 +30,7 @@ pub trait TaskLike<'v>: 'v { fn description(&self) -> &String; fn group(&self) -> &Vec; fn name(&self) -> &String; + fn binding(&self) -> values::Value<'v>; } pub trait AsTaskLike<'v>: TaskLike<'v> { @@ -46,6 +50,7 @@ where #[display("")] pub struct Task<'v> { r#impl: values::Value<'v>, + binding: values::Value<'v>, #[allocative(skip)] pub(super) args: SmallMap, pub(super) description: String, @@ -57,6 +62,9 @@ impl<'v> Task<'v> { pub fn implementation(&self) -> values::Value<'v> { self.r#impl } + pub fn binding(&self) -> values::Value<'v> { + self.binding + } pub fn args(&self) -> &SmallMap { &self.args } @@ -72,9 +80,13 @@ impl<'v> Task<'v> { } impl<'v> TaskLike<'v> for Task<'v> { + fn binding(&self) -> values::Value<'v> { + self.binding + } fn args(&self) -> &SmallMap { &self.args } + fn description(&self) -> &String { &self.description } @@ -99,8 +111,10 @@ impl<'v> values::Freeze for Task<'v> { type Frozen = FrozenTask; fn freeze(self, freezer: &values::Freezer) -> values::FreezeResult { let frozen_impl = self.r#impl.freeze(freezer)?; + let binding = self.binding.freeze(freezer)?; Ok(FrozenTask { args: self.args, + binding: binding, r#impl: frozen_impl, description: self.description, group: self.group, @@ -113,6 +127,7 @@ impl<'v> values::Freeze for Task<'v> { #[display("")] pub struct FrozenTask { r#impl: values::FrozenValue, + binding: values::FrozenValue, #[allocative(skip)] pub(super) args: SmallMap, pub(super) description: String, @@ -134,6 +149,9 @@ impl FrozenTask { } impl<'v> TaskLike<'v> for FrozenTask { + fn binding(&self) -> values::Value<'v> { + self.binding.to_value() + } fn args(&self) -> &SmallMap { &self.args } @@ -190,7 +208,9 @@ pub fn register_globals(globals: &mut GlobalsBuilder) { #[starlark(require = named)] args: values::dict::UnpackDictEntries<&'v str, TaskArg>, #[starlark(require = named, default = String::new())] description: String, #[starlark(require = named, default = UnpackList::default())] group: UnpackList, + #[starlark(require = named, default = NoneOr::None)] binding: NoneOr>, #[starlark(require = named, default = String::new())] name: String, + eval: &mut Evaluator<'v, '_, '_>, ) -> starlark::Result> { if group.items.len() > MAX_TASK_GROUPS { return Err(anyhow::anyhow!( @@ -199,12 +219,19 @@ pub fn register_globals(globals: &mut GlobalsBuilder) { ) .into()); } + let binding = if let Some(binding) = binding.into_option() { + eval.eval_function(binding, &[], &[])? + } else { + Value::new_none() + }; + let mut args_ = SmallMap::new(); for (arg, def) in args.entries { args_.insert(arg.to_owned(), def.clone()); } Ok(Task { args: args_, + binding, r#impl: implementation.0, description, group: group.items, From 0653ecdee4b7e79857642736445bc6c6671c2d04 Mon Sep 17 00:00:00 2001 From: thesayyn Date: Sun, 7 Dec 2025 21:28:07 -0800 Subject: [PATCH 2/3] delivery example --- .aspect/config.axl | 5 +- .aspect/delivery.axl | 329 ++++++++++++++++++++++++++ crates/axl-runtime/src/engine/task.rs | 1 - 3 files changed, 331 insertions(+), 4 deletions(-) create mode 100644 .aspect/delivery.axl diff --git a/.aspect/config.axl b/.aspect/config.axl index c90490dcf..486ea5f65 100644 --- a/.aspect/config.axl +++ b/.aspect/config.axl @@ -10,8 +10,7 @@ task2 = task( def config(ctx: ConfigContext): ctx.tasks.add(task2, name = "added_by_config", group = []) - print("running config") - for task in ctx.tasks: - print(task.name, task.group, task.binding) + # for task in ctx.tasks: + # print(task.name, task.group, task.binding) pass diff --git a/.aspect/delivery.axl b/.aspect/delivery.axl new file mode 100644 index 000000000..ed3ec13f1 --- /dev/null +++ b/.aspect/delivery.axl @@ -0,0 +1,329 @@ +""" +Delivery task that coordinates artifact delivery via Redis. + +Reads the delivery manifest for a commit, delivers each target via bazel run +with stamping enabled, and signs artifacts to prevent re-delivery. + +See: DELIVERY_REDIS_KEYS.md for key format documentation. +""" + +# Redis command helpers + +def _redis_cmd(ctx, redis_cfg, *cmd_args): + """Execute a redis-cli command and return the output.""" + host, port, use_tls = redis_cfg + + cmd = ctx.std.process.command("redis-cli") + cmd.args(["-h", host, "-p", str(port)]) + # Add connection timeout to prevent hanging on TLS mismatch + cmd.args(["-t", "5"]) + if use_tls: + cmd.arg("--tls") + cmd.args(list(cmd_args)) + cmd.stdout("piped") + cmd.stderr("piped") + + child = cmd.spawn() + output = child.wait_with_output() + + if not output.status.success: + fail("redis-cli failed: " + output.stderr) + + return output.stdout.strip() + +def _redis_get(ctx, redis_cfg, key): + """GET a value by key from Redis.""" + result = _redis_cmd(ctx, redis_cfg, "GET", key) + return None if result == "(nil)" else result + +def _redis_set(ctx, redis_cfg, key, value): + """SET a key-value pair in Redis.""" + return _redis_cmd(ctx, redis_cfg, "SET", key, value) + +def _redis_setnx(ctx, redis_cfg, key, value): + """SETNX - set if not exists. Returns True if key was set, False if it already existed.""" + result = _redis_cmd(ctx, redis_cfg, "SETNX", key, value) + return result == "1" + +def _redis_del(ctx, redis_cfg, key): + """DEL a key from Redis.""" + return _redis_cmd(ctx, redis_cfg, "DEL", key) + +def _redis_lrange(ctx, redis_cfg, key, start, stop): + """LRANGE to get list elements from Redis.""" + result = _redis_cmd(ctx, redis_cfg, "LRANGE", key, str(start), str(stop)) + if not result or result == "(empty list or set)": + return [] + # Parse redis-cli output format (numbered lines) + lines = result.split("\n") + items = [] + for line in lines: + # Format: "1) value" or just "value" + if ") " in line: + items.append(line.split(") ", 1)[1]) + elif line: + items.append(line) + return items + +# Key builders (match format from delivery-base.task.ts) + +def _artifact_metadata_key(ci_host, output_sha, workspace): + """ + Redis key for mapping of output sha -> metadata. + Format: :: + """ + return "{}:{}:{}".format(ci_host, output_sha, workspace) + +def _delivery_manifest_key(ci_host, commit_sha, workspace): + """ + Redis key for mapping of commit sha -> delivery target list. + Format: :: + """ + return "{}:{}:{}".format(ci_host, commit_sha, workspace) + +def _output_sha_lookup_key(ci_host, commit_sha, workspace, label): + """ + Redis key for reverse mapping of label -> output sha for a commit. + Format: output-sha::::