/* * Copyright 2012-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.facebook.buck.parser; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetPattern; import com.facebook.buck.model.ImmediateDirectoryBuildTargetPattern; import com.facebook.buck.model.SingletonBuildTargetPattern; import com.facebook.buck.model.SubdirectoryBuildTargetPattern; import com.facebook.buck.rules.CellPathResolver; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import java.nio.file.Path; import java.util.Optional; /** * Context for parsing build target names. Fully-qualified target names are parsed the same * regardless of the context. */ public abstract class BuildTargetPatternParser<T> { private static final String BUILD_RULE_PREFIX = "//"; private static final String WILDCARD_BUILD_RULE_SUFFIX = "..."; private static final String BUILD_RULE_SEPARATOR = ":"; private final String baseName; protected BuildTargetPatternParser(String baseName) { this.baseName = Preconditions.checkNotNull(baseName); } public String getBaseName() { return baseName; } protected boolean isWildCardAllowed() { return false; } /** * 1. //src/com/facebook/buck/cli:cli will be converted to a single build target 2. * //src/com/facebook/buck/cli: will match all in the same directory. 3. * //src/com/facebook/buck/cli/... will match all in or under that directory. For case 2 and 3, * parseContext is expected to be {@link BuildTargetPatternParser#forVisibilityArgument()}. */ public final T parse(CellPathResolver cellNames, String buildTargetPattern) { Preconditions.checkArgument( buildTargetPattern.contains(BUILD_RULE_PREFIX), String.format("'%s' must start with '//' or a cell followed by '//'", buildTargetPattern)); if (buildTargetPattern.endsWith("/" + WILDCARD_BUILD_RULE_SUFFIX)) { return createWildCardPattern(cellNames, buildTargetPattern); } BuildTarget target = BuildTargetParser.INSTANCE.parse(buildTargetPattern, this, cellNames); if (target.getShortNameAndFlavorPostfix().isEmpty()) { return createForChildren(target.getCellPath(), target.getBasePath()); } else { return createForSingleton(target); } } private T createWildCardPattern(CellPathResolver cellNames, String buildTargetPattern) { if (!isWildCardAllowed()) { throw new BuildTargetParseException( String.format("'%s' cannot end with '...'", buildTargetPattern)); } Path cellPath; int index = buildTargetPattern.indexOf(BUILD_RULE_PREFIX); if (index > 0) { cellPath = cellNames.getCellPath(Optional.of(buildTargetPattern.substring(0, index))); buildTargetPattern = buildTargetPattern.substring(index); } else { cellPath = cellNames.getCellPath(Optional.empty()); } if (buildTargetPattern.contains(BUILD_RULE_SEPARATOR)) { throw new BuildTargetParseException( String.format("'%s' cannot contain colon", buildTargetPattern)); } if (!buildTargetPattern.equals(BUILD_RULE_PREFIX + WILDCARD_BUILD_RULE_SUFFIX)) { String basePathWithPrefix = buildTargetPattern.substring( 0, buildTargetPattern.length() - WILDCARD_BUILD_RULE_SUFFIX.length() - 1); BuildTargetParser.checkBaseName(basePathWithPrefix, buildTargetPattern); } String basePathWithSlash = buildTargetPattern.substring( BUILD_RULE_PREFIX.length(), buildTargetPattern.length() - WILDCARD_BUILD_RULE_SUFFIX.length()); // Make sure the basePath comes from the same underlying filesystem. Path basePath = cellPath.getFileSystem().getPath(basePathWithSlash); return createForDescendants(cellPath, basePath); } /** * Used when parsing target names relative to another target, such as in a build file. * * @param baseName name such as {@code //first-party/orca} */ public static BuildTargetPatternParser<BuildTargetPattern> forBaseName(String baseName) { Preconditions.checkNotNull(Strings.emptyToNull(baseName)); return new BuildFileContext(baseName); } /** Used when parsing target names in the {@code visibility} argument to a build rule. */ public static BuildTargetPatternParser<BuildTargetPattern> forVisibilityArgument() { return new VisibilityContext(); } /** Used when parsing fully-qualified target names only, such as from the command line. */ public static BuildTargetPatternParser<BuildTargetPattern> fullyQualified() { return new FullyQualifiedContext(); } /** * @return description of the target name and context being parsed when an error was encountered. * Examples are ":azzetz in build file //first-party/orca/orcaapp/BUCK" and * "//first-party/orca/orcaapp:mezzenger in context FULLY_QUALIFIED" */ public abstract String makeTargetDescription(String buildTargetName, String buildFileName); protected abstract T createForDescendants(Path cellPath, Path basePath); protected abstract T createForChildren(Path cellPath, Path basePath); protected abstract T createForSingleton(BuildTarget target); private abstract static class BuildTargetPatternBaseParser extends BuildTargetPatternParser<BuildTargetPattern> { public BuildTargetPatternBaseParser(String baseName) { super(baseName); } @Override public BuildTargetPattern createForDescendants(Path cellPath, Path basePath) { return SubdirectoryBuildTargetPattern.of(cellPath, basePath); } @Override public BuildTargetPattern createForChildren(Path cellPath, Path basePath) { return ImmediateDirectoryBuildTargetPattern.of(cellPath, basePath); } @Override public BuildTargetPattern createForSingleton(BuildTarget target) { return SingletonBuildTargetPattern.of( target.getUnflavoredBuildTarget().getCellPath(), target.getFullyQualifiedName()); } } private static class BuildFileContext extends BuildTargetPatternBaseParser { public BuildFileContext(String basePath) { super(basePath); } @Override public String makeTargetDescription(String buildTargetName, String buildFileName) { return String.format("%s in build file %s/%s", buildTargetName, getBaseName(), buildFileName); } } /** * When parsing a build target for the visibility argument in a build file, targets must be * fully-qualified, but wildcards are allowed. */ private static class FullyQualifiedContext extends BuildTargetPatternBaseParser { public FullyQualifiedContext() { super(""); } @Override public String makeTargetDescription(String buildTargetName, String buildFileName) { return String.format("%s in fully qualified context.", buildTargetName); } } private static class VisibilityContext extends BuildTargetPatternBaseParser { public VisibilityContext() { super(""); } @Override public String makeTargetDescription(String buildTargetName, String buildFileName) { return String.format("%s in context visibility", buildTargetName); } @Override protected boolean isWildCardAllowed() { return true; } } }