blob: d705189a01fe0d0fb247cc0a9c5cb4f6f8235d49 [file] [log] [blame]
/*
* Copyright 2014-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.rules;
import com.facebook.buck.model.BuildFileTree;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetPattern;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.model.HasBuildTarget;
import com.facebook.buck.parser.NoSuchBuildTargetException;
import com.facebook.buck.rules.coercer.TypeCoercerFactory;
import com.facebook.buck.util.ExceptionWithHumanReadableMessage;
import com.facebook.buck.util.HumanReadableException;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
import java.lang.reflect.Field;
import java.nio.file.Path;
/**
* A {@link TargetNode} represents a node in the target graph which is created by the
* {@link com.facebook.buck.parser.Parser} as a result of parsing BUCK files in a project. It is
* responsible for processing the raw (python) inputs of a build rule, and gathering any build
* targets and paths referenced from those inputs.
*/
public class TargetNode<T> implements Comparable<TargetNode<?>>, HasBuildTarget {
private final BuildRuleFactoryParams ruleFactoryParams;
private final Description<T> description;
private final T constructorArg;
private final ImmutableSet<Path> pathsReferenced;
private final ImmutableSet<BuildTarget> declaredDeps;
private final ImmutableSortedSet<BuildTarget> extraDeps;
private final ImmutableSet<BuildTargetPattern> visibilityPatterns;
@SuppressWarnings("unchecked")
public TargetNode(
Description<T> description,
T constructorArg,
BuildRuleFactoryParams params,
ImmutableSet<BuildTarget> declaredDeps,
ImmutableSet<BuildTargetPattern> visibilityPatterns)
throws NoSuchBuildTargetException, InvalidSourcePathInputException {
this.description = description;
this.constructorArg = constructorArg;
this.ruleFactoryParams = params;
final ImmutableSet.Builder<Path> paths = ImmutableSet.builder();
final ImmutableSortedSet.Builder<BuildTarget> extraDeps = ImmutableSortedSet.naturalOrder();
// Scan the input to find possible BuildTargets, necessary for loading dependent rules.
TypeCoercerFactory typeCoercerFactory = new TypeCoercerFactory();
T arg = description.createUnpopulatedConstructorArg();
for (Field field : arg.getClass().getFields()) {
ParamInfo<T> info = new ParamInfo<>(typeCoercerFactory, field);
if (info.isDep() &&
info.hasElementTypes(BuildTarget.class, SourcePath.class, Path.class)) {
detectBuildTargetsAndPathsForConstructorArg(extraDeps, paths, info, constructorArg);
}
}
if (description instanceof ImplicitDepsInferringDescription) {
extraDeps
.addAll(
((ImplicitDepsInferringDescription<T>) description)
.findDepsForTargetFromConstructorArgs(params.target, constructorArg));
}
this.extraDeps = ImmutableSortedSet.copyOf(Sets.difference(extraDeps.build(), declaredDeps));
this.pathsReferenced = ruleFactoryParams.enforceBuckPackageBoundary()
? verifyPaths(paths.build())
: paths.build();
this.declaredDeps = declaredDeps;
this.visibilityPatterns = visibilityPatterns;
}
public Description<T> getDescription() {
return description;
}
public BuildRuleType getType() {
return description.getBuildRuleType();
}
public T getConstructorArg() {
return constructorArg;
}
@Override
public BuildTarget getBuildTarget() {
return ruleFactoryParams.target;
}
public ImmutableSet<Path> getInputs() {
return pathsReferenced;
}
public ImmutableSet<BuildTarget> getDeclaredDeps() {
return declaredDeps;
}
public ImmutableSet<BuildTarget> getExtraDeps() {
return extraDeps;
}
public ImmutableSet<BuildTarget> getDeps() {
ImmutableSet.Builder<BuildTarget> builder = ImmutableSet.builder();
builder.addAll(getDeclaredDeps());
builder.addAll(getExtraDeps());
return builder.build();
}
public BuildRuleFactoryParams getRuleFactoryParams() {
return ruleFactoryParams;
}
/**
* TODO(agallagher): It'd be nice to eventually move this implementation to an
* `AbstractDescription` base class, so that the various types of descriptions
* can install their own implementations. However, we'll probably want to move
* most of what is now `BuildRuleParams` to `DescriptionParams` and set them up
* while building the target graph.
*/
public boolean isVisibleTo(BuildTarget other) {
return BuildTargets.isVisibleTo(
getBuildTarget(),
visibilityPatterns,
other);
}
public void checkVisibility(BuildTarget other) {
if (!isVisibleTo(other)) {
throw new HumanReadableException(
"%s depends on %s, which is not visible",
other,
getBuildTarget());
}
}
/**
* Type safe checked cast of the constructor arg.
*/
@SuppressWarnings("unchecked")
public <U> Optional<TargetNode<U>> castArg(Class<U> cls) {
if (cls.isInstance(constructorArg)) {
return Optional.of((TargetNode<U>) this);
} else {
return Optional.absent();
}
}
private void detectBuildTargetsAndPathsForConstructorArg(
final ImmutableSet.Builder<BuildTarget> depsBuilder,
final ImmutableSet.Builder<Path> pathsBuilder,
ParamInfo<T> info,
T constructorArg) throws NoSuchBuildTargetException {
// We'll make no test for optionality here. Let's assume it's done elsewhere.
try {
info.traverse(
new ParamInfo.Traversal() {
@Override
public void traverse(Object object) {
if (object instanceof PathSourcePath) {
pathsBuilder.add(((PathSourcePath) object).getRelativePath());
} else if (object instanceof BuildTargetSourcePath) {
depsBuilder.add(((BuildTargetSourcePath) object).getTarget());
} else if (object instanceof Path) {
pathsBuilder.add((Path) object);
} else if (object instanceof BuildTarget) {
depsBuilder.add((BuildTarget) object);
}
}
},
constructorArg);
} catch (RuntimeException e) {
if (e.getCause() instanceof NoSuchBuildTargetException) {
throw (NoSuchBuildTargetException) e.getCause();
}
}
}
private ImmutableSet<Path> verifyPaths(ImmutableSet<Path> paths)
throws InvalidSourcePathInputException {
Path basePath = getBuildTarget().getBasePath();
BuildFileTree buildFileTree = ruleFactoryParams.getBuildFileTree();
for (Path path : paths) {
if (!basePath.toString().isEmpty() && !path.startsWith(basePath)) {
throw new InvalidSourcePathInputException(
"'%s' in '%s' refers to a parent directory.",
basePath.relativize(path),
getBuildTarget());
}
Optional<Path> ancestor = buildFileTree.getBasePathOfAncestorTarget(path);
if (!ancestor.isPresent() || !ancestor.get().equals(basePath)) {
throw new InvalidSourcePathInputException(
"'%s' in '%s' crosses a buck package boundary. Find the nearest BUCK file in the " +
"directory that contains this file and refer to the rule referencing the desired" +
"file.",
path,
getBuildTarget());
}
}
return paths;
}
@Override
public int compareTo(TargetNode<?> o) {
return getBuildTarget().compareTo(o.getBuildTarget());
}
@Override
public final boolean equals(Object obj) {
if (!(obj instanceof TargetNode<?>)) {
return false;
}
TargetNode<?> that = (TargetNode<?>) obj;
return this.getBuildTarget().equals(that.getBuildTarget());
}
@Override
public final int hashCode() {
return getBuildTarget().hashCode();
}
@Override
public final String toString() {
return getBuildTarget().getFullyQualifiedName();
}
@SuppressWarnings({"rawtypes", "unchecked"})
public TargetNode<T> with(
Description<T> description,
T constructorArg,
BuildRuleFactoryParams ruleFactoryParams,
ImmutableSet declaredDeps,
ImmutableSet<BuildTargetPattern> visibilityPatterns) {
try {
return new TargetNode(
description,
constructorArg,
ruleFactoryParams,
declaredDeps,
visibilityPatterns);
} catch (InvalidSourcePathInputException | NoSuchBuildTargetException e) {
throw new RuntimeException(e);
}
}
/**
* Return a copy of the current TargetNode, with the {@link Description} used for creating
* {@link BuildRule} instances switched out.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public TargetNode<?> withDescription(Description<?> description) {
try {
return new TargetNode(
description,
constructorArg,
ruleFactoryParams,
declaredDeps,
visibilityPatterns);
} catch (InvalidSourcePathInputException | NoSuchBuildTargetException e) {
// This is extremely unlikely to happen --- we've already created a TargetNode with these
// values before.
throw new RuntimeException(e);
}
}
public TargetNode<T> withConstructorArg(T constructorArg) {
return with(description, constructorArg, ruleFactoryParams, declaredDeps, visibilityPatterns);
}
public TargetNode<T> withBuildTarget(BuildTarget buildTarget) {
return with(
description,
constructorArg,
ruleFactoryParams.withBuildTarget(buildTarget),
declaredDeps,
visibilityPatterns);
}
@SuppressWarnings("serial")
public static class InvalidSourcePathInputException extends Exception
implements ExceptionWithHumanReadableMessage{
private InvalidSourcePathInputException(String message, Object...objects) {
super(String.format(message, objects));
}
@Override
public String getHumanReadableErrorMessage() {
return getMessage();
}
}
}