Create a simple session type and create a test for it. It's mostly filled with todo! for implementing the more complex logic, but it can load values from starlark files. Bug: 528225104 Change-Id: I72fab7d0c2691453b612f1beb39f86d16a6a6964 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/24580 Reviewed-by: Takuto Ikuta <tikuta@google.com> Reviewed-by: Richard Wang <richardwa@google.com> Commit-Queue: Matt Stark <msta@google.com>
diff --git a/build/gen.py b/build/gen.py index eacd28a..c7d03f7 100755 --- a/build/gen.py +++ b/build/gen.py
@@ -764,6 +764,7 @@ 'src/gn/ffi/bridge.cc', 'src/gn/ffi/scope.cc', 'src/gn/ffi/value.cc', + 'src/gn/ffi/session.cc', 'src/gn/filesystem_utils.cc', 'src/gn/file_writer.cc', 'src/gn/frameworks_utils.cc', @@ -966,6 +967,7 @@ 'src/gn/path_output_unittest.cc', 'src/gn/pattern_unittest.cc', 'src/gn/pointer_set_unittest.cc', + 'src/gn/range_utils_unittest.cc', 'src/gn/resolved_target_data_unittest.cc', 'src/gn/resolved_target_deps_unittest.cc', 'src/gn/runtime_deps_unittest.cc', @@ -1059,6 +1061,12 @@ executables['gn']['libs'].extend(static_libraries.keys()) executables['gn_unittests']['libs'].extend(static_libraries.keys()) + if options.starlark: + executables['gn_unittests']['sources'].extend([ + 'src/gn/ffi/session_unittest.cc', + ]) + executables['gn_unittests']['libs'].append('gn_starlark') + # Write the absolute path of the source root to a file in the output directory # so that tests can locate the source tree robustly. with open(os.path.join(options.out_path, 'source_root.txt'), 'w') as f:
diff --git a/src/gn/ffi/bridge.cc b/src/gn/ffi/bridge.cc index 5b94043..912855c 100644 --- a/src/gn/ffi/bridge.cc +++ b/src/gn/ffi/bridge.cc
@@ -32,6 +32,7 @@ #ifdef __GNUC__ #pragma GCC diagnostic ignored "-Wmissing-declarations" +#pragma GCC diagnostic ignored "-Wshadow" #ifdef __clang__ #pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" #endif // __clang__ @@ -54,8 +55,6 @@ class impl; } // namespace -class Opaque; - template <typename T> ::std::size_t size_of(); template <typename T> @@ -504,6 +503,172 @@ } #endif // CXXBRIDGE1_RUST_SLICE +#ifndef CXXBRIDGE1_RUST_BOX +#define CXXBRIDGE1_RUST_BOX +template <typename T> +class Box final { +public: + using element_type = T; + using const_pointer = + typename std::add_pointer<typename std::add_const<T>::type>::type; + using pointer = typename std::add_pointer<T>::type; + + Box() = delete; + Box(Box &&) noexcept; + ~Box() noexcept; + + explicit Box(const T &); + explicit Box(T &&); + + Box &operator=(Box &&) & noexcept; + + const T *operator->() const noexcept; + const T &operator*() const noexcept; + T *operator->() noexcept; + T &operator*() noexcept; + + template <typename... Fields> + static Box in_place(Fields &&...); + + void swap(Box &) noexcept; + + static Box from_raw(T *) noexcept; + + T *into_raw() noexcept; + + /* Deprecated */ using value_type = element_type; + +private: + class uninit; + class allocation; + Box(uninit) noexcept; + void drop() noexcept; + + friend void swap(Box &lhs, Box &rhs) noexcept { lhs.swap(rhs); } + + T *ptr; +}; + +template <typename T> +class Box<T>::uninit {}; + +template <typename T> +class Box<T>::allocation { + static T *alloc() noexcept; + static void dealloc(T *) noexcept; + +public: + allocation() noexcept : ptr(alloc()) {} + ~allocation() noexcept { + if (this->ptr) { + dealloc(this->ptr); + } + } + T *ptr; +}; + +template <typename T> +Box<T>::Box(Box &&other) noexcept : ptr(other.ptr) { + other.ptr = nullptr; +} + +template <typename T> +Box<T>::Box(const T &val) { + allocation alloc; + ::new (alloc.ptr) T(val); + this->ptr = alloc.ptr; + alloc.ptr = nullptr; +} + +template <typename T> +Box<T>::Box(T &&val) { + allocation alloc; + ::new (alloc.ptr) T(std::move(val)); + this->ptr = alloc.ptr; + alloc.ptr = nullptr; +} + +template <typename T> +Box<T>::~Box() noexcept { + if (this->ptr) { + this->drop(); + } +} + +template <typename T> +Box<T> &Box<T>::operator=(Box &&other) & noexcept { + if (this->ptr) { + this->drop(); + } + this->ptr = other.ptr; + other.ptr = nullptr; + return *this; +} + +template <typename T> +const T *Box<T>::operator->() const noexcept { + return this->ptr; +} + +template <typename T> +const T &Box<T>::operator*() const noexcept { + return *this->ptr; +} + +template <typename T> +T *Box<T>::operator->() noexcept { + return this->ptr; +} + +template <typename T> +T &Box<T>::operator*() noexcept { + return *this->ptr; +} + +template <typename T> +template <typename... Fields> +Box<T> Box<T>::in_place(Fields &&...fields) { + allocation alloc; + auto ptr = alloc.ptr; + ::new (ptr) T{std::forward<Fields>(fields)...}; + alloc.ptr = nullptr; + return from_raw(ptr); +} + +template <typename T> +void Box<T>::swap(Box &rhs) noexcept { + using std::swap; + swap(this->ptr, rhs.ptr); +} + +template <typename T> +Box<T> Box<T>::from_raw(T *raw) noexcept { + Box box = uninit{}; + box.ptr = raw; + return box; +} + +template <typename T> +T *Box<T>::into_raw() noexcept { + T *raw = this->ptr; + this->ptr = nullptr; + return raw; +} + +template <typename T> +Box<T>::Box(uninit) noexcept {} +#endif // CXXBRIDGE1_RUST_BOX + +#ifndef CXXBRIDGE1_RUST_OPAQUE +#define CXXBRIDGE1_RUST_OPAQUE +class Opaque { +public: + Opaque() = delete; + Opaque(const Opaque &) = delete; + ~Opaque() = delete; +}; +#endif // CXXBRIDGE1_RUST_OPAQUE + #ifndef CXXBRIDGE1_IS_COMPLETE #define CXXBRIDGE1_IS_COMPLETE namespace detail { @@ -572,6 +737,13 @@ } #endif // CXXBRIDGE1_LAYOUT +template <typename T> +union ManuallyDrop { + T value; + ManuallyDrop(T &&value) : value(::std::move(value)) {} + ~ManuallyDrop() {} +}; + namespace { template <bool> struct deleter_if { template <typename T> void operator()(T *) {} @@ -604,6 +776,7 @@ using TestWithScope = ::TestWithScope; using Value = ::Value; using ParseNode = ::ParseNode; +struct Session; #ifndef CXXBRIDGE1_STRUCT_Any #define CXXBRIDGE1_STRUCT_Any @@ -655,6 +828,23 @@ }; #endif // CXXBRIDGE1_ENUM_ValueType +#ifndef CXXBRIDGE1_STRUCT_Session +#define CXXBRIDGE1_STRUCT_Session +struct Session final : public ::rust::Opaque { + static ::rust::Box<::Session> new_cxx(::rust::Str source_root, ::rust::Str source_root_rel) noexcept; + static ::rust::Box<::Session> new_for_testing() noexcept; + void load_values(::rust::Str label, ::rust::Str relative_to, ::rust::Slice<::rust::Str const> keys, ::Scope &scope, ::Settings const &settings, ::ParseNodePtr origin, ::Err &err) const noexcept; + ~Session() = delete; + +private: + friend ::rust::layout; + struct layout { + static ::std::size_t size() noexcept; + static ::std::size_t align() noexcept; + }; +}; +#endif // CXXBRIDGE1_STRUCT_Session + extern "C" { bool cxxbridge1$196$Err$has_error(::Err const &self) noexcept { bool (::Err::*has_error$)() const = &::Err::has_error; @@ -736,6 +926,11 @@ return GetValue$(scope, ident); } +::Value *cxxbridge1$196$SetValue(::Scope &scope, ::rust::Str ident, ::ParseNodePtr *origin) noexcept { + ::Value &(*SetValue$)(::Scope &, ::rust::Str, ::ParseNodePtr) = ::SetValue; + return &SetValue$(scope, ident, ::std::move(*origin)); +} + ::Settings const *cxxbridge1$196$Scope$settings_cxx(::Scope const &self) noexcept { ::Settings const *(::Scope::*settings_cxx$)() const = &::Scope::settings; return (self.*settings_cxx$)(); @@ -820,7 +1015,38 @@ void (*SetValueScope$)(::Value &, ::ParseNodePtr, ::std::unique_ptr<::Scope>) = ::SetValueScope; SetValueScope$(val, ::std::move(*origin), ::std::unique_ptr<::Scope>(scope)); } +::std::size_t cxxbridge1$196$Session$operator$sizeof() noexcept; +::std::size_t cxxbridge1$196$Session$operator$alignof() noexcept; +::Session *cxxbridge1$196$Session$new(::rust::Str source_root, ::rust::Str source_root_rel) noexcept; + +::Session *cxxbridge1$196$Session$new_for_testing() noexcept; + +void cxxbridge1$196$Session$load_values(::Session const &self, ::rust::Str label, ::rust::Str relative_to, ::rust::Slice<::rust::Str const> keys, ::Scope &scope, ::Settings const &settings, ::ParseNodePtr *origin, ::Err &err) noexcept; +} // extern "C" + +::std::size_t Session::layout::size() noexcept { + return cxxbridge1$196$Session$operator$sizeof(); +} + +::std::size_t Session::layout::align() noexcept { + return cxxbridge1$196$Session$operator$alignof(); +} + +::rust::Box<::Session> Session::new_cxx(::rust::Str source_root, ::rust::Str source_root_rel) noexcept { + return ::rust::Box<::Session>::from_raw(cxxbridge1$196$Session$new(source_root, source_root_rel)); +} + +::rust::Box<::Session> Session::new_for_testing() noexcept { + return ::rust::Box<::Session>::from_raw(cxxbridge1$196$Session$new_for_testing()); +} + +void Session::load_values(::rust::Str label, ::rust::Str relative_to, ::rust::Slice<::rust::Str const> keys, ::Scope &scope, ::Settings const &settings, ::ParseNodePtr origin, ::Err &err) const noexcept { + ::rust::ManuallyDrop<::ParseNodePtr> origin$(::std::move(origin)); + cxxbridge1$196$Session$load_values(*this, label, relative_to, keys, scope, settings, &origin$.value, err); +} + +extern "C" { static_assert(::rust::detail::is_complete<::std::remove_extent<::Err>::type>::value, "definition of `::Err` is required"); static_assert(sizeof(::std::unique_ptr<::Err>) == sizeof(void *), ""); static_assert(alignof(::std::unique_ptr<::Err>) == alignof(void *), ""); @@ -896,4 +1122,25 @@ void cxxbridge1$unique_ptr$Value$drop(::std::unique_ptr<::Value> *ptr) noexcept { ::rust::deleter_if<::rust::detail::is_complete<::Value>::value>{}(ptr); } + +::Session *cxxbridge1$box$Session$alloc() noexcept; +void cxxbridge1$box$Session$dealloc(::Session *) noexcept; +void cxxbridge1$box$Session$drop(::rust::Box<::Session> *ptr) noexcept; } // extern "C" + +namespace rust { +inline namespace cxxbridge1 { +template <> +::Session *Box<::Session>::allocation::alloc() noexcept { + return cxxbridge1$box$Session$alloc(); +} +template <> +void Box<::Session>::allocation::dealloc(::Session *ptr) noexcept { + cxxbridge1$box$Session$dealloc(ptr); +} +template <> +void Box<::Session>::drop() noexcept { + cxxbridge1$box$Session$drop(this); +} +} // namespace cxxbridge1 +} // namespace rust
diff --git a/src/gn/ffi/bridge.h b/src/gn/ffi/bridge.h index e4baff8..6435cb1 100644 --- a/src/gn/ffi/bridge.h +++ b/src/gn/ffi/bridge.h
@@ -19,9 +19,11 @@ #include <cstdint> #include <iterator> #include <memory> +#include <new> #include <stdexcept> #include <string> #include <type_traits> +#include <utility> #if __cplusplus >= 201703L #include <string_view> #endif @@ -46,8 +48,6 @@ class impl; } // namespace -class Opaque; - template <typename T> ::std::size_t size_of(); template <typename T> @@ -496,6 +496,172 @@ } #endif // CXXBRIDGE1_RUST_SLICE +#ifndef CXXBRIDGE1_RUST_BOX +#define CXXBRIDGE1_RUST_BOX +template <typename T> +class Box final { +public: + using element_type = T; + using const_pointer = + typename std::add_pointer<typename std::add_const<T>::type>::type; + using pointer = typename std::add_pointer<T>::type; + + Box() = delete; + Box(Box &&) noexcept; + ~Box() noexcept; + + explicit Box(const T &); + explicit Box(T &&); + + Box &operator=(Box &&) & noexcept; + + const T *operator->() const noexcept; + const T &operator*() const noexcept; + T *operator->() noexcept; + T &operator*() noexcept; + + template <typename... Fields> + static Box in_place(Fields &&...); + + void swap(Box &) noexcept; + + static Box from_raw(T *) noexcept; + + T *into_raw() noexcept; + + /* Deprecated */ using value_type = element_type; + +private: + class uninit; + class allocation; + Box(uninit) noexcept; + void drop() noexcept; + + friend void swap(Box &lhs, Box &rhs) noexcept { lhs.swap(rhs); } + + T *ptr; +}; + +template <typename T> +class Box<T>::uninit {}; + +template <typename T> +class Box<T>::allocation { + static T *alloc() noexcept; + static void dealloc(T *) noexcept; + +public: + allocation() noexcept : ptr(alloc()) {} + ~allocation() noexcept { + if (this->ptr) { + dealloc(this->ptr); + } + } + T *ptr; +}; + +template <typename T> +Box<T>::Box(Box &&other) noexcept : ptr(other.ptr) { + other.ptr = nullptr; +} + +template <typename T> +Box<T>::Box(const T &val) { + allocation alloc; + ::new (alloc.ptr) T(val); + this->ptr = alloc.ptr; + alloc.ptr = nullptr; +} + +template <typename T> +Box<T>::Box(T &&val) { + allocation alloc; + ::new (alloc.ptr) T(std::move(val)); + this->ptr = alloc.ptr; + alloc.ptr = nullptr; +} + +template <typename T> +Box<T>::~Box() noexcept { + if (this->ptr) { + this->drop(); + } +} + +template <typename T> +Box<T> &Box<T>::operator=(Box &&other) & noexcept { + if (this->ptr) { + this->drop(); + } + this->ptr = other.ptr; + other.ptr = nullptr; + return *this; +} + +template <typename T> +const T *Box<T>::operator->() const noexcept { + return this->ptr; +} + +template <typename T> +const T &Box<T>::operator*() const noexcept { + return *this->ptr; +} + +template <typename T> +T *Box<T>::operator->() noexcept { + return this->ptr; +} + +template <typename T> +T &Box<T>::operator*() noexcept { + return *this->ptr; +} + +template <typename T> +template <typename... Fields> +Box<T> Box<T>::in_place(Fields &&...fields) { + allocation alloc; + auto ptr = alloc.ptr; + ::new (ptr) T{std::forward<Fields>(fields)...}; + alloc.ptr = nullptr; + return from_raw(ptr); +} + +template <typename T> +void Box<T>::swap(Box &rhs) noexcept { + using std::swap; + swap(this->ptr, rhs.ptr); +} + +template <typename T> +Box<T> Box<T>::from_raw(T *raw) noexcept { + Box box = uninit{}; + box.ptr = raw; + return box; +} + +template <typename T> +T *Box<T>::into_raw() noexcept { + T *raw = this->ptr; + this->ptr = nullptr; + return raw; +} + +template <typename T> +Box<T>::Box(uninit) noexcept {} +#endif // CXXBRIDGE1_RUST_BOX + +#ifndef CXXBRIDGE1_RUST_OPAQUE +#define CXXBRIDGE1_RUST_OPAQUE +class Opaque { +public: + Opaque() = delete; + Opaque(const Opaque &) = delete; + ~Opaque() = delete; +}; +#endif // CXXBRIDGE1_RUST_OPAQUE + #ifndef CXXBRIDGE1_IS_COMPLETE #define CXXBRIDGE1_IS_COMPLETE namespace detail { @@ -587,6 +753,7 @@ using TestWithScope = ::TestWithScope; using Value = ::Value; using ParseNode = ::ParseNode; +struct Session; #ifndef CXXBRIDGE1_STRUCT_Any #define CXXBRIDGE1_STRUCT_Any @@ -637,3 +804,20 @@ Scope = 5, }; #endif // CXXBRIDGE1_ENUM_ValueType + +#ifndef CXXBRIDGE1_STRUCT_Session +#define CXXBRIDGE1_STRUCT_Session +struct Session final : public ::rust::Opaque { + static ::rust::Box<::Session> new_cxx(::rust::Str source_root, ::rust::Str source_root_rel) noexcept; + static ::rust::Box<::Session> new_for_testing() noexcept; + void load_values(::rust::Str label, ::rust::Str relative_to, ::rust::Slice<::rust::Str const> keys, ::Scope &scope, ::Settings const &settings, ::ParseNodePtr origin, ::Err &err) const noexcept; + ~Session() = delete; + +private: + friend ::rust::layout; + struct layout { + static ::std::size_t size() noexcept; + static ::std::size_t align() noexcept; + }; +}; +#endif // CXXBRIDGE1_STRUCT_Session
diff --git a/src/gn/ffi/scope.cc b/src/gn/ffi/scope.cc index 6b3ed4d..1816803 100644 --- a/src/gn/ffi/scope.cc +++ b/src/gn/ffi/scope.cc
@@ -8,6 +8,7 @@ #include "gn/ffi/bridge.h" #include "gn/ffi/scope.h" #include "gn/ffi/slice.h" +#include "gn/range_utils.h" #include "gn/scope.h" #include "gn/value.h" @@ -71,13 +72,16 @@ SliceAny GetScopeItems(const Scope& scope) { auto range = scope.GetCurrentScopeValues() | std::views::transform([](auto pair) { - return KeyValue{rust::Str(pair.first.data(), pair.first.size()), - *pair.second}; + return KeyValue{rust::Str(pair.first), *pair.second}; }); - return IntoSlice(std::vector<KeyValue>(range.begin(), range.end())); + return IntoSlice(to_vec(range)); } const Value* GetValue(const Scope& scope, rust::Str ident) { std::string_view ident_sv(ident.data(), ident.size()); return scope.GetValue(ident_sv); } + +Value& SetValue(Scope& scope, rust::Str ident, ParseNodePtr origin) { + return *scope.SetValue(std::string_view(ident), Value(), origin.ptr); +}
diff --git a/src/gn/ffi/scope.h b/src/gn/ffi/scope.h index c62af06..58a228b 100644 --- a/src/gn/ffi/scope.h +++ b/src/gn/ffi/scope.h
@@ -9,6 +9,7 @@ #include "cxx.h" +struct ParseNodePtr; class Scope; class Settings; struct SliceAny; @@ -44,4 +45,7 @@ // Returns a pointer to the value in the scope or nullptr if not found. const Value* GetValue(const Scope& scope, rust::Str ident); +// Adds a value slot to the scope under `ident` and returns a reference to it. +Value& SetValue(Scope& scope, rust::Str ident, ParseNodePtr origin); + #endif // TOOLS_GN_FFI_SCOPE_H_
diff --git a/src/gn/ffi/session.cc b/src/gn/ffi/session.cc new file mode 100644 index 0000000..b2329bf --- /dev/null +++ b/src/gn/ffi/session.cc
@@ -0,0 +1,78 @@ +// 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. + +#include "gn/ffi/session.h" + +#include <vector> + +#include "gn/build_settings.h" +#include "gn/err.h" +#include "gn/ffi/bridge.h" +#include "gn/functions.h" +#include "gn/parse_tree.h" +#include "gn/scope.h" +#include "gn/source_dir.h" +#include "gn/tokenizer.h" + +struct Session; + +namespace { + +bool IsValidIdentifier(std::string_view str) { + if (str.empty()) + return false; + if (!Tokenizer::IsIdentifierFirstChar(str[0])) + return false; + for (char c : str.substr(1)) { + if (!Tokenizer::IsIdentifierContinuingChar(c)) + return false; + } + return true; +} + +} // namespace + +bool session_load(const Session& session, + const Value& label, + std::span<const Value> keys, + Scope& dest_scope, + ParseNodePtr parse_node, + Err& err) { + if (label.type() != Value::STRING) { + err = Err(label.origin(), "Invalid load path.", + "First argument to load must be a string corresponding to the " + "label for the file to load."); + return false; + } + std::string_view label_str = label.string_value(); + if (!label_str.ends_with(".scl")) { + err = Err(label.origin(), "The file to load must be a '.scl' file."); + return false; + } + + std::vector<rust::Str> keys_slice; + keys_slice.reserve(keys.size()); + for (const auto& key : keys) { + if (key.type() != Value::STRING) { + err = Err(key.origin(), "Invalid variable to load.", + "Arguments to load must be strings."); + return false; + } + std::string_view val_str = key.string_value(); + if (!IsValidIdentifier(val_str)) { + err = Err(key.origin(), "Invalid variable to load.", + "Arguments to load must be valid identifiers."); + return false; + } + keys_slice.push_back(rust::Str(StringAtom(val_str).str())); + } + + session.load_values( + rust::Str(label.string_value()), + rust::Str(dest_scope.GetSourceDir().SourceWithNoTrailingSlash()), + rust::Slice<const rust::Str>(keys_slice), dest_scope, + *dest_scope.settings(), parse_node, err); + + return !err.has_error(); +}
diff --git a/src/gn/ffi/session.h b/src/gn/ffi/session.h new file mode 100644 index 0000000..75839c2 --- /dev/null +++ b/src/gn/ffi/session.h
@@ -0,0 +1,38 @@ +// 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. + +#ifndef TOOLS_GN_FFI_SESSION_H_ +#define TOOLS_GN_FFI_SESSION_H_ + +#include <span> + +class Err; +class ParseNode; +struct Session; +class Scope; +class Value; + +struct ParseNodePtr; + +// Executes the starlark file and loads the requested variables. +// Example: session_load( +// session, +// Value(":foo.scl"), +// {"foo", "bar"} +// scope for //dir/BUILD.gn, +// ParseNodePtr{parse_node}, +// err +// ) +// +// In this example, we execute the file //dir/foo.scl. +// We then get the variables foo and bar and insert them into the dest_scope. +// Unlike `import`, all imported variables *must* be used. +bool session_load(const Session& session, + const Value& label, + std::span<const Value> keys, + Scope& dest_scope, + ParseNodePtr parse_node, + Err& err); + +#endif // TOOLS_GN_FFI_SESSION_H_ \ No newline at end of file
diff --git a/src/gn/ffi/session_unittest.cc b/src/gn/ffi/session_unittest.cc new file mode 100644 index 0000000..44db44f --- /dev/null +++ b/src/gn/ffi/session_unittest.cc
@@ -0,0 +1,35 @@ +// 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. + +#include "gn/ffi/session.h" + +#include "gn/ffi/bridge.h" +#include "gn/scope.h" +#include "util/test/test.h" + +TEST(SessionTest, SessionLoad) { + TestWithScope setup; + rust::Box<Session> session = Session::new_for_testing(); + + std::vector<Value> keys = {Value(nullptr, "absolute_value"), + Value(nullptr, "relative_value")}; + + setup.scope()->set_source_dir(SourceDir("//load/")); + Err err; + bool success = + session_load(*session, Value(nullptr, ":root.scl"), keys, *setup.scope(), + ParseNodePtr{.ptr = nullptr}, err); + EXPECT_TRUE(success); + EXPECT_FALSE(err.has_error()); + + const Value* absolute_val = setup.scope()->GetValue("absolute_value"); + ASSERT_TRUE(absolute_val); + EXPECT_EQ(absolute_val->type(), Value::STRING); + EXPECT_EQ(absolute_val->string_value(), "absolute"); + + const Value* relative_val = setup.scope()->GetValue("relative_value"); + ASSERT_TRUE(relative_val); + EXPECT_EQ(relative_val->type(), Value::STRING); + EXPECT_EQ(relative_val->string_value(), "relative"); +}
diff --git a/src/gn/range_utils.h b/src/gn/range_utils.h new file mode 100644 index 0000000..ef4e140 --- /dev/null +++ b/src/gn/range_utils.h
@@ -0,0 +1,42 @@ +// 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. + +#ifndef TOOLS_GN_RANGE_UTILS_H_ +#define TOOLS_GN_RANGE_UTILS_H_ + +#include <concepts> +#include <ranges> +#include <vector> + +template <typename R, typename T> +concept RangeOf = std::ranges::input_range<R> && + std::convertible_to<std::ranges::range_reference_t<R>, T>; + +// std::ranges::to<std::vector> should be preferred, but isn't available on the +// older versions of mac used in CI. +template <std::ranges::input_range R> +auto to_vec(R&& range) { + using ValType = std::ranges::range_value_t<R>; + // For common ranges (where begin() and end() return the same type, such as + // std::vector or simple views), we can use std::vector's iterator + // constructor. + if constexpr (std::ranges::common_range<R>) { + return std::vector<ValType>(range.begin(), range.end()); + } else { + // For non-common ranges (where begin() and end() have different types, such + // as lazy split views or generators using sentinels), std::vector's + // iterator constructor will fail to compile. We must use a range-based for + // loop, which natively supports sentinel comparisons of different types. + std::vector<ValType> vec; + if constexpr (std::ranges::sized_range<R>) { + vec.reserve(std::ranges::size(range)); + } + for (auto&& item : range) { + vec.push_back(std::forward<decltype(item)>(item)); + } + return vec; + } +} + +#endif // TOOLS_GN_RANGE_UTILS_H_
diff --git a/src/gn/range_utils_unittest.cc b/src/gn/range_utils_unittest.cc new file mode 100644 index 0000000..1437ff9 --- /dev/null +++ b/src/gn/range_utils_unittest.cc
@@ -0,0 +1,24 @@ +// 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. + +#include "gn/range_utils.h" + +#include <ranges> +#include <vector> + +#include "util/test/test.h" + +TEST(RangeUtilsTest, ToVec) { + // Test converting a common range (like a transformed vector) to vector. + std::vector<int> input = {1, 2, 3, 4, 5}; + std::vector<int> output = + to_vec(input | std::views::transform([](int i) { return i + 1; })); + ASSERT_EQ(output, (std::vector<int>{2, 3, 4, 5, 6})); + + // Test converting a non-common range (like a take_view of an infinite + // iota_view) to vector. + std::vector<int> output_non_common = + to_vec(std::views::take(std::views::iota(0), 5)); + ASSERT_EQ(output_non_common, (std::vector<int>{0, 1, 2, 3, 4})); +}
diff --git a/src/gn/starlark/Cargo.lock b/src/gn/starlark/Cargo.lock index b4c12dc..c8e078a 100644 --- a/src/gn/starlark/Cargo.lock +++ b/src/gn/starlark/Cargo.lock
@@ -671,7 +671,14 @@ name = "ffi" version = "0.1.0" dependencies = [ + "allocative", + "anyhow", + "attr", "cxx", + "depset", + "loader", + "providers", + "rule", "starlark", "starlark_derive", "thiserror", @@ -739,6 +746,9 @@ [[package]] name = "gn_starlark" version = "0.1.0" +dependencies = [ + "ffi", +] [[package]] name = "hash32"
diff --git a/src/gn/starlark/Cargo.toml b/src/gn/starlark/Cargo.toml index f534d77..c16cdfd 100644 --- a/src/gn/starlark/Cargo.toml +++ b/src/gn/starlark/Cargo.toml
@@ -10,6 +10,9 @@ crate-type = ["staticlib"] test = false +[dependencies] +ffi = { path = "crates/ffi" } + [workspace] members = [ ".",
diff --git a/src/gn/starlark/crates/ffi/Cargo.toml b/src/gn/starlark/crates/ffi/Cargo.toml index c740bf3..c9e0211 100644 --- a/src/gn/starlark/crates/ffi/Cargo.toml +++ b/src/gn/starlark/crates/ffi/Cargo.toml
@@ -10,8 +10,15 @@ doctest = false [dependencies] +allocative = { workspace = true } +anyhow = { workspace = true } +attr = { path = "../attr" } +cxx = { workspace = true } +depset = { path = "../depset" } +loader = { path = "../loader" } +providers = { path = "../providers" } +rule = { path = "../rule" } starlark = { workspace = true } starlark_derive = { workspace = true } -cxx = { workspace = true } -types = { path = "../types" } thiserror = { workspace = true } +types = { path = "../types" } \ No newline at end of file
diff --git a/src/gn/starlark/crates/ffi/src/bridge.rs b/src/gn/starlark/crates/ffi/src/bridge.rs index 396ed42..95fabcd 100644 --- a/src/gn/starlark/crates/ffi/src/bridge.rs +++ b/src/gn/starlark/crates/ffi/src/bridge.rs
@@ -12,7 +12,12 @@ /// * This allows for C++ code to #include rust types /// * The `cxxbridge` command generates shims to allow us to use C++ types in /// rust. +use crate::session::Session; + #[cxx::bridge] +// Allow let_underscore_drop because the cxx::bridge generated code has non-binding +// lets on C++ types with destructors. +#[allow(let_underscore_drop)] // CxxBridge requires a module, but we don't want one. So we make a private one // and re-export all fields. mod dummy { @@ -132,6 +137,11 @@ // Returns an OwnedSlice<KeyValue> corresponding to references to each element. pub(in crate::scope) fn GetScopeItems(scope: &Scope) -> SliceAny; pub(in crate::scope) fn GetValue(scope: &Scope, ident: &str) -> *const Value; + pub(in crate::scope) fn SetValue<'a>( + scope: Pin<&'a mut Scope>, + ident: &str, + origin: ParseNodePtr, + ) -> Pin<&'a mut Value>; #[rust_name = "settings_cxx"] pub(in crate::scope) fn settings(self: &Scope) -> *const Settings; @@ -175,6 +185,28 @@ scope: UniquePtr<Scope>, ); } + + extern "Rust" { + type Session; + + #[Self = "Session"] + #[cxx_name = "new_cxx"] + fn new(source_root: &str, source_root_rel: &str) -> Box<Session>; + + #[Self = "Session"] + fn new_for_testing() -> Box<Session>; + + fn load_values( + self: &'static Session, + label: &str, + relative_to: &str, + keys: &[&str], + scope: Pin<&mut Scope>, + settings: &Settings, + origin: ParseNodePtr, + err: Pin<&mut Err>, + ); + } } pub use dummy::*;
diff --git a/src/gn/starlark/crates/ffi/src/errors.rs b/src/gn/starlark/crates/ffi/src/errors.rs new file mode 100644 index 0000000..d76cb6d --- /dev/null +++ b/src/gn/starlark/crates/ffi/src/errors.rs
@@ -0,0 +1,18 @@ +// 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 types::Label; + +/// Errors returned by the FFI layer. +#[derive(thiserror::Error, Debug)] +pub(crate) enum Error { + #[error("Key '{0}' not found in module '{1}'")] + KeyNotFound(String, Label), +} + +impl From<Error> for starlark::Error { + fn from(err: Error) -> Self { + Self::new_other(err) + } +}
diff --git a/src/gn/starlark/crates/ffi/src/eval_context.rs b/src/gn/starlark/crates/ffi/src/eval_context.rs new file mode 100644 index 0000000..e514876 --- /dev/null +++ b/src/gn/starlark/crates/ffi/src/eval_context.rs
@@ -0,0 +1,82 @@ +// 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 allocative::Allocative; +use starlark::values::ProvidesStaticType; +use types::{LabelRef, PackageRef, PathResolver}; + +#[derive(Allocative)] +enum EvalContextKind { + BzlFile, +} + +#[derive(Allocative, ProvidesStaticType)] +pub struct EvalContext { + #[allocative(skip)] + session: &'static crate::session::Session, + #[allocative(skip)] + package: &'static PackageRef, + kind: EvalContextKind, +} + +impl EvalContext { + pub fn new_bzl_file( + session: &'static crate::session::Session, + package: &'static PackageRef, + ) -> Self { + Self { + session, + package, + kind: EvalContextKind::BzlFile, + } + } +} + +impl types::EvalContext for EvalContext { + type Scope = crate::scope::OwnedScope; + type Session = crate::session::Session; + + fn current_package(&self) -> &types::PackageRef { + self.package + } + + fn path_resolver(&self) -> &PathResolver { + &self.session.path_resolver + } + + fn session(&self) -> &Self::Session { + self.session + } + + fn current_toolchain(&self) -> LabelRef<'_> { + todo!() + } + + fn require_macro(&self) -> starlark::Result<&Self::Scope> { + todo!() + } + + fn require_bzl(&self) -> starlark::Result<()> { + todo!() + } + + fn require_rule_impl( + &self, + ) -> starlark::Result<&mut types::CtxState<crate::target_ref::TargetRef>> { + todo!() + } +} + +impl attr::traits::EvalContextAttrExt for EvalContext { + fn create_target( + &self, + _target_type: Option<types::OutputType>, + _target_name: &str, + _scope: &Self::Scope, + _rule: starlark::values::FrozenValue, + _attrs: Vec<attr::Attr>, + ) -> starlark::Result<<Self::Session as types::Session>::TargetRef> { + todo!() + } +}
diff --git a/src/gn/starlark/crates/ffi/src/lib.rs b/src/gn/starlark/crates/ffi/src/lib.rs index a27dbbd..2edea21 100644 --- a/src/gn/starlark/crates/ffi/src/lib.rs +++ b/src/gn/starlark/crates/ffi/src/lib.rs
@@ -18,13 +18,17 @@ //! these types in their own files. mod bridge; mod err; +mod errors; +mod eval_context; mod label; mod mutability; mod opaque; mod output_file; mod scope; +mod session; mod settings; mod slice; +mod target_ref; mod test_with_scope; mod value; @@ -32,5 +36,6 @@ pub use mutability::Immutable; pub use opaque::{NonOpaque, OpaqueSized}; pub use scope::OwnedScope; +pub use session::Session; pub use slice::{OwnedSlice, Slice}; pub use test_with_scope::TestWithScope;
diff --git a/src/gn/starlark/crates/ffi/src/session.rs b/src/gn/starlark/crates/ffi/src/session.rs new file mode 100644 index 0000000..a69d2b1 --- /dev/null +++ b/src/gn/starlark/crates/ffi/src/session.rs
@@ -0,0 +1,123 @@ +// 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::pin::Pin; + +use loader::FileLoader; +use starlark::environment::{FrozenModule, Globals}; +use types::{ + util::extend_lifetime, Label, LabelRef, PackageRef, PathResolver, Session as TypesSession, +}; + +use crate::errors::Error; + +/// Represents a Starlark evaluation session exposed to C++ via FFI. +pub struct Session { + loader: FileLoader, + pub(crate) path_resolver: PathResolver, + globals: Globals, +} + +fn make_attr_schema<'v>( + kind: attr::AttrKind, + args: attr::AttrSpecArgs<'v>, + eval: &mut starlark::eval::Evaluator<'v, '_, '_>, +) -> starlark::Result<starlark::values::Value<'v>> { + use types::{EvalContext as _, EvaluatorContextExt as _}; + let ctx = eval.context::<crate::eval_context::EvalContext>(); + attr::AttrSchema::create( + kind, + args, + ctx.current_package(), + ctx.path_resolver(), + &eval.heap(), + ) +} + +fn build_globals() -> Globals { + let mut builder = starlark::environment::GlobalsBuilder::standard(); + builder.set("attr", attr::AttrModule { make_attr_schema }); + providers::globals::register_providers(&mut builder); + depset::depset_globals!(&mut builder, crate::eval_context::EvalContext); + rule::register_rule_globals!(&mut builder, crate::eval_context::EvalContext); + builder.build() +} + +impl Session { + /// Creates a new `Session`. + pub fn from_resolver(path_resolver: PathResolver) -> Self { + Self { + loader: FileLoader::default(), + path_resolver, + globals: build_globals(), + } + } + + /// Associated function for C++ constructor. + pub fn new(source_root: &str, source_root_rel: &str) -> Box<Self> { + Box::new(Self::from_resolver(PathResolver::new( + std::path::PathBuf::from(source_root), + source_root_rel.to_owned(), + ))) + } + + /// Associated function for C++ constructor. + pub fn new_for_testing() -> Box<Self> { + Box::new(Self::from_resolver(PathResolver::new_for_testing())) + } + + fn load(&'static self, label: LabelRef<'_>) -> starlark::Result<FrozenModule> { + self.loader + .load(label, &self.path_resolver, &self.globals, &|pkg| { + // Safety: The package reference is guaranteed to live as long as the + // eval context. + crate::eval_context::EvalContext::new_bzl_file(self, unsafe { + extend_lifetime(pkg) + }) + }) + } + + /// Loads a Starlark module and populates a scope with values by key. + pub fn load_values( + &'static self, + label: &str, + relative_to: &str, + keys: &[&str], + mut scope: Pin<&mut crate::bridge::Scope>, + settings: &crate::Settings, + origin: crate::bridge::ParseNodePtr, + mut err: Pin<&mut crate::bridge::Err>, + ) { + err.as_mut().handle((|| -> starlark::Result<()> { + let label = Label::parse(label, PackageRef::new(relative_to)?)?; + let module = self.load(label.as_ref())?; + + for key in keys { + let value = module + .get(key) + .map_err(|_| Error::KeyNotFound(key.to_string(), label.clone()))?; + let mut cxx_value = crate::bridge::SetValue(scope.as_mut(), key, origin); + cxx_value.as_mut().assign(value.value(), settings, origin); + } + Ok(()) + })()); + } +} + +impl TypesSession for Session { + type TargetRef = crate::target_ref::TargetRef; + + fn get_target(&self, _label: LabelRef<'_>, _toolchain: LabelRef<'_>) -> Self::TargetRef { + todo!() + } + + fn register_dependency<'a>( + &self, + _source: Self::TargetRef, + _label: LabelRef<'a>, + _toolchain: LabelRef<'a>, + ) { + todo!() + } +}
diff --git a/src/gn/starlark/crates/ffi/src/target_ref.rs b/src/gn/starlark/crates/ffi/src/target_ref.rs new file mode 100644 index 0000000..08a4db0 --- /dev/null +++ b/src/gn/starlark/crates/ffi/src/target_ref.rs
@@ -0,0 +1,65 @@ +// 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 allocative::Allocative; +use starlark::values::{AllocValue, Heap, ProvidesStaticType, StarlarkValue, Value}; +use starlark_derive::{starlark_value, NoSerialize}; +use types::LabelRef; + +#[derive(Clone, Allocative, ProvidesStaticType, Debug, NoSerialize, PartialEq, Eq, Hash)] +pub struct TargetRef; + +impl std::fmt::Display for TargetRef { + fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } +} + +impl types::IPromiseToImplementStarlarkEqAndHash for TargetRef {} + +#[starlark_value(type = "Target")] +impl<'v> StarlarkValue<'v> for TargetRef { + fn equals(&self, _other: Value<'v>) -> starlark::Result<bool> { + todo!() + } + + fn write_hash( + &self, + _hasher: &mut starlark::collections::StarlarkHasher, + ) -> starlark::Result<()> { + todo!() + } +} + +impl<'v> AllocValue<'v> for TargetRef { + fn alloc_value(self, heap: Heap<'v>) -> Value<'v> { + heap.alloc_simple(self) + } +} + +impl types::TargetRef for TargetRef { + fn label(&self) -> LabelRef<'_> { + todo!() + } + + fn toolchain(&self) -> LabelRef<'_> { + todo!() + } + + fn outputs(&self) -> Vec<types::File> { + todo!() + } + + fn target_out_dir(&self, _prefix: &str, _suffix: &str, _separator: &str) -> String { + todo!() + } + + fn register_dependencies<S: types::Session<TargetRef = Self>>( + &self, + _session: &S, + _toolchain: LabelRef<'_>, + ) { + todo!() + } +}
diff --git a/src/gn/starlark/src/lib.rs b/src/gn/starlark/src/lib.rs index b1216cc..99da268 100644 --- a/src/gn/starlark/src/lib.rs +++ b/src/gn/starlark/src/lib.rs
@@ -1,3 +1,4 @@ // 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. +pub use ffi::*;