From c32e7176168dc6f771145d70182f457db78f70b4 Mon Sep 17 00:00:00 2001 From: thesayyn Date: Fri, 27 Feb 2026 15:57:56 -0800 Subject: [PATCH] feat: add ctx.bazel.info api --- .aspect/axl.axl | 27 +++++++++++ crates/axl-runtime/src/engine/bazel/mod.rs | 55 ++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/.aspect/axl.axl b/.aspect/axl.axl index 4233fe9ba..fe9714402 100644 --- a/.aspect/axl.axl +++ b/.aspect/axl.axl @@ -346,6 +346,32 @@ def test_large_bes(ctx: TaskContext, tc: int, temp_dir: str) -> int: return tc +def test_bazel_info(ctx: TaskContext, tc: int) -> int: + info = ctx.bazel.info() + + # Should return a dict + tc = test_case(tc, type(info) == "dict", "bazel.info() should return a dict") + tc = test_case(tc, len(info) > 0, "bazel.info() dict should be non-empty") + + # Common keys should be present + for key in ["output_base", "execution_root", "workspace", "bazel-bin", "bazel-genfiles", "bazel-testlogs"]: + tc = test_case(tc, key in info, "bazel.info() should contain key: " + key) + tc = test_case(tc, type(info[key]) == "string", "bazel.info()[" + key + "] should be a string") + tc = test_case(tc, info[key] != "", "bazel.info()[" + key + "] should be non-empty") + + # output_base should be an absolute path + tc = test_case(tc, info["output_base"].startswith("/"), "output_base should be an absolute path") + + # workspace should match root_dir + root_dir = ctx.std.env.root_dir() + tc = test_case(tc, info["workspace"] == root_dir, "bazel.info()['workspace'] should match ctx.std.env.root_dir()") + + # workdir override: pointing at the same workspace should give the same output_base + info2 = ctx.bazel.info(workdir = root_dir) + tc = test_case(tc, info2["output_base"] == info["output_base"], "bazel.info(workdir=root_dir) should return same output_base") + + return tc + def test_http(ctx: TaskContext, tc: int, temp_dir: str) -> int: # Test HTTP download with integrity/sha256 verification # Using a well-known static file: LICENSE from this repo's GitHub @@ -437,6 +463,7 @@ def impl(ctx: TaskContext) -> int: tc = test_build_events(ctx, tc, temp_dir) tc = test_large_bes(ctx, tc, temp_dir) tc = test_http(ctx, tc, temp_dir) + tc = test_bazel_info(ctx, tc) print(tc, "tests passed") return 0 diff --git a/crates/axl-runtime/src/engine/bazel/mod.rs b/crates/axl-runtime/src/engine/bazel/mod.rs index 55fc0ffd6..06acdcd1f 100644 --- a/crates/axl-runtime/src/engine/bazel/mod.rs +++ b/crates/axl-runtime/src/engine/bazel/mod.rs @@ -1,8 +1,10 @@ use std::collections::HashMap; +use std::process::Stdio; use allocative::Allocative; use derive_more::Display; use either::Either; +use starlark::collections::SmallMap; use starlark::environment::Methods; use starlark::environment::MethodsBuilder; use starlark::environment::MethodsStatic; @@ -210,6 +212,59 @@ pub(crate) fn bazel_methods(registry: &mut MethodsBuilder) { fn query<'v>(#[allow(unused)] this: values::Value<'v>) -> starlark::Result { Ok(query::Query::new()) } + + /// Run `bazel info` and return all key/value pairs as a dict. + /// + /// Blocks until the command completes. Raises an error if Bazel exits + /// with a non-zero code. + /// + /// # Arguments + /// * `workdir`: workspace root to run `bazel info` in (default: inferred from ctx) + /// + /// **Examples** + /// + /// ```python + /// def _show_info_impl(ctx): + /// info = ctx.bazel.info() + /// print(info["output_base"]) + /// print(info["execution_root"]) + /// ``` + fn info<'v>( + #[allow(unused)] this: values::Value<'v>, + #[starlark(require = named, default = NoneOr::None)] workdir: NoneOr, + eval: &mut Evaluator<'v, '_, '_>, + ) -> anyhow::Result> { + let store = AxlStore::from_eval(eval)?; + let workdir = workdir + .into_option() + .map(std::path::PathBuf::from) + .unwrap_or_else(|| store.root_dir.clone()); + + let output = std::process::Command::new("bazel") + .arg("info") + .current_dir(&workdir) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .stdin(Stdio::null()) + .output() + .map_err(|e| anyhow::anyhow!("failed to spawn bazel: {}", e))?; + + if !output.status.success() { + anyhow::bail!( + "bazel info failed with exit code {:?}", + output.status.code() + ); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + let mut map = SmallMap::new(); + for line in stdout.lines() { + if let Some((key, value)) = line.split_once(": ") { + map.insert(key.trim().to_string(), value.trim().to_string()); + } + } + Ok(map) + } } #[starlark_module]