diff --git a/app/buck2_action_impl/src/dynamic.rs b/app/buck2_action_impl/src/dynamic.rs index a7b49fca49c46..126e80180947a 100644 --- a/app/buck2_action_impl/src/dynamic.rs +++ b/app/buck2_action_impl/src/dynamic.rs @@ -8,14 +8,15 @@ */ pub(crate) mod attrs; -pub(crate) mod attrs_starlark; +pub mod attrs_starlark; pub mod bxl; pub mod calculation; pub mod deferred; pub(crate) mod dynamic_actions; -pub(crate) mod dynamic_actions_callable; +pub mod dynamic_actions_callable; pub(crate) mod dynamic_actions_globals; pub(crate) mod dynamic_value; pub mod params; pub(crate) mod resolved_dynamic_value; pub(crate) mod storage; +pub use dynamic_actions_globals::new_dynamic_actions_callable; diff --git a/app/buck2_action_impl/src/dynamic/attrs_starlark.rs b/app/buck2_action_impl/src/dynamic/attrs_starlark.rs index 0ee5cdd53ded2..4934cea0a3089 100644 --- a/app/buck2_action_impl/src/dynamic/attrs_starlark.rs +++ b/app/buck2_action_impl/src/dynamic/attrs_starlark.rs @@ -35,7 +35,7 @@ use crate::dynamic::attrs::DynamicAttrType; NoSerialize )] #[display("{}", ty)] -pub(crate) struct StarlarkDynamicAttrType { +pub struct StarlarkDynamicAttrType { pub(crate) ty: DynamicAttrType, } diff --git a/app/buck2_action_impl/src/dynamic/deferred.rs b/app/buck2_action_impl/src/dynamic/deferred.rs index 971d0ab008f31..9bd4c2b7d4caa 100644 --- a/app/buck2_action_impl/src/dynamic/deferred.rs +++ b/app/buck2_action_impl/src/dynamic/deferred.rs @@ -86,6 +86,11 @@ pub enum DynamicLambdaArgs<'v> { actions: ValueTyped<'v, AnalysisActions<'v>>, attr_values: Box<[(String, Value<'v>)]>, }, + DynamicActionsBxlNamed { + // cannot import BxlContext because it bxl is depends on this crate + bxl_ctx: Value<'v>, + attr_values: Box<[(String, Value<'v>)]>, + }, } pub fn invoke_dynamic_output_lambda<'v>( @@ -113,6 +118,15 @@ pub fn invoke_dynamic_output_lambda<'v>( .collect::>(); (&[], &named) } + DynamicLambdaArgs::DynamicActionsBxlNamed { + bxl_ctx, + attr_values, + } => { + named = iter::once(("bxl_ctx", bxl_ctx.dupe())) + .chain(attr_values.iter().map(|(k, v)| (k.as_str(), *v))) + .collect::>(); + (&[], &named) + } }; let return_value = eval .eval_function(lambda, pos, named) @@ -134,6 +148,9 @@ pub fn invoke_dynamic_output_lambda<'v>( DynamicLambdaArgs::DynamicActionsNamed { .. } => { ProviderCollection::try_from_value_dynamic_output(return_value)? } + DynamicLambdaArgs::DynamicActionsBxlNamed { .. } => { + ProviderCollection::try_from_value_dynamic_output(return_value)? + } }; Ok(provider_collection) diff --git a/app/buck2_action_impl/src/dynamic/dynamic_actions_callable.rs b/app/buck2_action_impl/src/dynamic/dynamic_actions_callable.rs index ae9a2907efb3e..4b0267c1927c3 100644 --- a/app/buck2_action_impl/src/dynamic/dynamic_actions_callable.rs +++ b/app/buck2_action_impl/src/dynamic/dynamic_actions_callable.rs @@ -50,11 +50,11 @@ use crate::dynamic::attrs::DynamicAttrValues; use crate::dynamic::dynamic_actions::StarlarkDynamicActions; use crate::dynamic::dynamic_actions::StarlarkDynamicActionsData; -pub(crate) struct DynamicActionsCallbackParamSpec; +pub struct DynamicActionsCallbackParamSpec; -pub(crate) struct DynamicActionsCallbackParam { - pub(crate) name: &'static str, - pub(crate) ty: LazyLock, +pub struct DynamicActionsCallbackParam { + pub name: &'static str, + pub ty: LazyLock, } pub(crate) static P_ACTIONS: DynamicActionsCallbackParam = DynamicActionsCallbackParam { @@ -79,7 +79,7 @@ impl StarlarkCallableParamSpec for DynamicActionsCallbackParamSpec { } } -pub(crate) type DynamicActionsCallbackReturnType = ListType; +pub type DynamicActionsCallbackReturnType = ListType; #[derive(Debug, thiserror::Error)] enum DynamicActionCallableError { @@ -102,7 +102,7 @@ enum DynamicActionCallableError { "DynamicActionCallable[{}]", self.name.get().map(|s| s.as_str()).unwrap_or("(unbound)") )] -pub(crate) struct DynamicActionsCallable<'v> { +pub struct DynamicActionsCallable<'v> { pub(crate) self_ty: Ty, pub(crate) implementation: StarlarkCallable<'v, DynamicActionsCallbackParamSpec, DynamicActionsCallbackReturnType>, diff --git a/app/buck2_action_impl/src/dynamic/dynamic_actions_globals.rs b/app/buck2_action_impl/src/dynamic/dynamic_actions_globals.rs index 69a6e69945034..c1346fc02db76 100644 --- a/app/buck2_action_impl/src/dynamic/dynamic_actions_globals.rs +++ b/app/buck2_action_impl/src/dynamic/dynamic_actions_globals.rs @@ -29,11 +29,68 @@ use crate::dynamic::attrs::DynamicAttrType; use crate::dynamic::attrs_starlark::StarlarkDynamicAttrType; use crate::dynamic::dynamic_actions::StarlarkDynamicActions; use crate::dynamic::dynamic_actions_callable::DynamicActionsCallable; +use crate::dynamic::dynamic_actions_callable::DynamicActionsCallbackParam; use crate::dynamic::dynamic_actions_callable::DynamicActionsCallbackParamSpec; use crate::dynamic::dynamic_actions_callable::DynamicActionsCallbackReturnType; use crate::dynamic::dynamic_actions_callable::FrozenStarlarkDynamicActionsCallable; use crate::dynamic::dynamic_actions_callable::P_ACTIONS; +pub fn new_dynamic_actions_callable<'v>( + r#impl: StarlarkCallableChecked< + 'v, + DynamicActionsCallbackParamSpec, + DynamicActionsCallbackReturnType, + >, + attrs: SmallMap, + callback_param: &DynamicActionsCallbackParam, +) -> anyhow::Result> { + if attrs.contains_key(callback_param.name) { + return Err(buck2_error_anyhow!([], "Cannot define `actions` attribute")); + } + let attrs: SmallMap = attrs + .into_iter() + .map(|(name, ty)| (name, ty.ty.clone())) + .collect(); + + let attr_args = attrs + .iter() + .map(|(name, ty)| (name.as_str(), ty.impl_param_ty())) + .collect::>(); + + r#impl + .0 + .check_callable_with( + [], + iter::once((callback_param.name, &*callback_param.ty)) + .chain(attr_args.iter().map(|(name, ty)| (*name, ty))), + None, + None, + &DynamicActionsCallbackReturnType::starlark_type_repr(), + ) + .into_anyhow_result() + .context("`impl` function must be callable with given params")?; + + let callable_ty = Ty::function( + ParamSpec::new_named_only(attrs.iter().map(|(name, ty)| { + ( + ArcStr::from(name.as_str()), + ParamIsRequired::Yes, + ty.callable_param_ty(), + ) + })) + .into_anyhow_result() + .internal_error("Signature must be correct")?, + StarlarkDynamicActions::starlark_type_repr(), + ); + + Ok(DynamicActionsCallable { + self_ty: callable_ty, + implementation: r#impl.to_unchecked(), + name: OnceCell::new(), + attrs, + }) +} + #[starlark_module] pub(crate) fn register_dynamic_actions(globals: &mut GlobalsBuilder) { /// Create new dynamic action callable. Returned object will be callable, @@ -46,51 +103,7 @@ pub(crate) fn register_dynamic_actions(globals: &mut GlobalsBuilder) { >, #[starlark(require = named)] attrs: SmallMap, ) -> anyhow::Result> { - if attrs.contains_key(P_ACTIONS.name) { - return Err(buck2_error_anyhow!([], "Cannot define `actions` attribute")); - } - let attrs: SmallMap = attrs - .into_iter() - .map(|(name, ty)| (name, ty.ty.clone())) - .collect(); - - let attr_args = attrs - .iter() - .map(|(name, ty)| (name.as_str(), ty.impl_param_ty())) - .collect::>(); - - r#impl - .0 - .check_callable_with( - [], - iter::once((P_ACTIONS.name, &*P_ACTIONS.ty)) - .chain(attr_args.iter().map(|(name, ty)| (*name, ty))), - None, - None, - &DynamicActionsCallbackReturnType::starlark_type_repr(), - ) - .into_anyhow_result() - .context("`impl` function must be callable with given params")?; - - let callable_ty = Ty::function( - ParamSpec::new_named_only(attrs.iter().map(|(name, ty)| { - ( - ArcStr::from(name.as_str()), - ParamIsRequired::Yes, - ty.callable_param_ty(), - ) - })) - .into_anyhow_result() - .internal_error("Signature must be correct")?, - StarlarkDynamicActions::starlark_type_repr(), - ); - - Ok(DynamicActionsCallable { - self_ty: callable_ty, - implementation: r#impl.to_unchecked(), - name: OnceCell::new(), - attrs, - }) + new_dynamic_actions_callable(r#impl, attrs, &P_ACTIONS) } const DynamicActions: StarlarkValueAsType = StarlarkValueAsType::new(); diff --git a/app/buck2_bxl/src/bxl/starlark_defs/context/dynamic.rs b/app/buck2_bxl/src/bxl/starlark_defs/context/dynamic.rs index b8d51834afdf1..fd1d40088987e 100644 --- a/app/buck2_bxl/src/bxl/starlark_defs/context/dynamic.rs +++ b/app/buck2_bxl/src/bxl/starlark_defs/context/dynamic.rs @@ -10,13 +10,20 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; +use std::sync::LazyLock; +use buck2_action_impl::dynamic::attrs_starlark::StarlarkDynamicAttrType; use buck2_action_impl::dynamic::bxl::EVAL_BXL_FOR_DYNAMIC_OUTPUT; use buck2_action_impl::dynamic::deferred::dynamic_lambda_ctx_data; use buck2_action_impl::dynamic::deferred::invoke_dynamic_output_lambda; use buck2_action_impl::dynamic::deferred::DynamicLambdaArgs; use buck2_action_impl::dynamic::deferred::DynamicLambdaCtxDataSpec; use buck2_action_impl::dynamic::deferred::InputArtifactsMaterialized; +use buck2_action_impl::dynamic::dynamic_actions_callable::DynamicActionsCallable; +use buck2_action_impl::dynamic::dynamic_actions_callable::DynamicActionsCallbackParam; +use buck2_action_impl::dynamic::dynamic_actions_callable::DynamicActionsCallbackParamSpec; +use buck2_action_impl::dynamic::dynamic_actions_callable::DynamicActionsCallbackReturnType; +use buck2_action_impl::dynamic::new_dynamic_actions_callable; use buck2_action_impl::dynamic::params::FrozenDynamicLambdaParams; use buck2_artifact::dynamic::DynamicLambdaResultsKey; use buck2_build_api::actions::artifact::get_artifact_fs::GetArtifactFs; @@ -39,7 +46,12 @@ use buck2_interpreter::starlark_profiler::profiler::StarlarkProfilerOpt; use dice::DiceComputations; use dupe::Dupe; use itertools::Itertools; +use starlark::collections::SmallMap; +use starlark::environment::GlobalsBuilder; use starlark::environment::Module; +use starlark::starlark_module; +use starlark::values::type_repr::StarlarkTypeRepr; +use starlark::values::typing::StarlarkCallableChecked; use starlark::values::OwnedRefFrozenRef; use starlark::values::ValueTyped; @@ -206,10 +218,11 @@ impl BxlDynamicOutputEvaluator<'_> { artifact_values: *artifact_values, outputs: *outputs, }, - (Some(_arg), DynamicLambdaCtxDataSpec::New { .. }) => { - return Err(anyhow::anyhow!( - "New `dynamic_actions` API is not implemented for BXL" - )); + (Some(_arg), DynamicLambdaCtxDataSpec::New { attr_values }) => { + DynamicLambdaArgs::DynamicActionsBxlNamed { + bxl_ctx: ctx.to_value(), + attr_values: attr_values.clone(), + } } (None, DynamicLambdaCtxDataSpec::New { .. }) | (Some(_), DynamicLambdaCtxDataSpec::Old { .. }) => { @@ -252,3 +265,24 @@ pub(crate) fn init_eval_bxl_for_dynamic_output() { }, ); } + +static P_BXLCTX: DynamicActionsCallbackParam = DynamicActionsCallbackParam { + name: "bxl_ctx", + ty: LazyLock::new(BxlContext::starlark_type_repr), +}; + +#[starlark_module] +pub(crate) fn register_dynamic_actions(globals: &mut GlobalsBuilder) { + /// Create new bxl dynamic action callable. Returned object will be callable, + /// and the result of calling it can be passed to `ctx.actions.dynamic_output_new`. + fn dynamic_actions<'v>( + #[starlark(require = named)] r#impl: StarlarkCallableChecked< + 'v, + DynamicActionsCallbackParamSpec, + DynamicActionsCallbackReturnType, + >, + #[starlark(require = named)] attrs: SmallMap, + ) -> anyhow::Result> { + new_dynamic_actions_callable(r#impl, attrs, &P_BXLCTX) + } +} diff --git a/app/buck2_bxl/src/bxl/starlark_defs/globals.rs b/app/buck2_bxl/src/bxl/starlark_defs/globals.rs index d2c1c5e6c39c4..fe01afa45d01b 100644 --- a/app/buck2_bxl/src/bxl/starlark_defs/globals.rs +++ b/app/buck2_bxl/src/bxl/starlark_defs/globals.rs @@ -15,6 +15,7 @@ use starlark::environment::GlobalsBuilder; use crate::bxl::starlark_defs::bxl_function::register_bxl_main_function; use crate::bxl::starlark_defs::bxl_function::register_bxl_prefixed_main_function; use crate::bxl::starlark_defs::cli_args; +use crate::bxl::starlark_defs::context::dynamic::register_dynamic_actions; use crate::bxl::starlark_defs::functions::register_artifact_function; use crate::bxl::starlark_defs::functions::register_error_handling_function; use crate::bxl::starlark_defs::functions::register_file_set_function; @@ -32,6 +33,7 @@ fn bxl_namespace(g: &mut GlobalsBuilder) { register_instant_function(g); register_error_handling_function(g); register_bxl_type_names_in_bxl_namespace(g); + register_dynamic_actions(g); } pub(crate) fn init_bxl_specific_globals() { diff --git a/tests/core/bxl/BUCK b/tests/core/bxl/BUCK index 35e2fb3aff5a0..3e9f03599cf22 100644 --- a/tests/core/bxl/BUCK +++ b/tests/core/bxl/BUCK @@ -21,6 +21,15 @@ buck2_e2e_test( ], ) +buck2_e2e_test( + name = "test_dynamic_new", + srcs = ["test_dynamic_new.py"], + data_dir = "test_dynamic_new_data", + deps = [ + "//buck2/tests/e2e_util:utils", + ], +) + buck2_e2e_test( name = "test_typecheck", srcs = ["test_typecheck.py"], diff --git a/tests/core/bxl/test_dynamic_new.py b/tests/core/bxl/test_dynamic_new.py new file mode 100644 index 0000000000000..ea6f49b83cc72 --- /dev/null +++ b/tests/core/bxl/test_dynamic_new.py @@ -0,0 +1,23 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under both the MIT license found in the +# LICENSE-MIT file in the root directory of this source tree and the Apache +# License, Version 2.0 found in the LICENSE-APACHE file in the root directory +# of this source tree. + +# pyre-strict + +from pathlib import Path + +from buck2.tests.e2e_util.api.buck import Buck +from buck2.tests.e2e_util.buck_workspace import buck_test + + +@buck_test() +async def test_bxl_dynamic_action_basic(buck: Buck) -> None: + + result = await buck.bxl( + "//:dynamic.bxl:basic", + ) + outputs = result.stdout.strip() + assert Path(outputs).read_text() == "foobar" diff --git a/tests/core/bxl/test_dynamic_new_data/.buckconfig b/tests/core/bxl/test_dynamic_new_data/.buckconfig new file mode 100644 index 0000000000000..e36725d7cc6ff --- /dev/null +++ b/tests/core/bxl/test_dynamic_new_data/.buckconfig @@ -0,0 +1,10 @@ +[buildfile] +name=TARGETS.fixture + +[repositories] +root = . +prelude = prelude +config = config + +[build] + execution_platforms = root//platforms:platforms diff --git a/tests/core/bxl/test_dynamic_new_data/.buckroot b/tests/core/bxl/test_dynamic_new_data/.buckroot new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tests/core/bxl/test_dynamic_new_data/dynamic.bxl b/tests/core/bxl/test_dynamic_new_data/dynamic.bxl new file mode 100644 index 0000000000000..e4e94a4f27b85 --- /dev/null +++ b/tests/core/bxl/test_dynamic_new_data/dynamic.bxl @@ -0,0 +1,40 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under both the MIT license found in the +# LICENSE-MIT file in the root directory of this source tree and the Apache +# License, Version 2.0 found in the LICENSE-APACHE file in the root directory +# of this source tree. + +def _basic_f_impl(bxl_ctx: bxl.Context, src: ArtifactValue, out: OutputArtifact): + src = src.read_string() + if src != "foo": + fail("Expect input 'foo' but got '{}'".format(src)) + bxl_actions = bxl_ctx.bxl_actions().actions + bxl_actions.write(out, src + "bar") + return [] + +_basic_f = bxl.dynamic_actions( + impl = _basic_f_impl, + attrs = { + "out": dynattrs.output(), + "src": dynattrs.artifact_value(), + }, +) + +def _basic(ctx): + bxl_actions = ctx.bxl_actions().actions + input = bxl_actions.write("input", "foo") + output = bxl_actions.declare_output("output") + + bxl_actions.dynamic_output_new(_basic_f( + src = input, + out = output.as_output(), + )) + res = ctx.output.ensure(output) + + ctx.output.print(res.abs_path()) + +basic = bxl_main( + impl = _basic, + cli_args = {}, +) diff --git a/tests/core/bxl/test_dynamic_new_data/platforms/TARGETS.fixture b/tests/core/bxl/test_dynamic_new_data/platforms/TARGETS.fixture new file mode 100644 index 0000000000000..e1839ac21017c --- /dev/null +++ b/tests/core/bxl/test_dynamic_new_data/platforms/TARGETS.fixture @@ -0,0 +1,15 @@ +load(":defs.bzl", "execution_platforms", "target_platform") + +execution_platforms( + name = "platforms", +) + +target_platform( + name = "platform1", + visibility = ["PUBLIC"], +) + +target_platform( + name = "platform2", + visibility = ["PUBLIC"], +) diff --git a/tests/core/bxl/test_dynamic_new_data/platforms/defs.bzl b/tests/core/bxl/test_dynamic_new_data/platforms/defs.bzl new file mode 100644 index 0000000000000..ffce952274d1d --- /dev/null +++ b/tests/core/bxl/test_dynamic_new_data/platforms/defs.bzl @@ -0,0 +1,46 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under both the MIT license found in the +# LICENSE-MIT file in the root directory of this source tree and the Apache +# License, Version 2.0 found in the LICENSE-APACHE file in the root directory +# of this source tree. + +def _execution_platform(ctx): + platform = ExecutionPlatformInfo( + label = ctx.label.raw_target(), + configuration = ConfigurationInfo( + constraints = { + }, + values = {}, + ), + executor_config = CommandExecutorConfig( + local_enabled = True, + remote_enabled = True, + remote_cache_enabled = True, + remote_execution_properties = { + "platform": "linux-remote-execution", + }, + remote_execution_use_case = "buck2-testing", + ), + ) + + return [ + DefaultInfo(), + ExecutionPlatformRegistrationInfo(platforms = [platform]), + ] + +execution_platforms = rule(attrs = {}, impl = _execution_platform) + +def _target_platform(ctx): + return [ + DefaultInfo(), + PlatformInfo( + label = str(ctx.label.raw_target()), + configuration = ConfigurationInfo(constraints = {}, values = {}), + ), + ] + +target_platform = rule( + impl = _target_platform, + attrs = {}, +) diff --git a/tests/core/bxl/test_dynamic_new_data/prelude.bzl b/tests/core/bxl/test_dynamic_new_data/prelude.bzl new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tests/core/bxl/test_dynamic_new_data/prelude/prelude.bzl b/tests/core/bxl/test_dynamic_new_data/prelude/prelude.bzl new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tests/core/docs/test_builtin_docs_data/buck2-golden-docs/bxl/index.md b/tests/core/docs/test_builtin_docs_data/buck2-golden-docs/bxl/index.md index 1243489c44510..de7328b8f6c4a 100644 --- a/tests/core/docs/test_builtin_docs_data/buck2-golden-docs/bxl/index.md +++ b/tests/core/docs/test_builtin_docs_data/buck2-golden-docs/bxl/index.md @@ -2,6 +2,7 @@ # Bxl APIs ## ctarget\_set +## dynamic\_actions ## fail\_no\_stacktrace ## file\_set ## get\_path\_without\_materialization