/* * Copyright 2015-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. */ // Copyright 2014 Google Inc. All rights reserved. // // 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.query; import com.facebook.buck.query.QueryEnvironment.Argument; import com.facebook.buck.query.QueryEnvironment.ArgumentType; import com.facebook.buck.query.QueryEnvironment.QueryFunction; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListeningExecutorService; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; /** * A 'deps(x [, depth, next_expr])' expression, which finds the dependencies of the given argument * set 'x'. The optional parameter 'depth' specifies the depth of the search. If 'depth' is absent, * the search is unbounded. The optional third argument specifies how new edges are added to the * traversal. If the 'next_expr' is absent, the default 'first_order_deps()' function is used. * * <pre>expr ::= DEPS '(' expr ')'</pre> * * <pre> | DEPS '(' expr ',' INTEGER ')'</pre> * * <pre> | DEPS '(' expr ',' INTEGER ',' expr ')'</pre> */ public class DepsFunction implements QueryFunction { private static final ImmutableList<ArgumentType> ARGUMENT_TYPES = ImmutableList.of(ArgumentType.EXPRESSION, ArgumentType.INTEGER, ArgumentType.EXPRESSION); public DepsFunction() {} @Override public String getName() { return "deps"; } @Override public int getMandatoryArguments() { return 1; } @Override public ImmutableList<ArgumentType> getArgumentTypes() { return ARGUMENT_TYPES; } private void forEachDep( QueryEnvironment env, ListeningExecutorService executor, QueryExpression depsExpression, Iterable<QueryTarget> targets, Consumer<? super QueryTarget> consumer) throws QueryException, InterruptedException { for (QueryTarget target : targets) { Set<QueryTarget> deps = depsExpression.eval( new TargetVariablesQueryEnvironment( ImmutableMap.of( FirstOrderDepsFunction.NAME, ImmutableSet.copyOf(env.getFwdDeps(ImmutableList.of(target)))), env), executor); deps.forEach(consumer); } } /** * Evaluates to the dependencies of the argument. Breadth first search from the given argument * until there are no more unvisited nodes in the transitive closure or the maximum depth (if * supplied) is reached. */ @Override public ImmutableSet<QueryTarget> eval( QueryEnvironment env, ImmutableList<Argument> args, ListeningExecutorService executor) throws QueryException, InterruptedException { Set<QueryTarget> argumentSet = args.get(0).getExpression().eval(env, executor); int depthBound = args.size() > 1 ? args.get(1).getInteger() : Integer.MAX_VALUE; Optional<QueryExpression> deps = args.size() > 2 ? Optional.of(args.get(2).getExpression()) : Optional.empty(); env.buildTransitiveClosure(argumentSet, depthBound, executor); // LinkedHashSet preserves the order of insertion when iterating over the values. // The order by which we traverse the result is meaningful because the dependencies are // traversed level-by-level. Set<QueryTarget> result = new LinkedHashSet<>(argumentSet); Collection<QueryTarget> current = argumentSet; // Iterating depthBound+1 times because the first one processes the given argument set. for (int i = 0; i < depthBound; i++) { Collection<QueryTarget> next = new ArrayList<>(); Consumer<? super QueryTarget> consumer = queryTarget -> { boolean added = result.add(queryTarget); if (added) { next.add(queryTarget); } }; if (deps.isPresent()) { forEachDep(env, executor, deps.get(), current, consumer); } else { env.forEachFwdDep(current, consumer); } if (next.isEmpty()) { break; } current = next; } return ImmutableSet.copyOf(result); } /** * A function that resolves to the current node's target being traversed when evaluating the deps * function. */ public static class FirstOrderDepsFunction implements QueryFunction { private static final String NAME = "first_order_deps"; @Override public String getName() { return NAME; } @Override public int getMandatoryArguments() { return 0; } @Override public ImmutableList<ArgumentType> getArgumentTypes() { return ImmutableList.of(); } @Override public ImmutableSet<QueryTarget> eval( QueryEnvironment env, ImmutableList<Argument> args, ListeningExecutorService executor) throws QueryException, InterruptedException { Preconditions.checkArgument(args.size() == 0); return env.resolveTargetVariable(getName()); } } }