Reland "Adds a path_exists() function" This reverts commit c6f1e69551890e28ab70da68b398408d9f2b4b48. Reason for reland: Fixed kPathExists Original change's description: > Revert "Adds a path_exists() function" > > This reverts commit 68b72c326a153f574f289099f77d85a92389d492. > > Reason for revert: Didn't update value of kPathExists > > Original change's description: > > Adds a path_exists() function > > > > Useful for conditionally depending on deps, conditionally importing a > > .gni, or setting default declare_args() values. > > > > Bug: chromium:397994249 > > Change-Id: I61ba0b3e370502511ebdc5e2a07786fe389824b8 > > Reviewed-on: https://gn-review.googlesource.com/c/gn/+/18200 > > Reviewed-by: Dirk Pranke <dpranke@google.com> > > Commit-Queue: Andrew Grieve <agrieve@google.com> > > TBR=dpranke@google.com,agrieve@google.com,gn-scoped@luci-project-accounts.iam.gserviceaccount.com > > Change-Id: If709fa6ca15c6076a20bd10334326d32016acae1 > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Bug: chromium:397994249 > Reviewed-on: https://gn-review.googlesource.com/c/gn/+/18240 > Reviewed-by: Andrew Grieve <agrieve@google.com> > Commit-Queue: Dirk Pranke <dpranke@google.com> > Reviewed-by: Dirk Pranke <dpranke@google.com> # Not skipping CQ checks because this is a reland. Bug: chromium:397994249 Change-Id: Ie9eb9c513b1ca323bb90b8915af0dc82d0ae356b Reviewed-on: https://gn-review.googlesource.com/c/gn/+/18260 Reviewed-by: Dirk Pranke <dpranke@google.com> Commit-Queue: Andrew Grieve <agrieve@google.com>
diff --git a/build/gen.py b/build/gen.py index d1c649c..cdee2f5 100755 --- a/build/gen.py +++ b/build/gen.py
@@ -684,6 +684,7 @@ 'src/gn/function_get_path_info.cc', 'src/gn/function_get_target_outputs.cc', 'src/gn/function_label_matches.cc', + 'src/gn/function_path_exists.cc', 'src/gn/function_process_file_template.cc', 'src/gn/function_read_file.cc', 'src/gn/function_rebase_path.cc', @@ -822,6 +823,7 @@ 'src/gn/function_get_path_info_unittest.cc', 'src/gn/function_get_target_outputs_unittest.cc', 'src/gn/function_label_matches_unittest.cc', + 'src/gn/function_path_exists_unittest.cc', 'src/gn/function_process_file_template_unittest.cc', 'src/gn/function_rebase_path_unittest.cc', 'src/gn/function_template_unittest.cc',
diff --git a/docs/reference.md b/docs/reference.md index 56c554f..d138138 100644 --- a/docs/reference.md +++ b/docs/reference.md
@@ -54,6 +54,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) * [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) * [print: Prints to the console.](#func_print) * [print_stack_trace: Prints a stack trace.](#func_print_stack_trace) @@ -3066,6 +3067,23 @@ not_needed(invoker, "*", [ "config" ]) not_needed(invoker, [ "data_deps", "deps" ]) ``` + +### <a name="func_path_exists"></a>**path_exists**: Returns whether the given path exists. + +``` + path_exists(path) + + The argument is a path to a file or directory. +``` + +#### **Example** + +``` + path_exists("//") # true + path_exists("BUILD.gn") # true + path_exists("/abs-non-existent") # false +``` + ### <a name="func_pool"></a>**pool**: Defines a pool object. [Back to Top](#gn-reference) ```
diff --git a/src/gn/function_path_exists.cc b/src/gn/function_path_exists.cc new file mode 100644 index 0000000..a858961 --- /dev/null +++ b/src/gn/function_path_exists.cc
@@ -0,0 +1,71 @@ +// Copyright 2025 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 <stddef.h> + +#include "base/files/file_util.h" +#include "gn/build_settings.h" +#include "gn/err.h" +#include "gn/functions.h" +#include "gn/parse_tree.h" +#include "gn/settings.h" +#include "gn/value.h" + +namespace functions { + +Value RunPathExists(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err) { + Value result; + + if (args.size() != 1) { + *err = Err(function->function(), "Expecting exactly one argument."); + return result; + } + const Value& value = args[0]; + if (!value.VerifyTypeIs(Value::STRING, err)) { + return result; + } + + const std::string& input_string = value.string_value(); + const SourceDir& cur_dir = scope->GetSourceDir(); + bool as_dir = + !input_string.empty() && input_string[input_string.size() - 1] == '/'; + + base::FilePath system_path; + if (as_dir) { + system_path = scope->settings()->build_settings()->GetFullPath( + cur_dir.ResolveRelativeDir( + value, err, + scope->settings()->build_settings()->root_path_utf8())); + } else { + system_path = scope->settings()->build_settings()->GetFullPath( + cur_dir.ResolveRelativeFile( + value, err, + scope->settings()->build_settings()->root_path_utf8())); + } + if (err->has_error()) { + return value; + } + + bool exists = PathExists(system_path); + return Value(function, exists); +} + +const char kPathExists[] = "path_exists"; +const char kPathExists_HelpShort[] = + "path_exists: Returns whether the given path exists."; +const char kPathExists_Help[] = + R"(path_exists: Returns whether the given path exists. + + path_exists(path) + +Examples: + path_exists("//") # true + path_exists("BUILD.gn") # true + path_exists("/abs-non-existent") # false +)"; + +} // namespace functions
diff --git a/src/gn/function_path_exists_unittest.cc b/src/gn/function_path_exists_unittest.cc new file mode 100644 index 0000000..516f551 --- /dev/null +++ b/src/gn/function_path_exists_unittest.cc
@@ -0,0 +1,87 @@ +// Copyright 2025 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 "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "gn/functions.h" +#include "gn/test_with_scope.h" +#include "util/build_config.h" +#include "util/test/test.h" + +namespace { +bool RunPathExists(Scope* scope, const std::string& path) { + Err err; + std::vector<Value> args; + args.push_back(Value(nullptr, path)); + + FunctionCallNode function_call; + Value result = functions::RunPathExists(scope, &function_call, args, &err); + EXPECT_FALSE(err.has_error()); + return !err.has_error() && result.boolean_value(); +} +} // namespace + +TEST(PathExistsTest, FileExists) { + TestWithScope setup; + setup.scope()->set_source_dir(SourceDir("//some-dir/")); + + // Make a real directory for the test. + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + setup.build_settings()->SetRootPath(temp_dir.GetPath()); + + std::string data = "foo"; + base::FilePath dir_path = temp_dir.GetPath().AppendASCII("some-dir"); + base::CreateDirectory(dir_path); + base::FilePath file_path = dir_path.AppendASCII("foo.txt"); + base::WriteFile(file_path, data.c_str(), static_cast<int>(data.size())); + + EXPECT_TRUE(RunPathExists(setup.scope(), "//")); + EXPECT_TRUE(RunPathExists(setup.scope(), "//some-dir")); + EXPECT_TRUE(RunPathExists(setup.scope(), "//some-dir/")); + EXPECT_TRUE(RunPathExists(setup.scope(), "../some-dir")); + EXPECT_TRUE(RunPathExists(setup.scope(), "//some-dir/foo.txt")); + EXPECT_TRUE(RunPathExists(setup.scope(), temp_dir.GetPath().As8Bit())); + EXPECT_FALSE(RunPathExists(setup.scope(), "//bar")); + EXPECT_FALSE(RunPathExists(setup.scope(), "bar")); +} + +TEST(PathExistsTest, FileExistsInvalidValues) { + TestWithScope setup; + FunctionCallNode function_call; + + { + // No arg. + Err err; + std::vector<Value> args; + functions::RunPathExists(setup.scope(), &function_call, args, &err); + EXPECT_TRUE(err.has_error()); + } + { + // Extra arg. + Err err; + std::vector<Value> args; + args.push_back(Value(nullptr, "a")); + args.push_back(Value(nullptr, "b")); + functions::RunPathExists(setup.scope(), &function_call, args, &err); + EXPECT_TRUE(err.has_error()); + } + { + // Wrong type. + Err err; + std::vector<Value> args; + args.push_back(Value(nullptr, Value::LIST)); + functions::RunPathExists(setup.scope(), &function_call, args, &err); + EXPECT_TRUE(err.has_error()); + } + { + // Empty string. + Err err; + std::vector<Value> args; + args.push_back(Value(nullptr, "")); + functions::RunPathExists(setup.scope(), &function_call, args, &err); + EXPECT_TRUE(err.has_error()); + } +} +
diff --git a/src/gn/functions.cc b/src/gn/functions.cc index 6642768..a12b078 100644 --- a/src/gn/functions.cc +++ b/src/gn/functions.cc
@@ -1476,6 +1476,7 @@ INSERT_FUNCTION(Import, false) INSERT_FUNCTION(LabelMatches, false) INSERT_FUNCTION(NotNeeded, false) + INSERT_FUNCTION(PathExists, false) INSERT_FUNCTION(Pool, false) INSERT_FUNCTION(Print, false) INSERT_FUNCTION(PrintStackTrace, false)
diff --git a/src/gn/functions.h b/src/gn/functions.h index 5cfeb82..5d8fdac 100644 --- a/src/gn/functions.h +++ b/src/gn/functions.h
@@ -277,6 +277,14 @@ const ListNode* args_list, Err* err); +extern const char kPathExists[]; +extern const char kPathExists_HelpShort[]; +extern const char kPathExists_Help[]; +Value RunPathExists(Scope* scope, + const FunctionCallNode* function, + const std::vector<Value>& args, + Err* err); + extern const char kPool[]; extern const char kPool_HelpShort[]; extern const char kPool_Help[];