/*
* 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.model;
import com.facebook.buck.io.BuckPaths;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.util.HumanReadableException;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
import java.nio.file.Path;
import java.util.Set;
/** Static helpers for working with build targets. */
public class BuildTargets {
/** Utility class: do not instantiate. */
private BuildTargets() {}
/**
* Return a path to a file in the buck-out/bin/ directory. {@code format} will be prepended with
* the {@link BuckPaths#getScratchDir()} and the target base path, then formatted with the target
* short name.
*
* @param target The {@link BuildTarget} to scope this path to.
* @param format {@link String#format} string for the path name. It should contain one "%s", which
* will be filled in with the rule's short name. It should not start with a slash.
* @return A {@link java.nio.file.Path} under buck-out/bin, scoped to the base path of {@code
* target}.
*/
public static Path getScratchPath(
ProjectFilesystem filesystem, BuildTarget target, String format) {
Preconditions.checkArgument(
!format.startsWith("/"), "format string should not start with a slash");
return filesystem
.getBuckPaths()
.getScratchDir()
.resolve(target.getBasePath())
.resolve(String.format(format, target.getShortNameAndFlavorPostfix()));
}
/**
* Return a path to a file in the buck-out/annotation/ directory. {@code format} will be prepended
* with the {@link BuckPaths#getAnnotationDir()} and the target base path, then formatted with the
* target short name.
*
* @param target The {@link BuildTarget} to scope this path to.
* @param format {@link String#format} string for the path name. It should contain one "%s", which
* will be filled in with the rule's short name. It should not start with a slash.
* @return A {@link java.nio.file.Path} under buck-out/annotation, scoped to the base path of
* {@code target}.
*/
public static Path getAnnotationPath(
ProjectFilesystem filesystem, BuildTarget target, String format) {
Preconditions.checkArgument(
!format.startsWith("/"), "format string should not start with a slash");
return filesystem
.getBuckPaths()
.getAnnotationDir()
.resolve(target.getBasePath())
.resolve(String.format(format, target.getShortNameAndFlavorPostfix()));
}
/**
* Return a relative path to a file in the buck-out/gen/ directory. {@code format} will be
* prepended with the {@link BuckPaths#getGenDir()} and the target base path, then formatted with
* the target short name.
*
* @param target The {@link BuildTarget} to scope this path to.
* @param format {@link String#format} string for the path name. It should contain one "%s", which
* will be filled in with the rule's short name. It should not start with a slash.
* @return A {@link java.nio.file.Path} under buck-out/gen, scoped to the base path of {@code
* target}.
*/
public static Path getGenPath(ProjectFilesystem filesystem, BuildTarget target, String format) {
Preconditions.checkArgument(
!format.startsWith("/"), "format string should not start with a slash");
return filesystem
.getBuckPaths()
.getGenDir()
.resolve(target.getBasePath())
.resolve(String.format(format, target.getShortNameAndFlavorPostfix()));
}
/**
* Takes the {@link BuildTarget} for {@code hasBuildTarget} and derives a new {@link BuildTarget}
* from it with the specified flavor.
*
* @throws IllegalArgumentException if the original {@link BuildTarget} already has a flavor.
*/
public static BuildTarget createFlavoredBuildTarget(
UnflavoredBuildTarget buildTarget, Flavor flavor) {
return BuildTarget.builder(buildTarget).addFlavors(flavor).build();
}
public static Predicate<BuildTarget> containsFlavors(final FlavorDomain<?> domain) {
return input -> {
ImmutableSet<Flavor> flavorSet =
Sets.intersection(domain.getFlavors(), input.getFlavors()).immutableCopy();
return !flavorSet.isEmpty();
};
}
public static Predicate<BuildTarget> containsFlavor(final Flavor flavor) {
return input -> input.getFlavors().contains(flavor);
}
/**
* Propagate flavors represented by the given {@link FlavorDomain} objects from a parent target to
* its dependencies.
*/
public static ImmutableSortedSet<BuildTarget> propagateFlavorDomains(
BuildTarget target, Iterable<FlavorDomain<?>> domains, Iterable<BuildTarget> deps) {
Set<Flavor> flavors = Sets.newHashSet();
// For each flavor domain, extract the corresponding flavor from the parent target and
// verify that each dependency hasn't already set this flavor.
for (FlavorDomain<?> domain : domains) {
// Now extract all relevant domain flavors from our parent target.
ImmutableSet<Flavor> flavorSet =
Sets.intersection(domain.getFlavors(), target.getFlavors()).immutableCopy();
if (flavorSet.isEmpty()) {
throw new HumanReadableException("%s: no flavor for \"%s\"", target, domain.getName());
}
flavors.addAll(flavorSet);
// First verify that our deps are not already flavored for our given domains.
for (BuildTarget dep : deps) {
if (domain.getFlavor(dep).isPresent()) {
throw new HumanReadableException(
"%s: dep %s already has flavor for \"%s\" : %s",
target, dep, domain.getName(), flavorSet.toString());
}
}
}
ImmutableSortedSet.Builder<BuildTarget> flavoredDeps = ImmutableSortedSet.naturalOrder();
// Now flavor each dependency with the relevant flavors.
for (BuildTarget dep : deps) {
flavoredDeps.add(BuildTarget.builder(dep).addAllFlavors(flavors).build());
}
return flavoredDeps.build();
}
/**
* Propagate a build target's flavors in a certain domain to a list of other build targets.
*
* @param domain the flavor domain to be propagated.
* @param buildTarget the build target containing the flavors to be propagated
* @param deps list of BuildTargets to propagate the flavors to. If a target already contains one
* or more flavors in domain, it is left unchanged.
* @return the list of BuildTargets with any flavors propagated.
*/
public static FluentIterable<BuildTarget> propagateFlavorsInDomainIfNotPresent(
FlavorDomain<?> domain, BuildTarget buildTarget, FluentIterable<BuildTarget> deps) {
if (domain.containsAnyOf(buildTarget.getFlavors())) {
FluentIterable<BuildTarget> targetsWithFlavorsAlready =
deps.filter(BuildTargets.containsFlavors(domain));
FluentIterable<BuildTarget> targetsWithoutFlavors =
deps.filter(Predicates.not(BuildTargets.containsFlavors(domain)));
deps =
targetsWithFlavorsAlready.append(
BuildTargets.propagateFlavorDomains(
buildTarget, ImmutableSet.of(domain), targetsWithoutFlavors));
}
return deps;
}
}