Add `load` function to GN Bug: 528225104 Change-Id: I52784a4173e6b4ee6ee675255b95ee9e6a6a6964 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/24801 Commit-Queue: Matt Stark <msta@google.com> Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/build/gen.py b/build/gen.py index 4dd28e6..80ca7e9 100755 --- a/build/gen.py +++ b/build/gen.py
@@ -1153,6 +1153,7 @@ 'src/gn/ffi/session_unittest.cc', ]) executables['gn_unittests']['libs'].append('gn_starlark') + executables['gn']['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.
diff --git a/docs/reference.md b/docs/reference.md index 62eb514..8247c48 100644 --- a/docs/reference.md +++ b/docs/reference.md
@@ -56,6 +56,7 @@ * [import: Import a file into the current scope.](#func_import) * [label_matches: Returns whether a label matches any of a list of patterns.](#func_label_matches) * [len: Returns the length of a string or a list.](#func_len) + * [load: Load variables from a starlark file into the current scope.](#func_load) * [not_needed: Mark variables from scope as not needed.](#func_not_needed) * [path_exists: Returns whether the given path exists.](#func_path_exists) * [pool: Defines a pool object.](#func_pool) @@ -3138,6 +3139,34 @@ len("foo") # 3 len([ "a", "b", "c" ]) # 3 ``` +### <a name="func_load"></a>**load**: Load variables from a starlark file into the current scope. [Back to Top](#gn-reference) + +``` + The load command executes a Starlark (.scl) file in a standalone environment + and imports the specified symbols into the current scope. +``` + +#### **Arguments** + +``` + First argument: + A label to the Starlark (.scl) file to load. This label can be + absolute (e.g. "//path/to:rules.scl") or relative to the current directory + (e.g, ":rules.scl") + + Remaining arguments: + A list of string names of the symbols to load from the file. +``` + +#### **Example**: + +``` + load("//:rules.scl", "custom_rule", "MY_CONSTANT") + + custom_rule("a") { + foo = MY_CONSTANT + } +``` ### <a name="func_not_needed"></a>**not_needed**: Mark variables from scope as not needed. [Back to Top](#gn-reference) ```
diff --git a/src/gn/build_settings.cc b/src/gn/build_settings.cc index 6d66376..359a5a7 100644 --- a/src/gn/build_settings.cc +++ b/src/gn/build_settings.cc
@@ -7,6 +7,7 @@ #include <utility> #include "base/files/file_util.h" +#include "gn/ffi/bridge.h" #include "gn/filesystem_utils.h" BuildSettings::BuildSettings() = default; @@ -23,7 +24,12 @@ build_config_file_(other.build_config_file_), arg_file_template_path_(other.arg_file_template_path_), build_dir_(other.build_dir_), - build_args_(other.build_args_) {} + build_args_(other.build_args_) { + if (other.starlark_session_.has_value()) { + starlark_session_ = + Session::new_cxx(other.root_path_utf8_, other.build_dir_.value()); + } +} void BuildSettings::SetRootTargetLabel(const Label& r) { root_target_label_ = r; @@ -37,6 +43,9 @@ DCHECK(r.value()[r.value().size() - 1] != base::FilePath::kSeparators[0]); root_path_ = r.NormalizePathSeparatorsTo('/'); root_path_utf8_ = FilePathToUTF8(root_path_); + if (!root_path_.empty() && !build_dir_.is_null()) { + starlark_session_ = Session::new_cxx(root_path_utf8_, build_dir_.value()); + } } void BuildSettings::SetSecondarySourcePath(const SourceDir& d) { @@ -59,6 +68,9 @@ void BuildSettings::SetBuildDir(const SourceDir& d) { build_dir_ = d; + if (!root_path_.empty() && !build_dir_.is_null()) { + starlark_session_ = Session::new_cxx(root_path_utf8_, build_dir_.value()); + } } base::FilePath BuildSettings::GetFullPath(const SourceFile& file) const { @@ -100,4 +112,9 @@ auto temp = std::move(print_callback_); print_callback_ = callback; return temp; +} + +const Session& BuildSettings::starlark_session() const { + DCHECK(starlark_session_.has_value()); + return **starlark_session_; } \ No newline at end of file
diff --git a/src/gn/build_settings.h b/src/gn/build_settings.h index 0d4decb..c5c5808 100644 --- a/src/gn/build_settings.h +++ b/src/gn/build_settings.h
@@ -8,10 +8,12 @@ #include <functional> #include <map> #include <memory> +#include <optional> #include <set> #include <utility> #include "base/files/file_path.h" +#include "cxx.h" #include "gn/args.h" #include "gn/label.h" #include "gn/label_pattern.h" @@ -21,6 +23,7 @@ #include "gn/version.h" class Item; +struct Session; // Settings for one build, which is one toplevel output directory. There // may be multiple Settings objects that refer to this, one for each toolchain. @@ -153,6 +156,8 @@ expand_directory_allowlist_ = std::move(list); } + const Session& starlark_session() const; + private: Label root_target_label_; std::vector<LabelPattern> root_patterns_; @@ -179,6 +184,8 @@ std::unique_ptr<SourceFileSet> expand_directory_allowlist_ = std::make_unique<SourceFileSet>(); + std::optional<rust::Box<Session>> starlark_session_; + BuildSettings& operator=(const BuildSettings&) = delete; };
diff --git a/src/gn/functions.cc b/src/gn/functions.cc index 4650362..10b41ce 100644 --- a/src/gn/functions.cc +++ b/src/gn/functions.cc
@@ -17,6 +17,8 @@ #include "gn/config.h" #include "gn/config_values_generator.h" #include "gn/err.h" +#include "gn/ffi/bridge.h" +#include "gn/ffi/session.h" #include "gn/input_file.h" #include "gn/parse_node_value_adapter.h" #include "gn/parse_tree.h" @@ -693,6 +695,68 @@ return Value(); } +// load ----------------------------------------------------------------------- + +const char kLoad[] = "load"; +const char kLoad_HelpShort[] = + "load: Load variables from a starlark file into the current scope."; +const char kLoad_Help[] = + R"(load: Load variables from a starlark file into the current scope. + + The load command executes a Starlark (.scl) file in a standalone environment + and imports the specified symbols into the current scope. + +Arguments + + First argument: + A label to the Starlark (.scl) file to load. This label can be + absolute (e.g. "//path/to:rules.scl") or relative to the current directory + (e.g, ":rules.scl") + + Remaining arguments: + A list of string names of the symbols to load from the file. + +Example: + + load("//:rules.scl", "custom_rule", "MY_CONSTANT") + + custom_rule("a") { + foo = MY_CONSTANT + } +)"; + +Value RunLoad(Scope* scope, + const FunctionCallNode* function, + const ListNode* args_list, + Err* err) { + const std::vector<std::unique_ptr<const ParseNode>>& args = + args_list->contents(); + if (args.size() < 2) { + *err = Err(function->function(), "Incorrect arguments.", + "This function requires at least a file to import and a list of " + "variables to load."); + return Value(); + } + + std::vector<Value> values; + values.reserve(args.size()); + for (const auto& arg : args) { + Value val = arg->Execute(scope, err); + if (err->has_error()) { + return Value(); + } + values.push_back(std::move(val)); + } + + const ::Session& loader = + scope->settings()->build_settings()->starlark_session(); + + session_load(loader, values[0], std::span(values).subspan(1), *scope, + ParseNodePtr{function}, *err); + + return Value(); +} + // not_needed ----------------------------------------------------------------- const char kNotNeeded[] = "not_needed"; @@ -1536,6 +1600,7 @@ INSERT_FUNCTION(GetPathInfo, false) INSERT_FUNCTION(GetTargetOutputs, false) INSERT_FUNCTION(Import, false) + INSERT_FUNCTION(Load, false) INSERT_FUNCTION(LabelMatches, false) INSERT_FUNCTION(Len, false) INSERT_FUNCTION(NotNeeded, false)
diff --git a/src/gn/functions.h b/src/gn/functions.h index bf97b9e..f3b48e7 100644 --- a/src/gn/functions.h +++ b/src/gn/functions.h
@@ -260,6 +260,14 @@ const std::vector<Value>& args, Err* err); +extern const char kLoad[]; +extern const char kLoad_HelpShort[]; +extern const char kLoad_Help[]; +Value RunLoad(Scope* scope, + const FunctionCallNode* function, + const ListNode* args_list, + Err* err); + extern const char kLabelMatches[]; extern const char kLabelMatches_HelpShort[]; extern const char kLabelMatches_Help[];
diff --git a/src/gn/functions_unittest.cc b/src/gn/functions_unittest.cc index 6fc0e2f..1b7da65 100644 --- a/src/gn/functions_unittest.cc +++ b/src/gn/functions_unittest.cc
@@ -7,6 +7,10 @@ #include <memory> #include <utility> +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "gn/filesystem_utils.h" #include "gn/parse_tree.h" #include "gn/test_with_scope.h" #include "gn/value.h" @@ -709,3 +713,56 @@ " print_stack_trace() //test:6\n", setup.print_output()); } + +TEST(Functions, Load) { + TestWithScope setup; + setup.build_settings()->SetRootPath(UTF8ToFilePath(".")); + setup.scope()->set_source_dir(SourceDir("//")); + + // Verify that .bzl files return an error as they are not supported. + { + TestParseInput input(R"gn(load("//:wrong_extension.bzl", "a"))gn"); + ASSERT_SUCCESS(input); + Err err; + input.parsed()->Execute(setup.scope(), &err); + ASSERT_TRUE(err.has_error()); + ASSERT_EQ(err.message(), "The file to load must be a '.scl' file."); + } + + // Verify that an error is returned if the file doesn't exist. + { + TestParseInput input(R"gn(load("//:non_existent.scl", "a"))gn"); + ASSERT_SUCCESS(input); + Err err; + input.parsed()->Execute(setup.scope(), &err); + ASSERT_TRUE(err.has_error()); + ASSERT_EQ(err.message(), "Failed to read file: //:non_existent.scl"); + } + + // Verify that we can successfully load variables from a starlark file. + { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + setup.build_settings()->SetRootPath(temp_dir.GetPath()); + setup.scope()->set_source_dir(SourceDir("//")); + + std::string scl_content = R"scl( +a = "hello" +)scl"; + base::FilePath scl_path = temp_dir.GetPath().AppendASCII("rules.scl"); + ASSERT_EQ( + static_cast<int>(scl_content.size()), + base::WriteFile(scl_path, scl_content.c_str(), scl_content.size())); + + TestParseInput input(R"gn(load("//:rules.scl", "a"))gn"); + ASSERT_SUCCESS(input); + Err err; + input.parsed()->Execute(setup.scope(), &err); + ASSERT_FALSE(err.has_error()) << err.message(); + + const Value* val_a = setup.scope()->GetValue("a"); + ASSERT_TRUE(val_a); + EXPECT_EQ(Value::STRING, val_a->type()); + EXPECT_EQ("hello", val_a->string_value()); + } +}