Resolve suggest inputs relative to target include directories. When a file path passed to `gn suggest` cannot be resolved relative to the output or source root directories, fall back to treating it as a #include and attempt to resolve it relative to the include directories of the includer target. Bug: 500845363 Change-Id: If1bcb31e18eda51842f4d7b678fe81c56a6a6964 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/23000 Commit-Queue: Matt Stark <msta@google.com> Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/docs/reference.md b/docs/reference.md index 3012a11..5a509df 100644 --- a/docs/reference.md +++ b/docs/reference.md
@@ -1405,6 +1405,7 @@ * A module name (usually the same as the label) * A file path relative to the build directory * An absolute file path (eg. "//foo/bar.txt") + * A file path relative to the include directories of the includer target (treated as a #include) Eg. gn suggest out_dir path/to/target.cc=foo/bar.h
diff --git a/src/gn/command_suggest.cc b/src/gn/command_suggest.cc index f9f9f15..b7cfc4d 100644 --- a/src/gn/command_suggest.cc +++ b/src/gn/command_suggest.cc
@@ -15,6 +15,7 @@ #include "base/files/file_util.h" #include "base/strings/string_split.h" #include "gn/commands.h" +#include "gn/config_values_extractors.h" #include "gn/filesystem_utils.h" #include "gn/item.h" #include "gn/setup.h" @@ -36,6 +37,7 @@ * A module name (usually the same as the label) * A file path relative to the build directory * An absolute file path (eg. "//foo/bar.txt") + * A file path relative to the include directories of the includer target (treated as a #include) Eg. gn suggest out_dir path/to/target.cc=foo/bar.h @@ -116,7 +118,8 @@ SourceFile ResolveFilePath(const BuildSettings* build_settings, const std::vector<const Target*>& all_targets, - std::string_view input) { + std::string_view input, + const Target* includer = nullptr) { if (input.starts_with("//")) { SourceFile file = SourceFile(input); if (FileExists(all_targets, file, build_settings)) { @@ -124,16 +127,34 @@ } return SourceFile(); } + Value input_value(nullptr, std::string(input)); + // Resolve relative to the output directory. // This is because the user is most likely running this based on an error // message from clang, which gives paths relative to the output directory to // be unambiguous. Err err; - SourceFile file = build_settings->build_dir().ResolveRelativeFile( - Value(nullptr, std::string(input)), &err); + SourceFile file = + build_settings->build_dir().ResolveRelativeFile(input_value, &err); if (!err.has_error() && FileExists(all_targets, file, build_settings)) { return file; } + // If we are unable to resolve the file, we should treat it as a #include. + // Thus, iterate through the include directories of the includer target to + // attempt to find a source file that exists relative to those include dirs. + if (includer) { + for (ConfigValuesIterator iter(includer); !iter.done(); iter.Next()) { + for (const SourceDir& dir : iter.cur().include_dirs()) { + Err resolve_err; + SourceFile resolved_file = + dir.ResolveRelativeFile(input_value, &resolve_err); + if (!resolve_err.has_error() && + FileExists(all_targets, resolved_file, build_settings)) { + return resolved_file; + } + } + } + } return SourceFile(); } @@ -228,7 +249,8 @@ ResolveSuggestionToTarget(const BuildSettings* build_settings, const std::vector<const Target*>& all_targets, const Label& current_toolchain, - std::string_view input) { + std::string_view input, + const Target* includer) { auto sort_results = [](auto& vec) { std::sort(vec.begin(), vec.end(), [](const auto& lhs, const auto& rhs) { return lhs.first->label() < rhs.first->label(); @@ -273,7 +295,8 @@ } // If that doesn't work, try to resolve as a file path. - SourceFile file = ResolveFilePath(build_settings, all_targets, input); + SourceFile file = + ResolveFilePath(build_settings, all_targets, input, includer); if (file.is_null()) { return {results, false}; } @@ -378,9 +401,10 @@ } }; - auto ResolveSuggestion = [&](std::string_view value) { + auto ResolveSuggestion = [&](std::string_view value, + const Target* target_context = nullptr) { const auto& [targets, ok] = ResolveSuggestionToTarget( - build_settings, all_targets, current_toolchain, value); + build_settings, all_targets, current_toolchain, value, target_context); if (!ok) { StartError(); if (value.starts_with("//")) { @@ -389,7 +413,11 @@ } else { OutputString("Unable to find "); OutputQuoted(value); - OutputString(" in either the output or source root directories\n"); + if (target_context) { + OutputString(" in the output, source root, or include directories\n"); + } else { + OutputString(" in either the output or source root directories\n"); + } } } return std::make_pair(targets, ok); @@ -422,7 +450,7 @@ const char* dep_field = (dep_kind == commands::ApiScope::kPrivate) ? "deps" : "public_deps"; - const auto& [targets, ok] = ResolveSuggestion(included_name); + const auto& [targets, ok] = ResolveSuggestion(included_name, includer); if (!ok) return false;
diff --git a/src/gn/command_suggest_unittest.cc b/src/gn/command_suggest_unittest.cc index 28131b2..1d02934 100644 --- a/src/gn/command_suggest_unittest.cc +++ b/src/gn/command_suggest_unittest.cc
@@ -109,6 +109,9 @@ base::WriteFile(root_dir.AppendASCII("simple.h"), "", 0); base::WriteFile(root_dir.AppendASCII("default_toolchain.h"), "", 0); base::WriteFile(root_dir.AppendASCII("secondary_toolchain.h"), "", 0); + base::FilePath inc_dir = root_dir.AppendASCII("include_dir"); + ASSERT_TRUE(base::CreateDirectory(inc_dir)); + base::WriteFile(inc_dir.AppendASCII("my_header.h"), "", 0); Target explicit_target( setup_scope.settings(), @@ -156,6 +159,16 @@ Err resolve_err; ASSERT_TRUE(generated.OnResolvedWithoutChecks(&resolve_err)); + Target included_target( + setup_scope.settings(), + Label(SourceDir("//"), "included_target", current_toolchain.dir(), + current_toolchain.name())); + included_target.set_output_type(Target::SOURCE_SET); + included_target.SetToolchain(setup_scope.toolchain()); + included_target.set_all_headers_public(true); + included_target.sources().push_back(SourceFile("//include_dir/my_header.h")); + ASSERT_TRUE(included_target.OnResolvedWithoutChecks(&resolve_err)); + Target consumer(setup_scope.settings(), Label(SourceDir("//"), "consumer", current_toolchain.dir(), current_toolchain.name())); @@ -164,11 +177,13 @@ consumer.set_all_headers_public(true); consumer.public_headers().push_back( SourceFile("//out/Debug/generated_file.h")); + consumer.config_values().include_dirs().push_back( + SourceDir("//include_dir/")); ASSERT_TRUE(consumer.OnResolvedWithoutChecks(&resolve_err)); std::vector<const Target*> all_targets = {&explicit_target, &implicit_target, - &simple_default, &simple_secondary, - &generated}; + &simple_default, &simple_secondary, + &generated, &included_target}; { auto [results, ok] = commands::ResolveSuggestionToTarget( @@ -259,6 +274,16 @@ EXPECT_TRUE(ok); EXPECT_EQ(expected_targets, results); } + + { + auto [results, ok] = commands::ResolveSuggestionToTarget( + setup_scope.build_settings(), all_targets, current_toolchain, + "my_header.h", &consumer); + EXPECT_TRUE(ok); + std::vector<std::pair<const Target*, commands::ApiScope>> expected_targets = + {{{&included_target, commands::ApiScope::kPublic}}}; + EXPECT_EQ(expected_targets, results); + } } TEST(Suggest, OutputSuggestions) {
diff --git a/src/gn/commands.h b/src/gn/commands.h index c25ba85..d5b07ab 100644 --- a/src/gn/commands.h +++ b/src/gn/commands.h
@@ -286,7 +286,8 @@ ResolveSuggestionToTarget(const BuildSettings* build_settings, const std::vector<const Target*>& all_targets, const Label& current_toolchain, - std::string_view input); + std::string_view input, + const Target* includer = nullptr); // Resolves a vector of command line inputs and figures out the full set of // things they resolve to.