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[];