Implement rule implementation execution. Note that I've had to move frozen rule tests to their own tests directory. This is because I've introduced a dependency chain: #[cfg(test)] rule -> testutil -> rule Tests in the tests directory compile against rule, while tests not in that directory compile against #[cfg(test)] rule, which is incompatible with testutil's rule. Bug: 528225104 Change-Id: Iee3614fc522580a1114b67d7948252276a6a6964 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/24300 Reviewed-by: Takuto Ikuta <tikuta@google.com> Commit-Queue: Matt Stark <msta@google.com>
diff --git a/src/gn/starlark/Cargo.lock b/src/gn/starlark/Cargo.lock index c8e078a..2a744ae 100644 --- a/src/gn/starlark/Cargo.lock +++ b/src/gn/starlark/Cargo.lock
@@ -1714,6 +1714,8 @@ dependencies = [ "allocative", "attr", + "providers", + "rule", "starlark", "starlark_derive", "types",
diff --git a/src/gn/starlark/crates/attr/src/ctx.rs b/src/gn/starlark/crates/attr/src/ctx.rs index f82650a..833d052 100644 --- a/src/gn/starlark/crates/attr/src/ctx.rs +++ b/src/gn/starlark/crates/attr/src/ctx.rs
@@ -22,6 +22,7 @@ /// Contains ctx.attr, ctx.files, and ctx.file. /// /// See https://bazel.build/rules/lib/builtins/ctx for more info on what they are. +#[derive(Debug, Clone, Copy, Allocative)] pub struct CtxAttr<'v> { pub attr: Value<'v>, pub files: Value<'v>,
diff --git a/src/gn/starlark/crates/attr/src/lib.rs b/src/gn/starlark/crates/attr/src/lib.rs index 1729bd7..65ce51b 100644 --- a/src/gn/starlark/crates/attr/src/lib.rs +++ b/src/gn/starlark/crates/attr/src/lib.rs
@@ -19,4 +19,4 @@ pub(crate) use errors::Error; pub use globals::{AttrModule, AttrSpecArgs}; pub use schema::{AllowFilesSchema, AttrKind, AttrSchema}; -pub use traits::{EvalContext, EvalContextAttrExt, Session, TargetRef}; +pub use traits::{EvalContext, EvalContextAttrExt, Session, TargetAttrExt, TargetRef};
diff --git a/src/gn/starlark/crates/attr/src/traits.rs b/src/gn/starlark/crates/attr/src/traits.rs index 7f67ad1..894a5ec 100644 --- a/src/gn/starlark/crates/attr/src/traits.rs +++ b/src/gn/starlark/crates/attr/src/traits.rs
@@ -5,6 +5,13 @@ /// Re-export the traits from types so caller crates can access them seamlessly. pub use types::{EvalContext, OutputType, Session, TargetRef}; +/// Represents a target with attributes that can be executed by custom Starlark +/// rules. +pub trait TargetAttrExt: TargetRef { + /// Returns the resolved custom attributes of the target. + fn attrs(&self) -> &[crate::Attr]; +} + /// Extension trait for EvalContext to support target creation. pub trait EvalContextAttrExt: types::EvalContext { fn create_target(
diff --git a/src/gn/starlark/crates/rule/src/ctx.rs b/src/gn/starlark/crates/rule/src/ctx.rs new file mode 100644 index 0000000..26ed24b --- /dev/null +++ b/src/gn/starlark/crates/rule/src/ctx.rs
@@ -0,0 +1,152 @@ +// Copyright 2026 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use std::cell::RefCell; + +use allocative::Allocative; +use attr::{traits::EvalContextAttrExt, CtxAttr, TargetAttrExt}; +use starlark::{ + any::ProvidesStaticType, + environment::Methods, + values::{AllocValue, Freeze, FreezeResult, Freezer, Heap, StarlarkValue, Value}, +}; +use starlark_derive::{starlark_value, NoSerialize}; +use types::{CtxMethods, Session}; + +use crate::FrozenRule; + +#[derive(Allocative, NoSerialize)] +pub struct Ctx<'v, C: EvalContextAttrExt> { + /// Contains ctx.attr/files/file + attrs: CtxAttr<'v>, + /// The rule currently being evaluated. + /// If you have a parent and child rule, this will start as [child], then + /// when you call ctx.super() it will be [child, parent]. + #[allocative(skip)] + rule_stack: RefCell<Vec<&'v FrozenRule<C>>>, +} + +impl<'v, C: EvalContextAttrExt> Ctx<'v, C> { + pub fn new(attrs: CtxAttr<'v>, rule: &'v FrozenRule<C>) -> Self { + Self { + attrs, + rule_stack: RefCell::new(vec![rule]), + } + } + + /// Runs ctx.super() + pub fn run_super( + &self, + this: Value<'v>, + eval: &mut starlark::eval::Evaluator<'v, '_, '_>, + ) -> starlark::Result<Value<'v>> { + let parent = { + let rule_stack = self.rule_stack.borrow(); + let current = rule_stack.last().expect("rule_stack is never empty"); + current.parent.ok_or(crate::errors::Error::NoParentRule)? + }; + self.rule_stack.borrow_mut().push(parent); + let res = eval.eval_function(parent.implementation.to_value(), &[this], &[]); + self.rule_stack.borrow_mut().pop(); + res + } +} + +impl<'v, C: EvalContextAttrExt> std::fmt::Debug for Ctx<'v, C> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ctx") + } +} + +impl<'v, C: EvalContextAttrExt> std::fmt::Display for Ctx<'v, C> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ctx") + } +} + +unsafe impl<'v, C: EvalContextAttrExt> starlark::values::Trace<'v> for Ctx<'v, C> { + fn trace(&mut self, tracer: &starlark::values::Tracer<'v>) { + self.attrs.attr.trace(tracer); + self.attrs.files.trace(tracer); + self.attrs.file.trace(tracer); + } +} + +unsafe impl<'v, EvalCtx: EvalContextAttrExt> ProvidesStaticType<'v> for Ctx<'v, EvalCtx> { + type StaticType = Ctx<'static, EvalCtx>; +} + +#[starlark_value(type = "ctx")] +impl<'v, C: EvalContextAttrExt + CtxMethods> StarlarkValue<'v> for Ctx<'v, C> +where + <C::Session as Session>::TargetRef: TargetAttrExt, +{ + type Canonical = Self; + + fn get_methods() -> Option<&'static Methods> { + Some(<C as CtxMethods>::methods()) + } + + fn get_attr(&self, attribute: &str, _heap: Heap<'v>) -> Option<Value<'v>> { + match attribute { + "attr" => Some(self.attrs.attr), + "files" => Some(self.attrs.files), + "file" => Some(self.attrs.file), + _ => None, + } + } + + fn dir_attr(&self) -> Vec<String> { + vec!["attr".to_owned(), "files".to_owned(), "file".to_owned()] + } +} + +impl<'v, C: EvalContextAttrExt + CtxMethods> AllocValue<'v> for Ctx<'v, C> +where + <C::Session as Session>::TargetRef: TargetAttrExt, +{ + fn alloc_value(self, heap: Heap<'v>) -> Value<'v> { + heap.alloc_complex(self) + } +} + +impl<'v, C: EvalContextAttrExt + CtxMethods> Freeze for Ctx<'v, C> +where + <C::Session as Session>::TargetRef: TargetAttrExt, +{ + type Frozen = starlark::values::none::NoneType; + + fn freeze(self, _packer: &Freezer) -> FreezeResult<Self::Frozen> { + Err(crate::errors::Error::ObjectUnfreezable("ctx").into()) + } +} + +#[macro_export] +macro_rules! impl_ctx_methods { + ($ctx_type:ty) => { + #[starlark_derive::starlark_module] + pub fn ctx_methods(builder: &mut starlark::environment::MethodsBuilder) { + #[starlark(name = "super")] + fn super_<'v>( + this: starlark::values::Value<'v>, + eval: &mut starlark::eval::Evaluator<'v, '_, '_>, + ) -> starlark::Result<starlark::values::Value<'v>> { + use starlark::values::ValueLike as _; + this.downcast_ref::<$crate::Ctx<'v, $ctx_type>>() + .unwrap() + .run_super(this, eval) + } + } + + impl $crate::CtxMethods for $ctx_type { + fn methods() -> &'static starlark::environment::Methods { + static RES: starlark::environment::MethodsStatic = + starlark::environment::MethodsStatic::new("Ctx", |builder| { + ctx_methods(builder); + }); + RES.methods() + } + } + }; +}
diff --git a/src/gn/starlark/crates/rule/src/errors.rs b/src/gn/starlark/crates/rule/src/errors.rs index 7fdd277..335c2f0 100644 --- a/src/gn/starlark/crates/rule/src/errors.rs +++ b/src/gn/starlark/crates/rule/src/errors.rs
@@ -4,13 +4,20 @@ /// Errors returned by the GN Starlark rule system. #[derive(thiserror::Error, Debug)] -pub(crate) enum Error { +pub enum Error { #[error("Rule must be assigned to a global variable to be used")] RuleMustBeNamed, #[error("Parent must be a rule")] ParentMustBeARule, #[error("Attribute '{0}' is reserved")] ReservedAttribute(String), + #[error("Rule does not have a parent rule")] + NoParentRule, + #[error( + "The '{0}' object is ephemeral and cannot be stored in providers or other long-lived \ + structures." + )] + ObjectUnfreezable(&'static str), } impl From<Error> for starlark::Error {
diff --git a/src/gn/starlark/crates/rule/src/frozen_rule.rs b/src/gn/starlark/crates/rule/src/frozen_rule.rs index 2b5aac0..cb9442f 100644 --- a/src/gn/starlark/crates/rule/src/frozen_rule.rs +++ b/src/gn/starlark/crates/rule/src/frozen_rule.rs
@@ -145,188 +145,3 @@ } } -#[cfg(test)] -mod tests { - use std::{ - collections::{HashMap, HashSet}, - sync::Mutex, - }; - - use attr::{Attr, LabelOrFile}; - use starlark::environment::FrozenModule; - use testutils::FakeTarget; - use types::{Label, OutputType, PackageRef, Session}; - - use crate::globals::tests::new_assert; - - #[test] - fn test_pure_rule_inheritance() { - let mut assert = new_assert(); - let native = assert.load_module("//rules:native.scl"); - let pure = assert.load_module("//rules:pure.scl"); - - let rule = |module: &FrozenModule, name: &str| { - module.get(name).unwrap().value().unpack_frozen().unwrap() - }; - - assert.pass( - r#" -load("//rules:pure.scl", "child_rule", "parent_rule") -load("//rules:native.scl", "custom_shared_library", "static_library") - -custom_shared_library( - name = "shared_library", - mandatory = "mandatory_val", - optional = "optional_val", - unknown = "unknown", -) - -static_library( - name = "static_library", - optional = "optional_val", - unknown = "unknown" -) - -parent_rule( - name = "parent_defaulted", - parent_only = "p", -) - -child_rule( - name = "child_defaulted", - parent_only = "p", - child_only = "c", -) - -child_rule( - name = "child_override", - parent_only = "parent_val", - child_only = "child_val", - override = "//:custom_val", -) -"#, - ); - - let heap = starlark::values::FrozenHeap::new(); - let mut unknown_attrs = HashMap::new(); - unknown_attrs.insert( - "unknown".to_owned(), - starlark::values::Value::new_frozen(heap.alloc("unknown")), - ); - - let context = assert.context(); - let load = |name: &str| { - let label = Label::new(PackageRef::root().to_owned(), name.to_owned()); - context - .session - .get_target(label.as_ref(), context.session.default_toolchain.as_ref()) - }; - - assert_eq!( - *load("shared_library"), - FakeTarget { - label: Label::new(PackageRef::root().to_owned(), "shared_library".to_owned()), - toolchain: context.session.default_toolchain.clone(), - outputs: vec![], - attrs: vec![ - Attr::String("optional_val".to_owned()), - Attr::String("mandatory_val".to_owned()), - ], - output_type: Some(OutputType::SharedLibrary), - rule: rule(&native, "custom_shared_library"), - cxx_attrs: unknown_attrs.clone(), - dependencies: Mutex::new(HashSet::new()), - } - ); - - assert_eq!( - *load("static_library"), - FakeTarget { - label: Label::new(PackageRef::root().to_owned(), "static_library".to_owned()), - toolchain: context.session.default_toolchain.clone(), - outputs: vec![], - attrs: vec![Attr::String("optional_val".to_owned())], - output_type: Some(OutputType::StaticLibrary), - rule: rule(&native, "static_library"), - cxx_attrs: unknown_attrs.clone(), - dependencies: Mutex::new(HashSet::new()), - } - ); - - let toolchain = Label::new( - PackageRef::root().to_owned(), - "default_toolchain".to_owned(), - ); - - assert_eq!( - *load("parent_defaulted"), - FakeTarget { - label: Label::new(PackageRef::root().to_owned(), "parent_defaulted".to_owned()), - toolchain: toolchain.clone(), - outputs: vec![], - attrs: vec![ - Attr::String("p".to_owned()), - Attr::Label(Some(LabelOrFile::Label(Label::new( - PackageRef::root().to_owned(), - "parent".to_owned() - )))), - ], - output_type: None, - rule: rule(&pure, "parent_rule"), - cxx_attrs: HashMap::new(), - dependencies: Mutex::new(HashSet::from([( - Label::new(PackageRef::root().to_owned(), "parent".to_owned()), - toolchain.clone(), - )])), - } - ); - - assert_eq!( - *load("child_defaulted"), - FakeTarget { - label: Label::new(PackageRef::root().to_owned(), "child_defaulted".to_owned()), - toolchain: toolchain.clone(), - outputs: vec![], - attrs: vec![ - Attr::String("p".to_owned()), - Attr::Label(Some(LabelOrFile::Label(Label::new( - PackageRef::root().to_owned(), - "child".to_owned() - )))), - Attr::String("c".to_owned()), - ], - output_type: None, - rule: rule(&pure, "child_rule"), - cxx_attrs: HashMap::new(), - dependencies: Mutex::new(HashSet::from([( - Label::new(PackageRef::root().to_owned(), "child".to_owned()), - toolchain.clone(), - )])), - } - ); - - assert_eq!( - *load("child_override"), - FakeTarget { - label: Label::new(PackageRef::root().to_owned(), "child_override".to_owned()), - toolchain: toolchain.clone(), - outputs: vec![], - attrs: vec![ - Attr::String("parent_val".to_owned()), - Attr::Label(Some(LabelOrFile::Label(Label::new( - PackageRef::root().to_owned(), - "custom_val".to_owned() - )))), - Attr::String("child_val".to_owned()), - ], - output_type: None, - rule: rule(&pure, "child_rule"), - cxx_attrs: HashMap::new(), - dependencies: Mutex::new(HashSet::from([( - Label::new(PackageRef::root().to_owned(), "custom_val".to_owned()), - toolchain.clone(), - )])), - } - ); - } -}
diff --git a/src/gn/starlark/crates/rule/src/globals.rs b/src/gn/starlark/crates/rule/src/globals.rs index 463e032..d676cfd 100644 --- a/src/gn/starlark/crates/rule/src/globals.rs +++ b/src/gn/starlark/crates/rule/src/globals.rs
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use attr::traits::EvalContextAttrExt; +use attr::{traits::EvalContextAttrExt, TargetAttrExt}; use starlark::{ environment::{FrozenModule, Module}, values::FrozenHeapName, @@ -20,7 +20,10 @@ /// sources = [...], /// ... /// ) -pub fn register_builtin_rules<C: EvalContextAttrExt>() -> FrozenModule { +pub fn register_builtin_rules<C: EvalContextAttrExt>() -> FrozenModule +where + <C::Session as types::Session>::TargetRef: TargetAttrExt, +{ Module::with_temp_heap(|module| { for output_type in OutputType::iter() { let name = output_type.to_string(); @@ -78,31 +81,9 @@ #[cfg(test)] pub(crate) mod tests { use starlark::values::list::UnpackList; - use testutils::FakeEvalContext; - - pub(crate) fn make_attr_schema<'v>( - kind: attr::AttrKind, - args: attr::AttrSpecArgs<'v>, - eval: &mut starlark::eval::Evaluator<'v, '_, '_>, - ) -> starlark::Result<starlark::values::Value<'v>> { - attr::AttrSchema::create( - kind, - args, - types::PackageRef::root(), - &types::PathResolver::new_for_testing(), - &eval.heap(), - ) - } pub(crate) fn new_assert() -> testutils::Assert { - let mut assert = testutils::Assert::default(); - assert.modify_globals(|builder| { - crate::register_rule_globals!(builder, FakeEvalContext); - builder.set("attr", attr::AttrModule { make_attr_schema }); - }); - let builtins = crate::register_builtin_rules::<FakeEvalContext>(); - assert.module_add(builtins); - assert + testutils::Assert::new_rule_assert() } #[test]
diff --git a/src/gn/starlark/crates/rule/src/implementation.rs b/src/gn/starlark/crates/rule/src/implementation.rs new file mode 100644 index 0000000..e45b35c --- /dev/null +++ b/src/gn/starlark/crates/rule/src/implementation.rs
@@ -0,0 +1,50 @@ +// Copyright 2026 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use attr::{traits::EvalContextAttrExt, TargetAttrExt, TargetRef}; +use starlark::{environment::Module, eval::Evaluator, values::OwnedFrozenValue}; +use types::{EvaluatorContextExt, Session}; + +use crate::{Ctx, FrozenRule}; + +/// Runs the rule implementation function for a given target. +/// This matches the execution phase where rule implementation is run +/// synchronously. +pub fn run<C: EvalContextAttrExt + crate::CtxMethods>( + target: &<C::Session as Session>::TargetRef, + create_context: impl FnOnce(&<C::Session as Session>::TargetRef) -> C, +) -> starlark::Result<OwnedFrozenValue> +where + <C::Session as Session>::TargetRef: TargetAttrExt<Rule = FrozenRule<C>>, +{ + // Safety: rule is always a rule for custom rule-built targets. + let rule = target.rule().unwrap(); + + Module::with_temp_heap(|module| { + let rule_context = create_context(target); + + // When the module is frozen, only things transitively required by extra_value + // are kept. + module.set_extra_value({ + let mut eval = Evaluator::new(&module); + let ctx = eval.heap().alloc(Ctx::<C>::new( + rule.schema.create_ctx_fields( + target.attrs(), + rule_context.session(), + &rule_context.current_toolchain(), + rule.builtin, + target.builtin_attrs(&eval.heap()), + &eval.heap(), + )?, + rule, + )); + + eval.set_context(&rule_context); + eval.eval_function(rule.implementation.to_value(), &[ctx], &[])? + }); + + let frozen = module.freeze()?; + Ok(frozen.owned_extra_value().unwrap()) + }) +}
diff --git a/src/gn/starlark/crates/rule/src/lib.rs b/src/gn/starlark/crates/rule/src/lib.rs index 52fb090..60080fe 100644 --- a/src/gn/starlark/crates/rule/src/lib.rs +++ b/src/gn/starlark/crates/rule/src/lib.rs
@@ -1,10 +1,15 @@ +pub mod ctx; pub mod errors; pub mod frozen_rule; pub mod globals; +pub mod implementation; pub mod rule; pub use attr::AttrSchema; -pub(crate) use errors::Error; +pub use ctx::Ctx; +pub use errors::Error; pub use frozen_rule::FrozenRule; pub use globals::register_builtin_rules; +pub use implementation::run; pub use rule::{OutputType, Rule}; +pub use types::CtxMethods;
diff --git a/src/gn/starlark/crates/rule/tests/ctx_super.rs b/src/gn/starlark/crates/rule/tests/ctx_super.rs new file mode 100644 index 0000000..f2f8f12 --- /dev/null +++ b/src/gn/starlark/crates/rule/tests/ctx_super.rs
@@ -0,0 +1,48 @@ +// Copyright 2026 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use rule::run; +use starlark::values::list::ListRef; +use testutils::Assert; +use types::{Label, PackageRef, Session}; + +#[test] +fn test_ctx_super() { + let mut assert = Assert::new_rule_assert(); + assert.load_module("//rules:pure.scl"); + assert.pass( + r#" +load("//rules:pure.scl", "child_rule", "parent_rule") + +parent_rule( + name = "child", + parent_only = "child_parent_val", +) + +child_rule( + name = "target", + parent_only = "parent_val", + child_only = "child_val", +) +"#, + ); + + let context = assert.context(); + let label = Label::new(PackageRef::root().to_owned(), "target".to_owned()); + let target = context + .session + .get_target(label.as_ref(), context.session.default_toolchain.as_ref()); + + let session = assert.session(); + let res = run(&target, move |t: &testutils::FakeTargetRef| { + testutils::FakeEvalContext::rule_impl(session.clone(), t.clone()) + }) + .unwrap(); + + let list = ListRef::from_value(res.value()).unwrap(); + let items: Vec<starlark::values::Value<'_>> = list.iter().collect(); + assert_eq!(items.len(), 2); + assert_eq!(items[0].to_repr(), r#"ParentInfo(parent = "parent_val")"#); + assert_eq!(items[1].to_repr(), r#"ChildInfo(child = "child_val")"#); +}
diff --git a/src/gn/starlark/crates/rule/tests/frozen_rule.rs b/src/gn/starlark/crates/rule/tests/frozen_rule.rs new file mode 100644 index 0000000..45b814f --- /dev/null +++ b/src/gn/starlark/crates/rule/tests/frozen_rule.rs
@@ -0,0 +1,191 @@ +// Copyright 2026 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use std::{ + collections::{HashMap, HashSet}, + sync::Mutex, +}; + +use attr::{Attr, LabelOrFile}; +use rule::FrozenRule; +use starlark::{environment::FrozenModule, values::FrozenValueTyped}; +use testutils::{Assert, FakeEvalContext, FakeTarget}; +use types::{Label, OutputType, PackageRef, Session}; + +fn new_assert() -> Assert { + Assert::new_rule_assert() +} + +#[test] +fn test_pure_rule_inheritance() { + let mut assert = new_assert(); + let native = assert.load_module("//rules:native.scl"); + let pure = assert.load_module("//rules:pure.scl"); + + let rule = |module: &FrozenModule, name: &str| { + let val = module.get(name).unwrap().value().unpack_frozen().unwrap(); + let typed = FrozenValueTyped::<FrozenRule<FakeEvalContext>>::new(val).unwrap(); + Some(typed.as_ref()) + }; + + assert.pass( + r#" +load("//rules:pure.scl", "child_rule", "parent_rule") +load("//rules:native.scl", "custom_shared_library", "static_library") + +custom_shared_library( + name = "shared_library", + mandatory = "mandatory_val", + optional = "optional_val", + unknown = "unknown", +) + +static_library( + name = "static_library", + optional = "optional_val", + unknown = "unknown" +) + +parent_rule( + name = "parent_defaulted", + parent_only = "p", +) + +child_rule( + name = "child_defaulted", + parent_only = "p", + child_only = "c", +) + +child_rule( + name = "child_override", + parent_only = "parent_val", + child_only = "child_val", + override = "//:custom_val", +) +"#, + ); + + let heap = starlark::values::FrozenHeap::new(); + let mut unknown_attrs = HashMap::new(); + unknown_attrs.insert( + "unknown".to_owned(), + starlark::values::Value::new_frozen(heap.alloc("unknown")), + ); + + let context = assert.context(); + let load = |name: &str| { + let label = Label::new(PackageRef::root().to_owned(), name.to_owned()); + context + .session + .get_target(label.as_ref(), context.session.default_toolchain.as_ref()) + }; + + assert_eq!( + *load("shared_library"), + FakeTarget { + label: Label::new(PackageRef::root().to_owned(), "shared_library".to_owned()), + toolchain: context.session.default_toolchain.clone(), + outputs: vec![], + attrs: vec![ + Attr::String("optional_val".to_owned()), + Attr::String("mandatory_val".to_owned()), + ], + output_type: Some(OutputType::SharedLibrary), + rule: rule(&native, "custom_shared_library"), + cxx_attrs: unknown_attrs.clone(), + dependencies: Mutex::new(HashSet::new()), + } + ); + + assert_eq!( + *load("static_library"), + FakeTarget { + label: Label::new(PackageRef::root().to_owned(), "static_library".to_owned()), + toolchain: context.session.default_toolchain.clone(), + outputs: vec![], + attrs: vec![Attr::String("optional_val".to_owned())], + output_type: Some(OutputType::StaticLibrary), + rule: rule(&native, "static_library"), + cxx_attrs: unknown_attrs.clone(), + dependencies: Mutex::new(HashSet::new()), + } + ); + + let toolchain = Label::new( + PackageRef::root().to_owned(), + "default_toolchain".to_owned(), + ); + + assert_eq!( + *load("parent_defaulted"), + FakeTarget { + label: Label::new(PackageRef::root().to_owned(), "parent_defaulted".to_owned()), + toolchain: toolchain.clone(), + outputs: vec![], + attrs: vec![ + Attr::String("p".to_owned()), + Attr::Label(Some(LabelOrFile::Label(Label::new( + PackageRef::root().to_owned(), + "parent".to_owned() + )))), + ], + output_type: None, + rule: rule(&pure, "parent_rule"), + cxx_attrs: HashMap::new(), + dependencies: Mutex::new(HashSet::from([( + Label::new(PackageRef::root().to_owned(), "parent".to_owned()), + toolchain.clone(), + )])), + } + ); + + assert_eq!( + *load("child_defaulted"), + FakeTarget { + label: Label::new(PackageRef::root().to_owned(), "child_defaulted".to_owned()), + toolchain: toolchain.clone(), + outputs: vec![], + attrs: vec![ + Attr::String("p".to_owned()), + Attr::Label(Some(LabelOrFile::Label(Label::new( + PackageRef::root().to_owned(), + "child".to_owned() + )))), + Attr::String("c".to_owned()), + ], + output_type: None, + rule: rule(&pure, "child_rule"), + cxx_attrs: HashMap::new(), + dependencies: Mutex::new(HashSet::from([( + Label::new(PackageRef::root().to_owned(), "child".to_owned()), + toolchain.clone(), + )])), + } + ); + + assert_eq!( + *load("child_override"), + FakeTarget { + label: Label::new(PackageRef::root().to_owned(), "child_override".to_owned()), + toolchain: toolchain.clone(), + outputs: vec![], + attrs: vec![ + Attr::String("parent_val".to_owned()), + Attr::Label(Some(LabelOrFile::Label(Label::new( + PackageRef::root().to_owned(), + "custom_val".to_owned() + )))), + Attr::String("child_val".to_owned()), + ], + output_type: None, + rule: rule(&pure, "child_rule"), + cxx_attrs: HashMap::new(), + dependencies: Mutex::new(HashSet::from([( + Label::new(PackageRef::root().to_owned(), "custom_val".to_owned()), + toolchain.clone(), + )])), + } + ); +}
diff --git a/src/gn/starlark/crates/testutils/Cargo.toml b/src/gn/starlark/crates/testutils/Cargo.toml index d307753..410da82 100644 --- a/src/gn/starlark/crates/testutils/Cargo.toml +++ b/src/gn/starlark/crates/testutils/Cargo.toml
@@ -10,6 +10,8 @@ [dependencies] allocative = { workspace = true } attr = { path = "../attr" } +providers = { path = "../providers" } +rule = { path = "../rule" } starlark = { workspace = true } starlark_derive = { workspace = true } types = { path = "../types" }
diff --git a/src/gn/starlark/crates/testutils/src/assert.rs b/src/gn/starlark/crates/testutils/src/assert.rs index 565240d..d8e7681 100644 --- a/src/gn/starlark/crates/testutils/src/assert.rs +++ b/src/gn/starlark/crates/testutils/src/assert.rs
@@ -8,7 +8,7 @@ environment::{FrozenModule, GlobalsBuilder}, values::UnpackValue, }; -use types::{EvaluatorContextExt, Label, PathResolver, UnpackedOwnedValue}; +use types::{EvalContext, EvaluatorContextExt, Label, PathResolver, UnpackedOwnedValue}; use crate::{register_globals, FakeEvalContext, FakeSession}; @@ -61,6 +61,35 @@ s } + /// Creates a new `Assert` helper instance with rule evaluation globals and + /// builtins registered. + pub fn new_rule_assert() -> Self { + let mut assert = Self::default(); + assert.modify_globals(|builder| { + rule::register_rule_globals!(builder, FakeEvalContext); + providers::register_providers(builder); + + fn make_attr_schema<'v>( + kind: attr::AttrKind, + args: attr::AttrSpecArgs<'v>, + eval: &mut starlark::eval::Evaluator<'v, '_, '_>, + ) -> starlark::Result<starlark::values::Value<'v>> { + let context: &FakeEvalContext = eval.context(); + attr::AttrSchema::create( + kind, + args, + context.current_package(), + &context.path_resolver, + &eval.heap(), + ) + } + builder.set("attr", attr::AttrModule { make_attr_schema }); + }); + let builtins = rule::register_builtin_rules::<FakeEvalContext>(); + assert.module_add(builtins); + assert + } + /// Adds a modifier to globals. /// This modifier is applied after all existing modifiers. pub fn modify_globals(&mut self, f: impl Fn(&mut GlobalsBuilder) + 'static) {
diff --git a/src/gn/starlark/crates/testutils/src/eval_context.rs b/src/gn/starlark/crates/testutils/src/eval_context.rs index ad2004a..3efc0d9 100644 --- a/src/gn/starlark/crates/testutils/src/eval_context.rs +++ b/src/gn/starlark/crates/testutils/src/eval_context.rs
@@ -5,7 +5,7 @@ use attr::{Attr, EvalContext as AttrEvalContext, EvalContextAttrExt, Session as AttrSession}; use starlark::{ - values::{FrozenValue, Heap, ProvidesStaticType, Value}, + values::{FrozenValue, FrozenValueTyped, Heap, ProvidesStaticType, Value}, Result, }; use types::{ @@ -139,7 +139,13 @@ label, toolchain, output_type: target_type, - rule, + rule: if rule.is_none() { + None + } else { + let typed = + FrozenValueTyped::<rule::FrozenRule<FakeEvalContext>>::new(rule).unwrap(); + Some(typed.as_ref()) + }, cxx_attrs: scope.0.clone(), outputs: vec![], attrs, @@ -147,3 +153,5 @@ })) } } + +rule::impl_ctx_methods!(FakeEvalContext);
diff --git a/src/gn/starlark/crates/testutils/src/session.rs b/src/gn/starlark/crates/testutils/src/session.rs index 78d1e94..9118d7c 100644 --- a/src/gn/starlark/crates/testutils/src/session.rs +++ b/src/gn/starlark/crates/testutils/src/session.rs
@@ -10,6 +10,7 @@ use crate::{FakeTarget, FakeTargetRef}; /// A fake implementation of the `Session` trait for testing. +#[derive(Clone)] pub struct FakeSession { /// The preconfigured default toolchain label. pub default_toolchain: Label, @@ -80,12 +81,18 @@ type TargetRef = FakeTargetRef; fn get_target(&self, label: LabelRef<'_>, current_toolchain: LabelRef<'_>) -> Self::TargetRef { - self.targets - .borrow() + let targets = self.targets.borrow(); + if let Some(target) = targets .iter() .find(|target| target.label() == label && target.toolchain() == current_toolchain) - .unwrap() - .clone() + { + target.clone() + } else { + panic!( + "get_target failed to find label: {:?}, toolchain: {:?}. Available targets: {:?}", + label, current_toolchain, targets + ); + } } fn register_dependency<'a>(
diff --git a/src/gn/starlark/crates/testutils/src/target.rs b/src/gn/starlark/crates/testutils/src/target.rs index f1a6ee8..4889eed 100644 --- a/src/gn/starlark/crates/testutils/src/target.rs +++ b/src/gn/starlark/crates/testutils/src/target.rs
@@ -12,13 +12,15 @@ use attr::Attr; use starlark::{ starlark_simple_value, - values::{FrozenValue, ProvidesStaticType, StarlarkValue, Value, ValueLike}, + values::{Heap, ProvidesStaticType, StarlarkValue, Value, ValueLike}, }; use starlark_derive::{starlark_value, NoSerialize}; use types::{ File, IPromiseToImplementStarlarkEqAndHash, Label, LabelRef, OutputType, Session, TargetRef, }; +use crate::FakeEvalContext; + /// A fake target struct for testing. #[derive(Allocative, Debug)] pub struct FakeTarget { @@ -29,7 +31,7 @@ /// A list of attributes. pub attrs: Vec<Attr>, pub output_type: Option<OutputType>, - pub rule: FrozenValue, + pub rule: Option<&'static rule::FrozenRule<FakeEvalContext>>, #[allocative(skip)] pub cxx_attrs: HashMap<String, Value<'static>>, /// Registered target dependencies. @@ -44,7 +46,7 @@ && self.outputs == other.outputs && self.attrs == other.attrs && self.output_type == other.output_type - && self.rule == other.rule + && self.rule.map(|r| r as *const _) == other.rule.map(|r| r as *const _) && self.cxx_attrs.len() == other.cxx_attrs.len() && self.cxx_attrs.iter().all(|(k, v)| { other @@ -132,6 +134,8 @@ } impl TargetRef for FakeTargetRef { + type Rule = rule::FrozenRule<FakeEvalContext>; + fn label(&self) -> LabelRef<'_> { self.get().label.as_ref() } @@ -140,6 +144,14 @@ self.get().toolchain.as_ref() } + fn rule(&self) -> Option<&'static Self::Rule> { + self.get().rule + } + + fn output_type(&self) -> Option<OutputType> { + self.get().output_type + } + fn outputs(&self) -> Vec<File> { self.get().outputs.clone() } @@ -157,4 +169,20 @@ attr.register_dependencies(session, self.clone(), toolchain); } } + + fn builtin_attrs<'v>(&self, _heap: &Heap<'v>) -> Vec<Value<'v>> { + self.get() + .output_type + .map(|ot| { + let (file_fields, target_fields) = ot.attrs(); + vec![Value::new_none(); file_fields.len() + target_fields.len()] + }) + .unwrap_or_default() + } +} + +impl attr::TargetAttrExt for FakeTargetRef { + fn attrs(&self) -> &[Attr] { + &self.get().attrs + } }
diff --git a/src/gn/starlark/crates/types/src/eval_context.rs b/src/gn/starlark/crates/types/src/eval_context.rs index 9886372..f42cbb9 100644 --- a/src/gn/starlark/crates/types/src/eval_context.rs +++ b/src/gn/starlark/crates/types/src/eval_context.rs
@@ -78,3 +78,7 @@ self.extra = Some(context); } } + +pub trait CtxMethods { + fn methods() -> &'static starlark::environment::Methods; +}
diff --git a/src/gn/starlark/crates/types/src/lib.rs b/src/gn/starlark/crates/types/src/lib.rs index ae22eaf..ea3ed1a 100644 --- a/src/gn/starlark/crates/types/src/lib.rs +++ b/src/gn/starlark/crates/types/src/lib.rs
@@ -20,7 +20,7 @@ pub use ctx_state::CtxState; pub(crate) use errors::Error; -pub use eval_context::{EvalContext, EvaluatorContextExt}; +pub use eval_context::{CtxMethods, EvalContext, EvaluatorContextExt}; pub use file::{intern_string, File}; pub use label::Label; pub use label_ref::LabelRef;
diff --git a/src/gn/starlark/crates/types/src/target_ref.rs b/src/gn/starlark/crates/types/src/target_ref.rs index 98c7b2c..f24c749 100644 --- a/src/gn/starlark/crates/types/src/target_ref.rs +++ b/src/gn/starlark/crates/types/src/target_ref.rs
@@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use starlark::values::{AllocValue, StarlarkValue}; +use starlark::values::{AllocValue, Heap, StarlarkValue, Value}; -use crate::{File, LabelRef, Session}; +use crate::{File, LabelRef, OutputType, Session}; /// Unfortunately while we could specify that Eq and Hash are implemented, there /// is no way to delegate starlark's equality and hash function to it @@ -23,6 +23,11 @@ /// Returns the toolchain the label was defined in. fn toolchain(&self) -> LabelRef<'_>; + type Rule: for<'v> StarlarkValue<'v>; + + /// Returns the rule that this target was built from. + /// May return None if the target is a pure GN target. + fn rule(&self) -> Option<&'static Self::Rule>; /// Returns the output files produced by this target. fn outputs(&self) -> Vec<File>; @@ -37,11 +42,16 @@ label_prefix: &str, package_name_separator: &str, ) -> String; - /// Registers target dependencies contained within this target's attributes. fn register_dependencies<S: Session<TargetRef = Self>>( &self, session: &S, toolchain: LabelRef<'_>, ); + + /// Returns the target's output type. + fn output_type(&self) -> Option<OutputType>; + + /// Returns the resolved built-in attributes as Starlark values. + fn builtin_attrs<'v>(&self, heap: &Heap<'v>) -> Vec<Value<'v>>; }
diff --git a/src/gn/starlark/src/testdata/rules/pure.scl b/src/gn/starlark/src/testdata/rules/pure.scl index 6be8bb6..368b3eb 100644 --- a/src/gn/starlark/src/testdata/rules/pure.scl +++ b/src/gn/starlark/src/testdata/rules/pure.scl
@@ -1,5 +1,8 @@ +ParentInfo = provider(fields = ["parent"]) +ChildInfo = provider(fields = ["child"]) + def _parent_impl(ctx): - return [] + return [ParentInfo(parent = ctx.attr.parent_only)] parent_rule = rule( implementation = _parent_impl, @@ -10,7 +13,7 @@ ) def _child_impl(ctx): - return [] + return ctx.super() + [ChildInfo(child = ctx.attr.child_only)] child_rule = rule( implementation = _child_impl,