Support builtin provider types.
Specifically, we add support here for DefaultInfo,
GnSubstititutionsInfo, and GnInputsInfo.
The latter two are only accessible through
load("//builtins:providers.bzl")
Bug: 528225104
Change-Id: I22a18e7f6626c5a882b31cba45ea34ba6a6a6964
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/24081
Reviewed-by: Cole Faust <colefaust@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Reviewed-by: Philipp Wollermann <philwo@google.com>
Commit-Queue: Matt Stark <msta@google.com>
diff --git a/src/gn/starlark/Cargo.lock b/src/gn/starlark/Cargo.lock
index 0be2db9..b4c12dc 100644
--- a/src/gn/starlark/Cargo.lock
+++ b/src/gn/starlark/Cargo.lock
@@ -24,7 +24,7 @@
[[package]]
name = "allocative"
version = "0.3.6"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#24e20d48f4601ccfad4707566f6e461d0973d9e0"
dependencies = [
"allocative_derive",
"bumpalo",
@@ -37,7 +37,7 @@
[[package]]
name = "allocative_derive"
version = "0.3.6"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#24e20d48f4601ccfad4707566f6e461d0973d9e0"
dependencies = [
"proc-macro2",
"quote",
@@ -291,7 +291,7 @@
[[package]]
name = "cmp_any"
version = "0.8.1"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#24e20d48f4601ccfad4707566f6e461d0973d9e0"
[[package]]
name = "cobs"
@@ -535,7 +535,7 @@
[[package]]
name = "display_container"
version = "0.9.0"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#24e20d48f4601ccfad4707566f6e461d0973d9e0"
dependencies = [
"either",
"indenter",
@@ -544,7 +544,7 @@
[[package]]
name = "dupe"
version = "0.9.1"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#24e20d48f4601ccfad4707566f6e461d0973d9e0"
dependencies = [
"dupe_derive",
]
@@ -552,7 +552,7 @@
[[package]]
name = "dupe_derive"
version = "0.9.1"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#24e20d48f4601ccfad4707566f6e461d0973d9e0"
dependencies = [
"proc-macro2",
"quote",
@@ -1038,7 +1038,7 @@
[[package]]
name = "pagable"
version = "0.4.1"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#24e20d48f4601ccfad4707566f6e461d0973d9e0"
dependencies = [
"allocative",
"anyhow",
@@ -1074,7 +1074,7 @@
[[package]]
name = "pagable_derive"
version = "0.4.1"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#24e20d48f4601ccfad4707566f6e461d0973d9e0"
dependencies = [
"proc-macro2",
"quote",
@@ -1138,6 +1138,8 @@
version = "0.1.0"
dependencies = [
"allocative",
+ "args",
+ "depset",
"either",
"starlark",
"starlark_derive",
@@ -1492,7 +1494,7 @@
[[package]]
name = "starlark"
version = "0.14.2"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#24e20d48f4601ccfad4707566f6e461d0973d9e0"
dependencies = [
"allocative",
"anyhow",
@@ -1535,7 +1537,7 @@
[[package]]
name = "starlark_derive"
version = "0.14.2"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#24e20d48f4601ccfad4707566f6e461d0973d9e0"
dependencies = [
"dupe",
"proc-macro2",
@@ -1546,7 +1548,7 @@
[[package]]
name = "starlark_map"
version = "0.14.2"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#24e20d48f4601ccfad4707566f6e461d0973d9e0"
dependencies = [
"allocative",
"dupe",
@@ -1561,7 +1563,7 @@
[[package]]
name = "starlark_syntax"
version = "0.14.2"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#24e20d48f4601ccfad4707566f6e461d0973d9e0"
dependencies = [
"allocative",
"annotate-snippets",
@@ -1599,7 +1601,7 @@
[[package]]
name = "strong_hash"
version = "0.1.0"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#24e20d48f4601ccfad4707566f6e461d0973d9e0"
dependencies = [
"ref-cast",
"strong_hash_derive",
@@ -1608,7 +1610,7 @@
[[package]]
name = "strong_hash_derive"
version = "0.1.0"
-source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#61ec3746b536d7f7824dd56b7f4cbd2e2d99d77d"
+source = "git+https://github.com/matts1/starlark-rust.git?branch=gn_starlark#24e20d48f4601ccfad4707566f6e461d0973d9e0"
dependencies = [
"quote",
"syn 2.0.119",
diff --git a/src/gn/starlark/crates/args/src/unpack.rs b/src/gn/starlark/crates/args/src/unpack.rs
index 80b7ed7..f583847 100644
--- a/src/gn/starlark/crates/args/src/unpack.rs
+++ b/src/gn/starlark/crates/args/src/unpack.rs
@@ -2,7 +2,7 @@
use starlark::{
eval::Evaluator,
typing::Ty,
- values::{list::UnpackList, type_repr::StarlarkTypeRepr, UnpackValue, Value, ValueTyped},
+ values::{list::ListRef, type_repr::StarlarkTypeRepr, UnpackValue, Value, ValueTyped},
};
use crate::{formatter::Formatter, FrozenArgs};
@@ -16,6 +16,7 @@
}
}
+#[derive(Debug, Default)]
pub struct FrozenArgsSequence<'v>(pub Vec<Either<&'v str, ValueTyped<'v, FrozenArgs>>>);
impl<'v> StarlarkTypeRepr for FrozenArgsSequence<'v> {
@@ -30,8 +31,15 @@
type Error = starlark::Error;
fn unpack_value_impl(value: Value<'v>) -> Result<Option<Self>, Self::Error> {
- let list = <UnpackList<Either<&'v str, ValueTyped<'v, FrozenArgs>>>>::unpack_value(value)?;
- Ok(list.map(|l| FrozenArgsSequence(l.items)))
+ // We unpack the list elements one by one, because when you pass [1] to
+ // UnpackList<&str>, it says "expected list, but got list".
+ // This way, it says "expected string | Args, but got int"
+ Ok(Some(FrozenArgsSequence(
+ <&ListRef>::unpack_value_err(value)?
+ .iter()
+ .map(<Either<&'v str, ValueTyped<'v, FrozenArgs>>>::unpack_value_err)
+ .collect::<Result<Vec<_>, _>>()?,
+ )))
}
}
diff --git a/src/gn/starlark/crates/loader/src/loader.rs b/src/gn/starlark/crates/loader/src/loader.rs
index dd4b19b..516f02c 100644
--- a/src/gn/starlark/crates/loader/src/loader.rs
+++ b/src/gn/starlark/crates/loader/src/loader.rs
@@ -55,6 +55,17 @@
}
impl FileLoader {
+ /// Preloads a frozen module into the loader's cache.
+ /// This should be called once for each builtin module.
+ /// By convention, builtin modules will be named "//builtins:$NAME.scl"
+ pub fn preload(&self, module: FrozenModule) {
+ let mut files = self.files.write().unwrap();
+ files.insert(
+ module.frozen_heap().name().unwrap().to_string(),
+ Arc::new(Mutex::new(FileStatus::Loaded(Ok(Box::pin(module))))),
+ );
+ }
+
fn wait_for_load(
&self,
file_status: &Arc<Mutex<FileStatus>>,
diff --git a/src/gn/starlark/crates/providers/Cargo.toml b/src/gn/starlark/crates/providers/Cargo.toml
index ff8eaf4..80a0dcc 100644
--- a/src/gn/starlark/crates/providers/Cargo.toml
+++ b/src/gn/starlark/crates/providers/Cargo.toml
@@ -11,6 +11,8 @@
[dependencies]
allocative = { workspace = true }
+args = { path = "../args" }
+depset = { path = "../depset" }
either = { workspace = true }
starlark = { workspace = true }
starlark_derive = { workspace = true }
diff --git a/src/gn/starlark/crates/providers/src/builtins.rs b/src/gn/starlark/crates/providers/src/builtins.rs
new file mode 100644
index 0000000..53e0958
--- /dev/null
+++ b/src/gn/starlark/crates/providers/src/builtins.rs
@@ -0,0 +1,153 @@
+// 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 depset::FrozenDepset;
+use starlark::{
+ environment::{FrozenModule, GlobalsBuilder, Module},
+ eval::ParametersSpecParam::{Defaulted, Required},
+ values::{typing::TypeInstanceId, FrozenValue, FrozenValueTyped},
+};
+
+use crate::{provider_instance::ProviderInstanceGen, provider_type::FrozenProviderType};
+
+// Compile-time assertion that the layout matches u64, in case starlark-rs
+// decides to change this.
+const _: () = {
+ assert!(std::mem::size_of::<TypeInstanceId>() == std::mem::size_of::<u64>());
+ assert!(std::mem::align_of::<TypeInstanceId>() == std::mem::align_of::<u64>());
+};
+
+// Hardcoded stable IDs for builtin providers.
+// These are verified by a unit test to guarantee correctness and prevent drift.
+pub(crate) const DEFAULT_INFO_ID: TypeInstanceId =
+ unsafe { std::mem::transmute::<u64, TypeInstanceId>(6904120031855430705) };
+pub(crate) const INPUTS_INFO_ID: TypeInstanceId =
+ unsafe { std::mem::transmute::<u64, TypeInstanceId>(10321705343315126159) };
+pub(crate) const SUBSTITUTIONS_INFO_ID: TypeInstanceId =
+ unsafe { std::mem::transmute::<u64, TypeInstanceId>(8875227055924591575) };
+
+/// Holds the global built-in provider types and default instances created at
+/// initialization time.
+///
+/// All builtin providers except DefaultInfo (which is available globally) are
+/// available through load("//builtins:providers.scl", "$NAME")
+///
+/// Builtin providers are no different to any other providers, except that
+/// rather than just being metadata passed between targets, GN does something
+/// special with them.
+/// * DefaultInfo(files=depset(...)) (https://bazel.build/rules/lib/providers/DefaultInfo)
+/// * The "outputs" of a rule. Building the alias for the target builds all
+/// files in DefaultInfo.
+/// * Unlike all other providers, available globally without calling
+/// * GnInputsInfo(files=depset(...))
+/// * When the target is a mixed C++/starlark target, this adds the specified
+/// inputs as implicit inputs to all ninja actions C++ generates for this
+/// target.
+/// * GnSubstitutionsInfo(substitutions=struct(foo =
+/// [ctx.actions.args().add("--foo", ctx.file.foo)]))
+/// * Adds "foo = --foo path/to/foo" to the ninja file
+/// * Adding command = "... {{foo}}" to your GN tool will allow you to use
+/// this in GN.
+pub struct BuiltinProviders {
+ /// Default instance of `DefaultInfo`.
+ /// Targets that do not return an explicit DefaultInfo provider will have
+ /// target[DefaultInfo] return this.
+ pub default_defaultinfo: FrozenValue,
+
+ /// A frozen Starlark module containing the built-in provider definitions.
+ /// This module is preloaded as `//builtins:providers.scl` and can be loaded
+ /// in Starlark files.
+ pub module: FrozenModule,
+}
+
+pub(crate) fn register_builtin_providers(builder: &mut GlobalsBuilder) -> BuiltinProviders {
+ let mut default_defaultinfo = None;
+
+ let module = Module::with_temp_heap(|module: Module| {
+ let heap = module.frozen_heap();
+
+ let empty_depset = heap.alloc(FrozenDepset::default());
+ let default_info = FrozenProviderType::new(
+ DEFAULT_INFO_ID,
+ "DefaultInfo",
+ &[
+ ("files", Defaulted(empty_depset)),
+ ("executable", Defaulted(FrozenValue::new_none())),
+ ],
+ heap,
+ );
+ let default_info_value = heap.alloc(default_info);
+ builder.set("DefaultInfo", default_info_value);
+
+ let inputs =
+ FrozenProviderType::new(INPUTS_INFO_ID, "GnInputsInfo", &[("files", Required)], heap);
+
+ let substitutions = FrozenProviderType::new(
+ SUBSTITUTIONS_INFO_ID,
+ "GnSubstitutionsInfo",
+ &[("substitutions", Required)],
+ heap,
+ );
+
+ let inputs_value = heap.alloc(inputs);
+ let substitutions_value = heap.alloc(substitutions);
+
+ module.set("GnInputsInfo", inputs_value.to_value());
+ module.set("GnSubstitutionsInfo", substitutions_value.to_value());
+
+ let defaultinfo_instance = heap.alloc(ProviderInstanceGen {
+ provider_type: FrozenValueTyped::<FrozenProviderType>::new(default_info_value)
+ .unwrap()
+ .as_ref(),
+ values: Box::new([Some(empty_depset), Some(FrozenValue::new_none())]),
+ });
+ default_defaultinfo = Some(defaultinfo_instance);
+
+ module
+ .freeze_named(starlark::values::FrozenHeapName::User(Box::new(
+ "//builtins:providers.scl".to_owned(),
+ )))
+ .unwrap()
+ });
+
+ let default_defaultinfo = default_defaultinfo.unwrap();
+ // DefaultInfo is allocated in the module's heap, so we ensure that the module's
+ // heap will never disappear while the builder is active.
+ builder.frozen_heap().add_reference(module.frozen_heap());
+
+ BuiltinProviders {
+ default_defaultinfo,
+ module,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use starlark::values::typing::TypeIdDomain;
+
+ use super::*;
+
+ struct GnBuiltinDomain;
+ impl TypeIdDomain for GnBuiltinDomain {
+ fn tag(&self) -> &'static str {
+ "gn.builtin_provider"
+ }
+ }
+
+ #[test]
+ fn test_builtin_provider_ids_match_identity() {
+ assert_eq!(
+ DEFAULT_INFO_ID,
+ TypeInstanceId::from_identity(GnBuiltinDomain, &"DefaultInfo")
+ );
+ assert_eq!(
+ INPUTS_INFO_ID,
+ TypeInstanceId::from_identity(GnBuiltinDomain, &"GnInputsInfo")
+ );
+ assert_eq!(
+ SUBSTITUTIONS_INFO_ID,
+ TypeInstanceId::from_identity(GnBuiltinDomain, &"GnSubstitutionsInfo")
+ );
+ }
+}
diff --git a/src/gn/starlark/crates/providers/src/errors.rs b/src/gn/starlark/crates/providers/src/errors.rs
index 0de8529..63bb393 100644
--- a/src/gn/starlark/crates/providers/src/errors.rs
+++ b/src/gn/starlark/crates/providers/src/errors.rs
@@ -17,6 +17,14 @@
ProviderNotExported,
#[error("Cannot construct values of non-frozen provider type")]
ProviderNotFrozen,
+ #[error("Duplicate provider: {0}")]
+ DuplicateProvider(String),
+ #[error("DefaultInfo.files must be a depset of files, got {0}")]
+ DefaultInfoFilesMustBeFileDepset(String),
+ #[error("GnInputsInfo.files must be a depset of files, got {0}")]
+ GnInputsInfoFilesMustBeFileDepset(String),
+ #[error("GnSubstitutionsInfo.substitutions must be a struct, got {0}")]
+ GnSubstitutionsInfoSubstitutionsMustBeStruct(String),
}
impl From<Error> for starlark::values::FreezeError {
diff --git a/src/gn/starlark/crates/providers/src/globals.rs b/src/gn/starlark/crates/providers/src/globals.rs
index d503789..5d28c26 100644
--- a/src/gn/starlark/crates/providers/src/globals.rs
+++ b/src/gn/starlark/crates/providers/src/globals.rs
@@ -1,7 +1,7 @@
use starlark::{environment::GlobalsBuilder, eval::Evaluator, values::Value};
use starlark_derive::starlark_module;
-use crate::Error;
+use crate::{BuiltinProviders, Error};
/// Registers the global `provider()` function.
#[starlark_module]
@@ -27,6 +27,7 @@
}
}
-pub fn register_providers(builder: &mut GlobalsBuilder) {
+pub fn register_providers(builder: &mut GlobalsBuilder) -> BuiltinProviders {
register_providers_globals(builder);
+ crate::builtins::register_builtin_providers(builder)
}
diff --git a/src/gn/starlark/crates/providers/src/lib.rs b/src/gn/starlark/crates/providers/src/lib.rs
index ab02770..77320b7 100644
--- a/src/gn/starlark/crates/providers/src/lib.rs
+++ b/src/gn/starlark/crates/providers/src/lib.rs
@@ -2,12 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+pub mod builtins;
pub(crate) mod errors;
pub mod globals;
pub mod provider_instance;
pub mod provider_type;
+pub mod providers;
+pub use builtins::BuiltinProviders;
pub(crate) use errors::Error;
pub use globals::register_providers;
pub use provider_instance::{FrozenProviderInstance, ProviderInstance};
pub use provider_type::ProviderType;
+pub use providers::Providers;
diff --git a/src/gn/starlark/crates/providers/src/provider_type.rs b/src/gn/starlark/crates/providers/src/provider_type.rs
index 943fc00..10f282c 100644
--- a/src/gn/starlark/crates/providers/src/provider_type.rs
+++ b/src/gn/starlark/crates/providers/src/provider_type.rs
@@ -157,6 +157,37 @@
})
}
}
+
+impl FrozenProviderType {
+ /// Creates a new builtin provider type with a custom stable TypeInstanceId.
+ /// Unlike regular providers, these providers may have either Defaulted or
+ /// Required parameters.
+ pub fn new(
+ id: TypeInstanceId,
+ name: &'static str,
+ fields: &[(
+ &'static str,
+ starlark::eval::ParametersSpecParam<FrozenValue>,
+ )],
+ heap: &starlark::values::FrozenHeap,
+ ) -> Self {
+ let field_map = fields
+ .iter()
+ .enumerate()
+ .map(|(idx, (name, _))| (name.to_string(), idx))
+ .collect();
+ let name_frozen = heap.alloc_str(name);
+ Self {
+ id,
+ data: ProviderTypeData {
+ name: name_frozen,
+ parameter_spec: ParametersSpec::new_named_only(name, fields.to_vec()),
+ },
+ fields: field_map,
+ }
+ }
+}
+
impl Freeze for ProviderType {
type Frozen = FrozenProviderType;
diff --git a/src/gn/starlark/crates/providers/src/providers.rs b/src/gn/starlark/crates/providers/src/providers.rs
new file mode 100644
index 0000000..c6c24f5
--- /dev/null
+++ b/src/gn/starlark/crates/providers/src/providers.rs
@@ -0,0 +1,259 @@
+// 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 args::FrozenArgsSequence;
+use depset::UnpackFileDepset;
+use starlark::{
+ collections::SmallMap,
+ typing::Ty,
+ values::{
+ list::FrozenListRef, structs::FrozenStructRef, type_repr::StarlarkTypeRepr,
+ typing::TypeInstanceId, FrozenHeapRef, FrozenValue, OwnedFrozenValue, UnpackValue as _,
+ Value,
+ },
+};
+use types::File;
+
+use crate::ProviderInstance;
+
+/// Helper to unpack a frozen list of providers to useful metadata.
+#[derive(Debug, Default)]
+pub struct Providers {
+ /// The output files produced by this target, parsed from the `DefaultInfo`
+ /// provider.
+ ///
+ /// `DefaultInfo` represents the default outputs of a target rule (similar
+ /// to Bazel's `DefaultInfo`). In GN, it contains the `files` depset
+ /// which lists the direct and transitive outputs of the rule.
+ pub outputs_phony: Option<File>,
+
+ /// Additional input files declared by this target, parsed from the
+ /// `GnInputsInfo` provider.
+ ///
+ /// `GnInputsInfo` is a built-in provider used by target rules to declare
+ /// additional, dynamic input files that the rule's tool dependencies or
+ /// parent targets must track.
+ pub inputs_phony: Option<File>,
+
+ /// Command-line substitution variables propagated by this target, parsed
+ /// from the `GnSubstitutionsInfo` provider.
+ ///
+ /// `GnSubstitutionsInfo` carries key-value substitutions (packaged as a
+ /// struct) that are used by toolchains and command-line execution
+ /// blocks to expand variables and flags dynamically.
+ pub substitutions: SmallMap<&'static str, FrozenArgsSequence<'static>>,
+
+ /// All provider instances mapped by their ProviderType's TypeInstanceId.
+ pub value: SmallMap<TypeInstanceId, FrozenValue>,
+
+ /// The frozen heap for the rule implementation, keeping substitutions and
+ /// value alive.
+ _heap: FrozenHeapRef,
+}
+
+impl StarlarkTypeRepr for Providers {
+ type Canonical = Value<'static>;
+
+ fn starlark_type_repr() -> Ty {
+ Ty::list(Ty::any())
+ }
+}
+
+impl TryFrom<OwnedFrozenValue> for Providers {
+ type Error = starlark::Error;
+
+ fn try_from(value: OwnedFrozenValue) -> Result<Self, Self::Error> {
+ let mut providers = Self {
+ _heap: value.owner().clone(),
+ ..Default::default()
+ };
+ let list = <&FrozenListRef>::unpack_value_err(value.value())?;
+ for &item in list.iter() {
+ let instance = <&ProviderInstance<'static>>::unpack_value_err(item.to_value())?;
+
+ if providers
+ .value
+ .insert(instance.provider_type.id, item)
+ .is_some()
+ {
+ return Err(
+ crate::errors::Error::DuplicateProvider(instance.ty_name().to_owned()).into(),
+ );
+ }
+
+ match instance.provider_type.id {
+ crate::builtins::DEFAULT_INFO_ID => {
+ let files = instance.values[0].unwrap();
+ providers.outputs_phony = UnpackFileDepset::unpack_value_err(files)
+ .map_err(|_| {
+ crate::errors::Error::DefaultInfoFilesMustBeFileDepset(files.to_repr())
+ })?
+ .0;
+ },
+ crate::builtins::INPUTS_INFO_ID => {
+ let files = instance.values[0].unwrap();
+ providers.inputs_phony = UnpackFileDepset::unpack_value_err(files)
+ .map_err(|_| {
+ crate::errors::Error::GnInputsInfoFilesMustBeFileDepset(files.to_repr())
+ })?
+ .0;
+ },
+ crate::builtins::SUBSTITUTIONS_INFO_ID => {
+ // GnSubstitutionsInfo(substitutions = struct)
+ // Safety: We already checked it was frozen earlier.
+ let substitutions_val = instance.values[0].unwrap();
+ let substitutions_struct = FrozenStructRef::from_value(unsafe {
+ substitutions_val.unpack_frozen().unwrap_unchecked()
+ })
+ .ok_or_else(|| {
+ starlark::Error::from(
+ crate::errors::Error::GnSubstitutionsInfoSubstitutionsMustBeStruct(
+ substitutions_val.to_repr(),
+ ),
+ )
+ })?;
+
+ providers.substitutions = substitutions_struct
+ .iter()
+ .map(|(k, v)| {
+ Ok((
+ k.as_str(),
+ <FrozenArgsSequence>::unpack_value_err(v.to_value())?,
+ ))
+ })
+ .collect::<Result<_, starlark::Error>>()?;
+ },
+ _ => {},
+ }
+ }
+
+ Ok(providers)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use types::File;
+
+ use crate::Providers;
+
+ fn new_assert() -> testutils::Assert {
+ let mut a = testutils::Assert::default();
+
+ a.modify_globals(|builder| {
+ depset::depset_globals!(builder, testutils::eval_context::FakeEvalContext);
+ let builtin_providers = crate::globals::register_providers(builder);
+ // Note: In production code, the module would be preloaded instead of loading
+ // into globals.
+ builder.set(
+ "GnInputsInfo",
+ builtin_providers.module.get("GnInputsInfo").unwrap(),
+ );
+ builder.set(
+ "GnSubstitutionsInfo",
+ builtin_providers.module.get("GnSubstitutionsInfo").unwrap(),
+ );
+ });
+ a
+ }
+
+ #[test]
+ fn test_providers_unpacking() {
+ let mut a = new_assert();
+
+ let val = a.pass("[]");
+ let providers = Providers::try_from(val).unwrap();
+ assert_eq!(providers.outputs_phony, None);
+ assert_eq!(providers.inputs_phony, None);
+ assert!(providers.substitutions.is_empty());
+ assert!(providers.value.is_empty());
+
+ let custom_info_ty = a.pass("CustomInfo = provider(fields = ['foo']); CustomInfo");
+ a.modify_globals(move |builder| {
+ builder.set("CustomInfo", custom_info_ty.clone());
+ });
+
+ let val = a.pass(
+ r#"[
+ DefaultInfo(files = depset([make_file("a")])),
+ GnInputsInfo(files = depset([make_file("b")])),
+ GnSubstitutionsInfo(substitutions = struct(key = ["val"])),
+ CustomInfo(foo = 1),
+]"#,
+ );
+ let providers = Providers::try_from(val).unwrap();
+
+ assert_eq!(providers.outputs_phony, Some(File::intern("a")));
+ assert_eq!(providers.inputs_phony, Some(File::intern("b")));
+
+ let keys: Vec<&str> = providers.substitutions.keys().copied().collect();
+ assert_eq!(keys, vec!["key"]);
+
+ assert_eq!(providers.value.len(), 4);
+ assert!(providers
+ .value
+ .contains_key(&crate::builtins::DEFAULT_INFO_ID));
+ assert!(providers
+ .value
+ .contains_key(&crate::builtins::INPUTS_INFO_ID));
+ assert!(providers
+ .value
+ .contains_key(&crate::builtins::SUBSTITUTIONS_INFO_ID));
+ }
+
+ #[track_caller]
+ fn assert_unpack_fails(a: &mut testutils::Assert, expr: &str, expected_err: &str) {
+ let val = a.pass(expr);
+ let err = Providers::try_from(val).unwrap_err();
+ assert_eq!(err.to_string(), expected_err);
+ }
+
+ #[test]
+ fn test_providers_unpack_fails() {
+ let mut a = new_assert();
+
+ assert_unpack_fails(
+ &mut a,
+ "[DefaultInfo(files = depset()), DefaultInfo(files = depset())]",
+ "Duplicate provider: DefaultInfo",
+ );
+
+ assert_unpack_fails(
+ &mut a,
+ r#"[DefaultInfo(files = "not-a-depset")]"#,
+ r#"DefaultInfo.files must be a depset of files, got "not-a-depset""#,
+ );
+
+ assert_unpack_fails(
+ &mut a,
+ r#"[DefaultInfo(files = depset(["not-a-file"]))]"#,
+ "DefaultInfo.files must be a depset of files, got depset(...)",
+ );
+
+ assert_unpack_fails(
+ &mut a,
+ r#"[GnInputsInfo(files = "not-a-depset")]"#,
+ r#"GnInputsInfo.files must be a depset of files, got "not-a-depset""#,
+ );
+
+ assert_unpack_fails(
+ &mut a,
+ r#"[GnSubstitutionsInfo(substitutions = {"key": ["val"]})]"#,
+ r#"GnSubstitutionsInfo.substitutions must be a struct, got {"key": ["val"]}"#,
+ );
+
+ assert_unpack_fails(
+ &mut a,
+ r#"[GnSubstitutionsInfo(substitutions = struct(key = "not-a-list"))]"#,
+ r#"Expected `list`, but got `string (repr: "not-a-list")`"#,
+ );
+
+ assert_unpack_fails(
+ &mut a,
+ r#"[GnSubstitutionsInfo(substitutions = struct(key = [123]))]"#,
+ "Expected `Args | str`, but got `int (repr: 123)`",
+ );
+ }
+}