#include "gn/label_pattern.h"
#include <stddef.h>
#include "base/strings/string_util.h"
#include "gn/err.h"
#include "gn/filesystem_utils.h"
#include "gn/value.h"
#include "util/build_config.h"
const char kLabelPattern_Help[] =
R"*(Label patterns
A label pattern is a way of expressing one or more labels in a portion of the
source tree. They are not general regular expressions.
They can take the following forms only:
- Explicit (no wildcard):
"//foo/bar:baz"
":baz"
- Wildcard target names:
"//foo/bar:*" (all targets in the //foo/bar/BUILD.gn file)
":*" (all targets in the current build file)
- Wildcard directory names ("*" is only supported at the end)
"*" (all targets)
"//foo/bar/*" (all targets in any subdir of //foo/bar)
"./*" (all targets in the current build file or sub dirs)
Any of the above forms can additionally take an explicit toolchain
in parenthesis at the end of the label pattern. In this case, the
toolchain must be fully qualified (no wildcards are supported in the
toolchain name).
"//foo:bar(//build/toolchain:mac)"
An explicit target in an explicit toolchain.
":*(//build/toolchain/linux:32bit)"
All targets in the current build file using the 32-bit Linux toolchain.
"//foo/*(//build/toolchain:win)"
All targets in //foo and any subdirectory using the Windows
toolchain.
)*";
LabelPattern::LabelPattern() : type_(MATCH) {}
LabelPattern::LabelPattern(Type type,
const SourceDir& dir,
std::string_view name,
const Label& toolchain_label)
: toolchain_(toolchain_label), type_(type), dir_(dir), name_(name) {}
LabelPattern::LabelPattern(const LabelPattern& other) = default;
LabelPattern::~LabelPattern() = default;
LabelPattern LabelPattern::GetPattern(const SourceDir& current_dir,
std::string_view source_root,
const Value& value,
Err* err) {
if (!value.VerifyTypeIs(Value::STRING, err))
return LabelPattern();
std::string_view str(value.string_value());
if (str.empty()) {
*err = Err(value, "Label pattern must not be empty.");
return LabelPattern();
}
size_t star = str.find('*');
if (star == std::string::npos) {
Label label = Label::Resolve(current_dir, source_root, Label(), value, err);
if (err->has_error())
return LabelPattern();
Label toolchain_label;
if (!label.toolchain_dir().is_null() || !label.toolchain_name().empty())
toolchain_label = label.GetToolchainLabel();
return LabelPattern(MATCH, label.dir(), label.name(), toolchain_label);
}
Label toolchain_label;
size_t open_paren = str.find('(');
if (open_paren != std::string::npos) {
size_t close_paren = str.find(')', open_paren);
if (close_paren == std::string::npos) {
*err = Err(value, "No close paren when looking for toolchain name.");
return LabelPattern();
}
std::string toolchain_string(
str.substr(open_paren + 1, close_paren - open_paren - 1));
if (toolchain_string.find('*') != std::string::npos) {
*err = Err(value, "Can't have a wildcard in the toolchain.");
return LabelPattern();
}
Value value_for_toolchain(value.origin(), toolchain_string);
toolchain_label = Label::Resolve(current_dir, source_root, Label(),
value_for_toolchain, err);
if (err->has_error())
return LabelPattern();
str = str.substr(0, open_paren);
}
std::string_view path;
std::string_view name;
size_t offset = 0;
#if defined(OS_WIN)
if (IsPathAbsolute(str)) {
size_t drive_letter_pos = str[0] == '/' ? 1 : 0;
if (str.size() > drive_letter_pos + 2 && str[drive_letter_pos + 1] == ':' &&
IsSlash(str[drive_letter_pos + 2]) &&
base::IsAsciiAlpha(str[drive_letter_pos])) {
offset = drive_letter_pos + 2;
}
}
#endif
size_t colon = str.find(':', offset);
if (colon == std::string::npos) {
path = std::string_view(str);
} else {
path = str.substr(0, colon);
name = str.substr(colon + 1);
}
SourceDir dir;
bool has_path_star = false;
if (path.empty()) {
dir = current_dir;
} else if (path[path.size() - 1] == '*') {
has_path_star = true;
path = path.substr(0, path.size() - 1);
if (!path.empty() && path[path.size() - 1] != '/') {
*err =
Err(value, "'*' must match full directories in a label pattern.",
"You did \"foo*\" but this thing doesn't do general pattern\n"
"matching. Instead, you have to add a slash: \"foo/*\" to match\n"
"all targets in a directory hierarchy.");
return LabelPattern();
}
}
if (!path.empty()) {
if (path.find('*') != std::string_view::npos) {
*err = Err(value, "Label patterns only support wildcard suffixes.",
"The pattern contained a '*' that wasn't at the end.");
return LabelPattern();
}
dir = current_dir.ResolveRelativeDir(value, path, err, source_root);
if (err->has_error())
return LabelPattern();
}
if (colon != std::string::npos && name != "*") {
*err = Err(
value, "Invalid label pattern.",
"You seem to be using the wildcard more generally that is supported.\n"
"Did you mean \"foo:*\" to match everything in the file, or\n"
"\"./*\" to recursively match everything in the current subtree.");
return LabelPattern();
}
Type type;
if (has_path_star) {
type = RECURSIVE_DIRECTORY;
} else {
type = DIRECTORY;
}
return LabelPattern(type, dir, std::string_view(), toolchain_label);
}
bool LabelPattern::HasWildcard(const std::string& str) {
return str.find('*') != std::string::npos;
}
bool LabelPattern::Matches(const Label& label) const {
if (!toolchain_.is_null()) {
if (toolchain_.dir() != label.toolchain_dir() ||
toolchain_.name() != label.toolchain_name())
return false;
}
switch (type_) {
case MATCH:
return label.name() == name_ && label.dir() == dir_;
case DIRECTORY:
return label.dir() == dir_;
case RECURSIVE_DIRECTORY:
return label.dir().value().compare(0, dir_.value().size(),
dir_.value()) == 0;
default:
NOTREACHED();
return false;
}
}
bool LabelPattern::VectorMatches(const std::vector<LabelPattern>& patterns,
const Label& label) {
for (const auto& pattern : patterns) {
if (pattern.Matches(label))
return true;
}
return false;
}
std::string LabelPattern::Describe() const {
std::string result;
switch (type()) {
case MATCH:
result = DirectoryWithNoLastSlash(dir()) + ":" + name();
break;
case DIRECTORY:
result = DirectoryWithNoLastSlash(dir()) + ":*";
break;
case RECURSIVE_DIRECTORY:
result = dir().value() + "*";
break;
}
if (!toolchain_.is_null()) {
result.push_back('(');
result.append(toolchain_.GetUserVisibleName(false));
result.push_back(')');
}
return result;
}