blob: 1eb1103717bb2809907019efd7c3c2b24d808a15 [file] [log] [blame]
/*
* Copyright 2013-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.cli;
import com.facebook.buck.util.HumanReadableException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This class abstracts the data for a dependency query that the "buck query" command processes.
* All queries reference a target. Queries that reference a source are queries for paths from
* target back to source, and queries that don't reference a source are queries for all
* dependencies of a target.
*/
public class DependencyQuery {
/**
* The ARROW_PATTERN is used for extracting the three components of a Buck query.
* These are the target rule, the (optional) source rule, and the depth specifier inside
* the dependency arrow. Target -depth> Source.
* <p>
* The target rule is mandatory so we use S+, while source rule is optional so it is specified
* by S*. A nonexistent source rule will still be captured as an empty string in the third group
* of the regex. Spaces are required between the build rules and the arrow, though a query
* can also end immediately after the arrow. Inside the -?> arrow, users can specify either
* a numeric depth \\d*, a single '*', or nothing (which is taken care of by the \\d* regex).
*/
@VisibleForTesting
static final Pattern ARROW_PATTERN = Pattern.compile(
"\\s*(\\S+)\\s+"
+ "-(\\d*|\\*)>"
+ "(?:\\s+|$)(\\S*)\\s*");
// TODO: Convert build rules from strings to BuildTargets
private final String target;
private final Optional<String> source;
private final Optional<Integer> depth;
private DependencyQuery(Optional<Integer> depth, String target) {
this(depth, target, /* source */ Optional.<String>absent());
}
private DependencyQuery(Optional<Integer> depth, String target, Optional<String> source) {
Preconditions.checkNotNull(depth);
Preconditions.checkNotNull(target);
Preconditions.checkNotNull(source);
this.depth = depth;
this.target = target;
this.source = source;
}
String getTarget() {
return target;
}
Optional<String> getSource() {
return source;
}
Optional<Integer> getDepth() {
return depth;
}
/**
* This static factory method parses an input string query.
* <p>
* The general structure of a query is T -D> S.
* T is the target, specified as a fully qualified target name. It is required.
* S is the source, similarly fully qualified. When it is specified we assume you are
* querying for the dependency paths from T to S. When it is omitted we assume you are
* querying for all of the dependencies of T.
* D is the depth. It controls the depth to which we will traverse the dependency graph
* starting at T. D=1 means we will search only for immediate dependencies. D=* means the
* depth will be unbounded. If D is omitted, as in "T -> S" then it defaults to *.
* <p>
* Example Queries:
* buck query '//:target -2>' finds all dependencies of dependencies of target
* buck query '//:target -*> //:source' finds all dependency paths from target to source
*
* @return dependency query we constructed based on string.
* @throws HumanReadableException on bad query strings.
*/
static DependencyQuery parseQueryString(String queryString) throws HumanReadableException {
Matcher queryMatcher = ARROW_PATTERN.matcher(queryString);
if (!queryMatcher.matches()) {
throw new HumanReadableException(String.format("Invalid query string: %s.", queryString));
}
String arrowType = queryMatcher.group(2).trim();
Optional<Integer> depth;
if (arrowType.isEmpty() || arrowType.equals("*")) {
depth = Optional.absent();
} else {
try {
int numDepth = Integer.parseInt(arrowType);
if (numDepth < 0) {
throw new IllegalArgumentException(String.format("Negative depth: %d.", numDepth));
}
depth = Optional.of(numDepth);
} catch (IllegalArgumentException e) {
throw new HumanReadableException(e, String.format("Invalid search depth: %s.", arrowType));
}
}
String target = queryMatcher.group(1).trim();
final String fullyQualifiedTarget =
CommandLineBuildTargetNormalizer.normalizeBuildTargetIdentifier(target);
String source = queryMatcher.group(3).trim();
String fullyQualifiedSource =
CommandLineBuildTargetNormalizer.normalizeBuildTargetIdentifier(source);
if (source.isEmpty()) {
return new DependencyQuery(depth, fullyQualifiedTarget);
} else {
return new DependencyQuery(depth, fullyQualifiedTarget, Optional.of(fullyQualifiedSource));
}
}
}