Skip to content

Commit b9dfd04

Browse files
committed
bxl aquery: allow passing in bxl.Action
This allows for fine grained action graph debugging by letting bxl ask about individual Actions' dependencies. I chose this API because it's the smallest API surface change I could think of to improve this. One thing which Claude pointed out is that you can't have heterogeneous lists of Actions and aquery nodes. This is sorta unfortunate. Allows users to implement "What deps does my action have recorded for it" from #1217 themselves. Note that I tried running the tests I could using buck bxl but I have not gone to https://metacareers.com to be able to run the python side. Example usage: Use `ctx.audit.output()` to obtain an Action object and then ask about its dependencies.
1 parent 6b33e07 commit b9dfd04

6 files changed

Lines changed: 91 additions & 1 deletion

File tree

app/buck2_build_api/src/query/bxl.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::future::Future;
1212
use std::pin::Pin;
1313

1414
use async_trait::async_trait;
15+
use buck2_artifact::actions::key::ActionKey;
1516
use buck2_core::cells::CellResolver;
1617
use buck2_core::cells::name::CellName;
1718
use buck2_core::configuration::compatibility::MaybeCompatible;
@@ -183,6 +184,11 @@ pub trait BxlAqueryFunctions: Send {
183184
dice: &mut DiceComputations<'_>,
184185
targets: &TargetSet<ActionQueryNode>,
185186
) -> buck2_error::Result<TargetSet<ActionQueryNode>>;
187+
async fn get_action_nodes(
188+
&self,
189+
dice: &mut DiceComputations<'_>,
190+
action_keys: Vec<ActionKey>,
191+
) -> buck2_error::Result<TargetSet<ActionQueryNode>>;
186192
}
187193

188194
pub static NEW_BXL_CQUERY_FUNCTIONS: LateBinding<

app/buck2_bxl/src/bxl/starlark_defs/aquery.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010

1111
use allocative::Allocative;
12+
use buck2_artifact::actions::key::ActionKey;
1213
use buck2_build_api::actions::query::ActionQueryNode;
1314
use buck2_build_api::query::bxl::BxlAqueryFunctions;
1415
use buck2_build_api::query::bxl::NEW_BXL_AQUERY_FUNCTIONS;
@@ -46,6 +47,7 @@ use starlark::values::type_repr::StarlarkTypeRepr;
4647

4748
use crate::bxl::starlark_defs::context::BxlContext;
4849
use crate::bxl::starlark_defs::context::ErrorPrinter;
50+
use crate::bxl::starlark_defs::nodes::action::StarlarkAction;
4951
use crate::bxl::starlark_defs::nodes::action::StarlarkActionQueryNode;
5052
use crate::bxl::starlark_defs::providers_expr::AnyProvidersExprArg;
5153
use crate::bxl::starlark_defs::providers_expr::ProvidersExpr;
@@ -122,6 +124,7 @@ enum UnpackActionNodes<'v> {
122124
ActionQueryNodesSet(&'v StarlarkTargetSet<ActionQueryNode>),
123125
ConfiguredProviders(AnyProvidersExprArg<'v>),
124126
ConfiguredTargets(ConfiguredTargetListExprArg<'v>),
127+
StarlarkActions(UnpackList<StarlarkAction>),
125128
}
126129

127130
// Aquery operates on `ActionQueryNode`s. Under the hood, the target set of action query nodes is obtained
@@ -140,6 +143,14 @@ async fn unpack_action_nodes<'v>(
140143
return Ok(action_nodes.into_iter().map(|v| v.0).collect());
141144
}
142145
UnpackActionNodes::ActionQueryNodesSet(action_nodes) => return Ok(action_nodes.0.clone()),
146+
UnpackActionNodes::StarlarkActions(actions) => {
147+
let action_keys: Vec<ActionKey> = actions
148+
.into_iter()
149+
.map(|starlark_action| starlark_action.0.key().dupe())
150+
.collect();
151+
152+
return aquery_env.get_action_nodes(dice, action_keys).await;
153+
}
143154
UnpackActionNodes::ConfiguredProviders(arg) => {
144155
ProvidersExpr::<ConfiguredProvidersLabel>::unpack(
145156
arg,
@@ -183,6 +194,11 @@ async fn unpack_action_nodes<'v>(
183194
///
184195
/// Query results are `target_set`s of `action_query_node`s, which supports iteration,
185196
/// indexing, `len()`, set addition/subtraction, and `equals()`.
197+
///
198+
/// Actions can be specified as:
199+
/// - Target expressions (configured targets/providers)
200+
/// - Existing action query nodes or target sets
201+
/// - `bxl.Action` objects (obtained from `ctx.audit().output()`)
186202
#[starlark_module]
187203
fn aquery_methods(builder: &mut MethodsBuilder) {
188204
/// The deps query for finding the transitive closure of dependencies.

app/buck2_query_impls/src/aquery/bxl.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::marker::PhantomData;
1212
use std::sync::Arc;
1313

1414
use async_trait::async_trait;
15+
use buck2_artifact::actions::key::ActionKey;
1516
use buck2_build_api::actions::query::ActionQueryNode;
1617
use buck2_build_api::analysis::calculation::RuleAnalysisCalculation;
1718
use buck2_build_api::query::bxl::BxlAqueryFunctions;
@@ -303,6 +304,24 @@ impl BxlAqueryFunctions for BxlAqueryFunctionsImpl {
303304
})
304305
.await
305306
}
307+
308+
async fn get_action_nodes(
309+
&self,
310+
dice: &mut DiceComputations<'_>,
311+
action_keys: Vec<ActionKey>,
312+
) -> buck2_error::Result<TargetSet<ActionQueryNode>> {
313+
dice.with_linear_recompute(|dice| async move {
314+
let delegate = self.aquery_delegate(&dice).await?;
315+
let mut result = TargetSet::new();
316+
let nodes = buck2_util::future::try_join_all(
317+
action_keys.iter().map(|key| delegate.get_action_node(&key)),
318+
)
319+
.await?;
320+
result.extend(nodes);
321+
Ok(result)
322+
})
323+
.await
324+
}
306325
}
307326

308327
pub(crate) fn init_new_bxl_aquery_functions() {

tests/core/query/aquery/test_aquery.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,16 @@ async def test_bxl_aquery_eval(buck: Buck) -> None:
154154
@buck_test()
155155
async def test_bxl_aquery_action_query_node(buck: Buck) -> None:
156156
await buck.bxl("//:aquery.bxl:action_query_node")
157+
158+
159+
# Tests for bxl.Action support in aquery operations
160+
@buck_test()
161+
async def test_bxl_action_deps_0(buck: Buck) -> None:
162+
"""Test passing a bxl.Action to aquery.deps() with depth=0 returns itself"""
163+
await buck.bxl("//:aquery.bxl:action_deps_0")
164+
165+
166+
@buck_test()
167+
async def test_bxl_action_deps(buck: Buck) -> None:
168+
"""Test that you can isolate the deps of one action by itself"""
169+
await buck.bxl("//:aquery.bxl:action_deps")

tests/core/query/aquery/test_aquery_data/aquery.bxl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,38 @@ action_query_node = bxl_main(
141141
impl = _impl_action_query_node,
142142
cli_args = {},
143143
)
144+
145+
def _impl_action_deps_0(ctx):
146+
"""Test passing a bxl.Action to aquery.deps() returns itself"""
147+
nodes = ctx.aquery().eval("//:test")
148+
149+
action = nodes[0].action()
150+
151+
# Pass the bxl.Action rather than a target to aquery.deps()
152+
result = ctx.aquery().deps([action], depth = 0)
153+
_assert_eq(action.outputs(), result[0].action().outputs())
154+
155+
action_deps_0 = bxl_main(
156+
impl = _impl_action_deps_0,
157+
cli_args = {},
158+
)
159+
160+
def _impl_action_deps(ctx):
161+
"""Test that you can isolate the deps of one action by itself"""
162+
nodes = ctx.aquery().eval("//:test")
163+
164+
action = nodes[0].action()
165+
166+
# Pass the bxl.Action rather than a target to aquery.deps()
167+
result = ctx.aquery().deps([action], depth = 1)
168+
169+
_assert_eq(len(result), 2)
170+
# First should be itself
171+
_assert_eq(action.outputs(), result[0].action().outputs())
172+
dep = result[1]
173+
_assert_eq(dep.action().outputs()[0].short_path, "dep")
174+
175+
action_deps = bxl_main(
176+
impl = _impl_action_deps,
177+
cli_args = {},
178+
)

tests/core/query/aquery/test_aquery_data/prelude.bzl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
def _test(ctx: AnalysisContext):
1010
dep = ctx.actions.write("dep", "")
1111
default = ctx.actions.copy_file("default", dep)
12+
other_default = ctx.actions.copy_file("other_default", dep)
1213
other = ctx.actions.write("other", "")
1314

1415
sub_default = ctx.actions.write("sub_default", "")
@@ -18,7 +19,7 @@ def _test(ctx: AnalysisContext):
1819
ctx.actions.write("unused", "")
1920

2021
return [DefaultInfo(
21-
default_outputs = [default],
22+
default_outputs = [default, other_default],
2223
other_outputs = [other],
2324
sub_targets = {
2425
"sub": [

0 commit comments

Comments
 (0)