diff --git a/.aspect/axl.axl b/.aspect/axl.axl index 9222ad7f1..88bd3997d 100644 --- a/.aspect/axl.axl +++ b/.aspect/axl.axl @@ -417,6 +417,32 @@ def test_http(ctx: TaskContext, tc: int, temp_dir: str) -> int: return tc +def test_conditional_flags(ctx: TaskContext, tc: int) -> int: + # Test 1: plain string flags still work (backward compat) + build1 = ctx.bazel.build( + "//crates/aspect-cli:aspect-cli", + flags = ["--nobuild"], + ) + tc = test_case(tc, build1.wait().success, "plain flags should still work") + + # Test 2: always-matching conditional flag is applied + # ("--nobuild", ">=1") should always match; build completes with analysis only + build2 = ctx.bazel.build( + "//crates/aspect-cli:aspect-cli", + flags = [("--nobuild", ">=1")], + ) + tc = test_case(tc, build2.wait().success, "always-matching conditional flag should be applied") + + # Test 3: never-matching conditional flag is dropped + # "--ASPECT_NEVER_MATCH_FLAG_XYZ" would cause a hard bazel error if NOT dropped + build3 = ctx.bazel.build( + "//crates/aspect-cli:aspect-cli", + flags = ["--nobuild", ("--ASPECT_NEVER_MATCH_FLAG_XYZ", ">=99999")], + ) + tc = test_case(tc, build3.wait().success, "never-matching conditional flag should be silently dropped") + + return tc + def test_http_get_post(ctx: TaskContext, tc: int, temp_dir: str) -> int: # Test HTTP get and post methods including unix_socket support @@ -482,6 +508,7 @@ def impl(ctx: TaskContext) -> int: tc = test_http(ctx, tc, temp_dir) tc = test_bazel_info(ctx, tc) tc = test_http_get_post(ctx, tc, temp_dir) + tc = test_conditional_flags(ctx, tc) print(tc, "tests passed") return 0 diff --git a/Cargo.lock b/Cargo.lock index 626f1f8c0..fd0553b1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -351,6 +351,7 @@ dependencies = [ "prost", "rand 0.8.5", "reqwest", + "semver", "serde_json", "sha256", "ssri", @@ -3705,6 +3706,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.227" diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 165daade9..199e04b07 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1207,6 +1207,7 @@ "hyper-tls_0.6.0": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"http-body-util\",\"req\":\"^0.1.0\"},{\"name\":\"hyper\",\"req\":\"^1\"},{\"features\":[\"client-legacy\",\"tokio\"],\"name\":\"hyper-util\",\"req\":\"^0.1.0\"},{\"features\":[\"http1\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1.0\"},{\"name\":\"native-tls\",\"req\":\"^0.2.1\"},{\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"io-std\",\"macros\",\"io-util\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0.0\"},{\"name\":\"tokio-native-tls\",\"req\":\"^0.3\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"}],\"features\":{\"alpn\":[\"native-tls/alpn\"],\"vendored\":[\"native-tls/vendored\"]}}", "hyper-util_0.1.16": "{\"dependencies\":[{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22\"},{\"name\":\"bytes\",\"req\":\"^1.7.1\"},{\"kind\":\"dev\",\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.16\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.16\"},{\"name\":\"http\",\"req\":\"^1.0\"},{\"name\":\"http-body\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1.0\"},{\"name\":\"hyper\",\"req\":\"^1.6.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.4.0\"},{\"name\":\"ipnet\",\"optional\":true,\"req\":\"^2.9\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"percent-encoding\",\"optional\":true,\"req\":\"^2.3\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.4\"},{\"kind\":\"dev\",\"name\":\"pnet_datalink\",\"req\":\"^0.35.0\",\"target\":\"cfg(any(target_os = \\\"linux\\\", target_os = \\\"macos\\\"))\"},{\"kind\":\"dev\",\"name\":\"pretty_env_logger\",\"req\":\"^0.5\"},{\"features\":[\"all\"],\"name\":\"socket2\",\"optional\":true,\"req\":\">=0.5.9, <0.7\"},{\"name\":\"system-configuration\",\"optional\":true,\"req\":\"^0.6.1\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"macros\",\"test-util\",\"signal\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"name\":\"tower-service\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"windows-registry\",\"optional\":true,\"req\":\"^0.5\",\"target\":\"cfg(windows)\"}],\"features\":{\"__internal_happy_eyeballs_tests\":[],\"client\":[\"hyper/client\",\"tokio/net\",\"dep:tracing\",\"dep:futures-channel\",\"dep:tower-service\"],\"client-legacy\":[\"client\",\"dep:socket2\",\"tokio/sync\",\"dep:libc\",\"dep:futures-util\"],\"client-proxy\":[\"client\",\"dep:base64\",\"dep:ipnet\",\"dep:percent-encoding\"],\"client-proxy-system\":[\"dep:system-configuration\",\"dep:windows-registry\"],\"default\":[],\"full\":[\"client\",\"client-legacy\",\"client-proxy\",\"client-proxy-system\",\"server\",\"server-auto\",\"server-graceful\",\"service\",\"http1\",\"http2\",\"tokio\",\"tracing\"],\"http1\":[\"hyper/http1\"],\"http2\":[\"hyper/http2\"],\"server\":[\"hyper/server\"],\"server-auto\":[\"server\",\"http1\",\"http2\"],\"server-graceful\":[\"server\",\"tokio/sync\"],\"service\":[\"dep:tower-service\"],\"tokio\":[\"dep:tokio\",\"tokio/rt\",\"tokio/time\"],\"tracing\":[\"dep:tracing\"]}}", "hyper_1.7.0": "{\"dependencies\":[{\"name\":\"atomic-waker\",\"optional\":true,\"req\":\"^1.1.2\"},{\"name\":\"bytes\",\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"form_urlencoded\",\"req\":\"^1\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\"},{\"features\":[\"sink\"],\"kind\":\"dev\",\"name\":\"futures-channel\",\"req\":\"^0.3\"},{\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3.31\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"alloc\",\"sink\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"h2\",\"optional\":true,\"req\":\"^0.4.2\"},{\"name\":\"http\",\"req\":\"^1\"},{\"name\":\"http-body\",\"req\":\"^1\"},{\"name\":\"http-body-util\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"name\":\"httparse\",\"optional\":true,\"req\":\"^1.9\"},{\"name\":\"httpdate\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"itoa\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"pin-project-lite\",\"optional\":true,\"req\":\"^0.2.4\"},{\"kind\":\"dev\",\"name\":\"pin-project-lite\",\"req\":\"^0.2.4\"},{\"name\":\"pin-utils\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"pretty_env_logger\",\"req\":\"^0.5\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"const_generics\",\"const_new\"],\"name\":\"smallvec\",\"optional\":true,\"req\":\"^1.12\"},{\"kind\":\"dev\",\"name\":\"spmc\",\"req\":\"^0.3\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"fs\",\"macros\",\"net\",\"io-std\",\"io-util\",\"rt\",\"rt-multi-thread\",\"sync\",\"time\",\"test-util\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"tokio-util\",\"req\":\"^0.7.10\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"want\",\"optional\":true,\"req\":\"^0.3\"}],\"features\":{\"capi\":[],\"client\":[\"dep:want\",\"dep:pin-project-lite\",\"dep:smallvec\"],\"default\":[],\"ffi\":[\"dep:http-body-util\",\"dep:futures-util\"],\"full\":[\"client\",\"http1\",\"http2\",\"server\"],\"http1\":[\"dep:atomic-waker\",\"dep:futures-channel\",\"dep:futures-core\",\"dep:httparse\",\"dep:itoa\",\"dep:pin-utils\"],\"http2\":[\"dep:futures-channel\",\"dep:futures-core\",\"dep:h2\"],\"nightly\":[],\"server\":[\"dep:httpdate\",\"dep:pin-project-lite\",\"dep:smallvec\"],\"tracing\":[\"dep:tracing\"]}}", + "hyperlocal_0.9.1": "{\"dependencies\":[{\"name\":\"hex\",\"req\":\"^0.4\"},{\"name\":\"http-body-util\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"hyper\",\"req\":\"^1.3\"},{\"name\":\"hyper-util\",\"optional\":true,\"req\":\"^0.1.2\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"thiserror\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"net\"],\"name\":\"tokio\",\"req\":\"^1.35\"},{\"features\":[\"io-std\",\"io-util\",\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.35\"},{\"name\":\"tower-service\",\"optional\":true,\"req\":\"^0.3\"}],\"features\":{\"client\":[\"http-body-util\",\"hyper/client\",\"hyper/http1\",\"hyper-util/client-legacy\",\"hyper-util/http1\",\"hyper-util/tokio\",\"tower-service\"],\"default\":[\"client\",\"server\"],\"server\":[\"hyper/http1\",\"hyper/server\",\"hyper-util/tokio\"]}}", "iana-time-zone-haiku_0.1.2": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.79\"}],\"features\":{}}", "iana-time-zone_0.1.63": "{\"dependencies\":[{\"name\":\"android_system_properties\",\"req\":\"^0.1.5\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"kind\":\"dev\",\"name\":\"chrono-tz\",\"req\":\"^0.10.1\"},{\"name\":\"core-foundation-sys\",\"req\":\"^0.8.6\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2.1\"},{\"features\":[\"js\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2.1\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"iana-time-zone-haiku\",\"req\":\"^0.1.1\",\"target\":\"cfg(target_os = \\\"haiku\\\")\"},{\"name\":\"js-sys\",\"req\":\"^0.3.66\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"log\",\"req\":\"^0.4.14\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"wasm-bindgen\",\"req\":\"^0.2.89\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.46\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"windows-core\",\"req\":\">=0.56, <=0.61\",\"target\":\"cfg(target_os = \\\"windows\\\")\"}],\"features\":{\"fallback\":[]}}", "icu_collections_2.0.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2.3\"},{\"kind\":\"dev\",\"name\":\"iai\",\"req\":\"^0.1.1\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"postcard\",\"req\":\"^1.0.3\"},{\"default_features\":false,\"features\":[\"zerovec\"],\"name\":\"potential_utf\",\"req\":\"^0.1.1\"},{\"default_features\":false,\"features\":[\"derive\",\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.110\"},{\"default_features\":false,\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.110\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"default_features\":false,\"features\":[\"parse\"],\"kind\":\"dev\",\"name\":\"toml\",\"req\":\"^0.8.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"yoke\",\"req\":\"^0.8.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"zerofrom\",\"req\":\"^0.1.3\"},{\"default_features\":false,\"features\":[\"derive\",\"yoke\"],\"name\":\"zerovec\",\"req\":\"^0.11.1\"}],\"features\":{\"alloc\":[\"zerovec/alloc\"],\"databake\":[\"dep:databake\",\"zerovec/databake\"],\"serde\":[\"dep:serde\",\"zerovec/serde\",\"potential_utf/serde\",\"alloc\"]}}", @@ -1391,6 +1392,7 @@ "security-framework_2.11.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.6\"},{\"name\":\"core-foundation\",\"req\":\"^0.9.4\"},{\"name\":\"core-foundation-sys\",\"req\":\"^0.8.6\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"libc\",\"req\":\"^0.2.139\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"name\":\"num-bigint\",\"optional\":true,\"req\":\"^0.4.6\"},{\"default_features\":false,\"name\":\"security-framework-sys\",\"req\":\"^2.11.1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.3.0\"},{\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.17\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.16\"}],\"features\":{\"OSX_10_10\":[\"OSX_10_9\",\"security-framework-sys/OSX_10_10\"],\"OSX_10_11\":[\"OSX_10_10\",\"security-framework-sys/OSX_10_11\"],\"OSX_10_12\":[\"OSX_10_11\",\"security-framework-sys/OSX_10_12\"],\"OSX_10_13\":[\"OSX_10_12\",\"security-framework-sys/OSX_10_13\",\"alpn\",\"session-tickets\",\"serial-number-bigint\"],\"OSX_10_14\":[\"OSX_10_13\",\"security-framework-sys/OSX_10_14\"],\"OSX_10_15\":[\"OSX_10_14\",\"security-framework-sys/OSX_10_15\"],\"OSX_10_9\":[\"security-framework-sys/OSX_10_9\"],\"alpn\":[],\"default\":[\"OSX_10_12\"],\"job-bless\":[],\"nightly\":[],\"serial-number-bigint\":[\"dep:num-bigint\"],\"session-tickets\":[]}}", "security-framework_3.3.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.6\"},{\"name\":\"core-foundation\",\"req\":\"^0.10\"},{\"name\":\"core-foundation-sys\",\"req\":\"^0.8.6\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"libc\",\"req\":\"^0.2.139\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"default_features\":false,\"name\":\"security-framework-sys\",\"req\":\"^2.14\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.12.0\"},{\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.23\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.16\"}],\"features\":{\"OSX_10_12\":[\"security-framework-sys/OSX_10_12\"],\"OSX_10_13\":[\"OSX_10_12\",\"security-framework-sys/OSX_10_13\",\"alpn\",\"session-tickets\"],\"OSX_10_14\":[\"OSX_10_13\",\"security-framework-sys/OSX_10_14\"],\"OSX_10_15\":[\"OSX_10_14\",\"security-framework-sys/OSX_10_15\"],\"alpn\":[],\"default\":[\"OSX_10_12\"],\"job-bless\":[],\"nightly\":[],\"session-tickets\":[],\"sync-keychain\":[\"OSX_10_13\"]}}", "seize_0.5.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7.0\"},{\"kind\":\"dev\",\"name\":\"crossbeam-epoch\",\"req\":\"^0.9.8\"},{\"kind\":\"dev\",\"name\":\"haphazard\",\"req\":\"^0.1.8\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2\"},{\"features\":[\"Win32_System_Threading\"],\"name\":\"windows-sys\",\"optional\":true,\"req\":\">=0.52, <=0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"fast-barrier\"],\"fast-barrier\":[\"windows-sys\",\"libc\"]}}", + "semver_1.0.27": "{\"dependencies\":[{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"package\":\"serde_core\",\"req\":\"^1.0.220\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\",\"target\":\"cfg(any())\"}],\"features\":{\"default\":[\"std\"],\"serde\":[\"dep:serde\"],\"std\":[]}}", "serde_1.0.227": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"result\"],\"name\":\"serde_core\",\"req\":\"=1.0.227\"},{\"name\":\"serde_derive\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[\"serde_core/alloc\"],\"default\":[\"std\"],\"derive\":[\"serde_derive\"],\"rc\":[\"serde_core/rc\"],\"std\":[\"serde_core/std\"],\"unstable\":[\"serde_core/unstable\"]}}", "serde_core_1.0.227": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"name\":\"serde_derive\",\"req\":\"=1.0.227\",\"target\":\"cfg(any())\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1\"}],\"features\":{\"alloc\":[],\"default\":[\"std\",\"result\"],\"rc\":[],\"result\":[],\"std\":[],\"unstable\":[]}}", "serde_derive_1.0.227": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"proc-macro\"],\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"default_features\":false,\"features\":[\"proc-macro\"],\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"clone-impls\",\"derive\",\"parsing\",\"printing\",\"proc-macro\"],\"name\":\"syn\",\"req\":\"^2.0.81\"}],\"features\":{\"default\":[],\"deserialize_in_place\":[]}}", diff --git a/crates/axl-runtime/BUILD.bazel b/crates/axl-runtime/BUILD.bazel index 0d40d5d68..5c8b2ff4a 100644 --- a/crates/axl-runtime/BUILD.bazel +++ b/crates/axl-runtime/BUILD.bazel @@ -44,6 +44,7 @@ rust_library( "@crates//:wasmi", "@crates//:zstd", "@crates//:base64", + "@crates//:semver", "//crates/axl-proto", "//crates/build-event-stream", "//crates/galvanize", diff --git a/crates/axl-runtime/Cargo.toml b/crates/axl-runtime/Cargo.toml index c48d0a921..8ac87bf94 100644 --- a/crates/axl-runtime/Cargo.toml +++ b/crates/axl-runtime/Cargo.toml @@ -20,6 +20,7 @@ starlark_derive = "0.13.0" starlark_map = "0.13.0" anyhow = "1.0.98" +semver = "1" thiserror = "2.0.12" prost = "0.14.1" diff --git a/crates/axl-runtime/src/engine/bazel/build.rs b/crates/axl-runtime/src/engine/bazel/build.rs index 87c5b9fe8..1666e46f2 100644 --- a/crates/axl-runtime/src/engine/bazel/build.rs +++ b/crates/axl-runtime/src/engine/bazel/build.rs @@ -143,19 +143,60 @@ pub struct Build { } impl Build { - pub fn pid() -> io::Result { + pub fn server_info() -> io::Result<(u32, semver::Version)> { let mut cmd = Command::new("bazel"); cmd.arg("info"); cmd.arg("server_pid"); + cmd.arg("release"); cmd.stdout(Stdio::piped()); cmd.stderr(Stdio::null()); cmd.stdin(Stdio::null()); let c = cmd.spawn()?.wait_with_output()?; if !c.status.success() { - return Err(io::Error::other(anyhow!("failed to determine Bazel pid"))); + return Err(io::Error::other(anyhow!( + "failed to determine Bazel server info" + ))); } - let bytes: [u8; 4] = c.stdout[0..4].try_into().unwrap(); - Ok(u32::from_be_bytes(bytes)) + + // When bazel info is called with multiple keys it emits "key: value" lines. + let stdout = String::from_utf8_lossy(&c.stdout); + let mut pid: Option = None; + let mut version: Option = None; + for line in stdout.lines() { + if let Some((key, value)) = line.split_once(": ") { + match key.trim() { + "server_pid" => { + pid = value.trim().parse::().ok(); + } + "release" => { + // Value is like "release 9.0.0" or "release 9.0.0-rc1" + let ver_str = value.trim().trim_start_matches("release ").trim(); + // Strip pre-release suffix: "9.0.0-rc1" -> "9.0.0" + let ver_str = ver_str.split('-').next().unwrap_or(ver_str); + version = semver::Version::parse(ver_str) + .map_err(|e| { + io::Error::other(anyhow!( + "failed to parse Bazel version '{}': {}", + ver_str, + e + )) + }) + .ok(); + } + _ => {} + } + } + } + + let pid = + pid.ok_or_else(|| io::Error::other(anyhow!("bazel info did not return server_pid")))?; + let version = version.ok_or_else(|| { + io::Error::other(anyhow!( + "bazel info did not return a parseable release version" + )) + })?; + + Ok((pid, version)) } // TODO: this should return a thiserror::Error @@ -172,7 +213,7 @@ impl Build { current_dir: Option, rt: AsyncRuntime, ) -> Result { - let pid = Self::pid()?; + let (pid, _) = Self::server_info()?; let span = tracing::info_span!( "ctx.bazel.build", diff --git a/crates/axl-runtime/src/engine/bazel/mod.rs b/crates/axl-runtime/src/engine/bazel/mod.rs index 06acdcd1f..37c672940 100644 --- a/crates/axl-runtime/src/engine/bazel/mod.rs +++ b/crates/axl-runtime/src/engine/bazel/mod.rs @@ -26,6 +26,36 @@ use starlark::{ use crate::engine::store::AxlStore; use axl_proto; +/// Resolve a mixed list of plain flags and conditional `(flag, constraint)` tuples into +/// a `Vec`. Returns only the flags whose semver constraint matches `version`. +/// When `version` is `None` all items must be plain strings (i.e. `Either::Left`). +fn resolve_flags<'v>( + items: &[Either, (values::StringValue<'v>, values::StringValue<'v>)>], + version: Option<&semver::Version>, +) -> anyhow::Result> { + let mut result = Vec::with_capacity(items.len()); + for item in items { + match item { + Either::Left(s) => result.push(s.as_str().to_string()), + Either::Right((flag, constraint)) => { + let version = + version.expect("server_info must be called when conditional flags are present"); + let req = semver::VersionReq::parse(constraint.as_str()).map_err(|e| { + anyhow::anyhow!( + "invalid version constraint '{}': {}", + constraint.as_str(), + e + ) + })?; + if req.matches(version) { + result.push(flag.as_str().to_string()); + } + } + } + } + Ok(result) +} + mod build; mod helpers; mod iter; @@ -50,29 +80,47 @@ impl<'v> values::StarlarkValue<'v> for Bazel { #[starlark_module] pub(crate) fn bazel_methods(registry: &mut MethodsBuilder) { - /// Build targets Bazel within AXL with ctx.bazel.build(). - /// The result is a `Build` object, which has `artifacts()` (TODO), - /// `failures()` (TODO), and a `events()` functions that provide - /// iterators to the artifacts, failures, events respectively. + /// Build one or more Bazel targets. /// - /// Running `ctx.bazel.build()` does not block the Starlark thread. Explicitly - /// call `.wait()` on the `Build` object to wait until the invocation finishes. + /// Returns a `Build` object. The call does not block — use `.wait()` to + /// wait for the invocation to finish and retrieve its exit status. /// - /// You can pass in a single target or target pattern to build. + /// # Arguments + /// + /// * `targets` - One or more Bazel target patterns to build. + /// * `flags` - Bazel flags. Each element is either a plain `str` that is + /// always included, or a `(flag, constraint)` tuple that is included only + /// when the running Bazel version satisfies the + /// [semver](https://semver.org) constraint. Pre-release versions are + /// normalised before matching, so `8.0.0-rc1` is matched by `>=8`. An + /// invalid constraint is a hard error. + /// * `startup_flags` - Bazel startup flags. Accepts the same plain string + /// and `(flag, constraint)` tuple format as `flags`. + /// * `build_events` - Enable the Build Event Protocol stream. Pass `True` + /// or a list of `BuildEventSink` values to forward events to remote sinks. + /// * `workspace_events` - Enable the workspace events stream. + /// * `execution_logs` - Enable the execution logs stream. + /// * `inherit_stdout` - Inherit stdout from the parent process. + /// * `inherit_stderr` - Inherit stderr from the parent process. Defaults to `True`. + /// * `current_dir` - Working directory for the Bazel invocation. /// /// **Examples** /// /// ```python - /// def _fancy_build_impl(ctx): - /// io = ctx.std.io - /// build = ctx.bazel.build( - /// "//target/to:build" - /// build_events = True, - /// ) - /// for event in build.build_events(): - /// if event.type == "progress": - /// io.stdout.write(event.payload.stdout) - /// io.stderr.write(event.payload.stderr) + /// build = ctx.bazel.build("//my/pkg:target", flags = ["--config=release"]) + /// status = build.wait() + /// ``` + /// + /// ```python + /// build = ctx.bazel.build( + /// "//my/pkg:target", + /// flags = [ + /// "--config=release", + /// ("--notmp_sandbox", ">=8"), + /// ("--some_legacy_flag", "<7"), + /// ], + /// ) + /// status = build.wait() /// ``` fn build<'v>( #[allow(unused)] this: values::Value<'v>, @@ -84,10 +132,10 @@ pub(crate) fn bazel_methods(registry: &mut MethodsBuilder) { #[starlark(require = named, default = false)] workspace_events: bool, #[starlark(require = named, default = false)] execution_logs: bool, #[starlark(require = named, default = UnpackList::default())] flags: UnpackList< - values::StringValue, + Either, (values::StringValue<'v>, values::StringValue<'v>)>, >, #[starlark(require = named, default = UnpackList::default())] startup_flags: UnpackList< - values::StringValue, + Either, (values::StringValue<'v>, values::StringValue<'v>)>, >, #[starlark(require = named, default = false)] inherit_stdout: bool, #[starlark(require = named, default = true)] inherit_stderr: bool, @@ -98,6 +146,17 @@ pub(crate) fn bazel_methods(registry: &mut MethodsBuilder) { Either::Left(events) => (events, vec![]), Either::Right(sinks) => (true, sinks.items), }; + let has_conditional = flags.items.iter().any(|f| f.is_right()) + || startup_flags.items.iter().any(|f| f.is_right()); + let bazel_version = if has_conditional { + let (_, version) = build::Build::server_info() + .map_err(|e| anyhow::anyhow!("failed to get Bazel server info: {}", e))?; + Some(version) + } else { + None + }; + let resolved_flags = resolve_flags(&flags.items, bazel_version.as_ref())?; + let resolved_startup_flags = resolve_flags(&startup_flags.items, bazel_version.as_ref())?; let store = AxlStore::from_eval(eval)?; let build = build::Build::spawn( "build", @@ -105,12 +164,8 @@ pub(crate) fn bazel_methods(registry: &mut MethodsBuilder) { build_events, execution_logs, workspace_events, - flags.items.iter().map(|f| f.as_str().to_string()).collect(), - startup_flags - .items - .iter() - .map(|f| f.as_str().to_string()) - .collect(), + resolved_flags, + resolved_startup_flags, inherit_stdout, inherit_stderr, current_dir.into_option(), @@ -119,29 +174,47 @@ pub(crate) fn bazel_methods(registry: &mut MethodsBuilder) { Ok(build) } - /// Build & test Bazel targets within AXL with ctx.bazel.test(). - /// The result is a `Build` object, which has `artifacts()` (TODO), - /// `failures()` (TODO), and a `events()` functions that provide - /// iterators to the artifacts, failures, events respectively. + /// Build and test one or more Bazel targets. /// - /// Running `ctx.bazel.test()` does not block the Starlark thread. Explicitly - /// call `.wait()` on the `Build` object to wait until the invocation finishes. + /// Returns a `Build` object. The call does not block — use `.wait()` to + /// wait for the invocation to finish and retrieve its exit status. + /// + /// # Arguments /// - /// You can pass in a single target or target pattern to test. + /// * `targets` - One or more Bazel target patterns to test. + /// * `flags` - Bazel flags. Each element is either a plain `str` that is + /// always included, or a `(flag, constraint)` tuple that is included only + /// when the running Bazel version satisfies the + /// [semver](https://semver.org) constraint. Pre-release versions are + /// normalised before matching, so `8.0.0-rc1` is matched by `>=8`. An + /// invalid constraint is a hard error. + /// * `startup_flags` - Bazel startup flags. Accepts the same plain string + /// and `(flag, constraint)` tuple format as `flags`. + /// * `build_events` - Enable the Build Event Protocol stream. Pass `True` + /// or a list of `BuildEventSink` values to forward events to remote sinks. + /// * `workspace_events` - Enable the workspace events stream. + /// * `execution_logs` - Enable the execution logs stream. + /// * `inherit_stdout` - Inherit stdout from the parent process. + /// * `inherit_stderr` - Inherit stderr from the parent process. Defaults to `True`. + /// * `current_dir` - Working directory for the Bazel invocation. /// /// **Examples** /// /// ```python - /// def _fancy_test_impl(ctx): - /// io = ctx.std.io - /// test = ctx.bazel.test( - /// "//target/to:test" - /// build_events = True, - /// ) - /// for event in test.build_events(): - /// if event.type == "progress": - /// io.stdout.write(event.payload.stdout) - /// io.stderr.write(event.payload.stderr) + /// test = ctx.bazel.test("//my/pkg:test", flags = ["--test_output=errors"]) + /// status = test.wait() + /// ``` + /// + /// ```python + /// test = ctx.bazel.test( + /// "//my/pkg:test", + /// flags = [ + /// "--test_output=errors", + /// ("--notmp_sandbox", ">=8"), + /// ("--some_legacy_flag", "<7"), + /// ], + /// ) + /// status = test.wait() /// ``` fn test<'v>( #[allow(unused)] this: values::Value<'v>, @@ -153,10 +226,10 @@ pub(crate) fn bazel_methods(registry: &mut MethodsBuilder) { #[starlark(require = named, default = false)] workspace_events: bool, #[starlark(require = named, default = false)] execution_logs: bool, #[starlark(require = named, default = UnpackList::default())] flags: UnpackList< - values::StringValue, + Either, (values::StringValue<'v>, values::StringValue<'v>)>, >, #[starlark(require = named, default = UnpackList::default())] startup_flags: UnpackList< - values::StringValue, + Either, (values::StringValue<'v>, values::StringValue<'v>)>, >, #[starlark(require = named, default = false)] inherit_stdout: bool, #[starlark(require = named, default = true)] inherit_stderr: bool, @@ -167,6 +240,17 @@ pub(crate) fn bazel_methods(registry: &mut MethodsBuilder) { Either::Left(events) => (events, vec![]), Either::Right(sinks) => (true, sinks.items), }; + let has_conditional = flags.items.iter().any(|f| f.is_right()) + || startup_flags.items.iter().any(|f| f.is_right()); + let bazel_version = if has_conditional { + let (_, version) = build::Build::server_info() + .map_err(|e| anyhow::anyhow!("failed to get Bazel server info: {}", e))?; + Some(version) + } else { + None + }; + let resolved_flags = resolve_flags(&flags.items, bazel_version.as_ref())?; + let resolved_startup_flags = resolve_flags(&startup_flags.items, bazel_version.as_ref())?; let store = AxlStore::from_eval(eval)?; let test = build::Build::spawn( "test", @@ -174,12 +258,8 @@ pub(crate) fn bazel_methods(registry: &mut MethodsBuilder) { build_events, execution_logs, workspace_events, - flags.items.iter().map(|f| f.as_str().to_string()).collect(), - startup_flags - .items - .iter() - .map(|f| f.as_str().to_string()) - .collect(), + resolved_flags, + resolved_startup_flags, inherit_stdout, inherit_stderr, current_dir.into_option(),