/*
* Copyright 2017-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.jvm.java;
import static com.facebook.buck.jvm.java.JavaLibraryRules.getAbiRulesWherePossible;
import com.facebook.buck.jvm.common.ResourceValidator;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.parser.NoSuchBuildTargetException;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.BuildRules;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.util.MoreCollectors;
import com.facebook.buck.util.RichStream;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
public class DefaultJavaLibraryBuilder {
private final BuildRuleParams params;
@Nullable private final JavaBuckConfig javaBuckConfig;
protected final BuildRuleResolver buildRuleResolver;
protected final SourcePathResolver sourcePathResolver;
protected final SourcePathRuleFinder ruleFinder;
protected ImmutableSortedSet<SourcePath> srcs = ImmutableSortedSet.of();
protected ImmutableSortedSet<SourcePath> resources = ImmutableSortedSet.of();
protected Optional<Path> generatedSourceFolder = Optional.empty();
protected Optional<SourcePath> proguardConfig = Optional.empty();
protected ImmutableList<String> postprocessClassesCommands = ImmutableList.of();
protected ImmutableSortedSet<BuildRule> fullJarExportedDeps = ImmutableSortedSet.of();
protected ImmutableSortedSet<BuildRule> fullJarProvidedDeps = ImmutableSortedSet.of();
protected boolean trackClassUsage = false;
protected boolean compileAgainstAbis = false;
protected Optional<Path> resourcesRoot = Optional.empty();
protected Optional<SourcePath> manifestFile = Optional.empty();
protected Optional<String> mavenCoords = Optional.empty();
protected ImmutableSortedSet<BuildTarget> tests = ImmutableSortedSet.of();
protected ImmutableSet<Pattern> classesToRemoveFromJar = ImmutableSet.of();
protected JavacOptionsAmender javacOptionsAmender = JavacOptionsAmender.IDENTITY;
@Nullable protected JavacOptions javacOptions = null;
@Nullable private JavaLibraryDescription.CoreArg args = null;
protected DefaultJavaLibraryBuilder(
BuildRuleParams params, BuildRuleResolver buildRuleResolver, JavaBuckConfig javaBuckConfig) {
this.params = params;
this.buildRuleResolver = buildRuleResolver;
this.javaBuckConfig = javaBuckConfig;
ruleFinder = new SourcePathRuleFinder(buildRuleResolver);
sourcePathResolver = new SourcePathResolver(ruleFinder);
setCompileAgainstAbis(javaBuckConfig.shouldCompileAgainstAbis());
}
protected DefaultJavaLibraryBuilder(BuildRuleParams params, BuildRuleResolver buildRuleResolver) {
this.params = params;
this.buildRuleResolver = buildRuleResolver;
ruleFinder = new SourcePathRuleFinder(buildRuleResolver);
sourcePathResolver = new SourcePathResolver(ruleFinder);
javaBuckConfig = null;
}
public DefaultJavaLibraryBuilder setArgs(JavaLibraryDescription.CoreArg args) {
this.args = args;
return setSrcs(args.getSrcs())
.setResources(args.getResources())
.setResourcesRoot(args.getResourcesRoot())
.setProguardConfig(args.getProguardConfig())
.setPostprocessClassesCommands(args.getPostprocessClassesCommands())
.setExportedDeps(args.getExportedDeps())
.setProvidedDeps(args.getProvidedDeps())
.setManifestFile(args.getManifestFile())
.setMavenCoords(args.getMavenCoords());
}
public DefaultJavaLibraryBuilder setJavacOptions(JavacOptions javacOptions) {
this.javacOptions = javacOptions;
return this;
}
public DefaultJavaLibraryBuilder setJavacOptionsAmender(JavacOptionsAmender amender) {
javacOptionsAmender = amender;
return this;
}
public DefaultJavaLibraryBuilder setSrcs(ImmutableSortedSet<SourcePath> srcs) {
this.srcs = srcs;
return this;
}
public DefaultJavaLibraryBuilder setResources(ImmutableSortedSet<SourcePath> resources) {
this.resources =
ResourceValidator.validateResources(
sourcePathResolver, params.getProjectFilesystem(), resources);
return this;
}
public DefaultJavaLibraryBuilder setGeneratedSourceFolder(Optional<Path> generatedSourceFolder) {
this.generatedSourceFolder = generatedSourceFolder;
return this;
}
public DefaultJavaLibraryBuilder setProguardConfig(Optional<SourcePath> proguardConfig) {
this.proguardConfig = proguardConfig;
return this;
}
public DefaultJavaLibraryBuilder setPostprocessClassesCommands(
ImmutableList<String> postprocessClassesCommands) {
this.postprocessClassesCommands = postprocessClassesCommands;
return this;
}
public DefaultJavaLibraryBuilder setExportedDeps(ImmutableSortedSet<BuildTarget> exportedDeps) {
this.fullJarExportedDeps = buildRuleResolver.getAllRules(exportedDeps);
return this;
}
@VisibleForTesting
public DefaultJavaLibraryBuilder setExportedDepRules(ImmutableSortedSet<BuildRule> exportedDeps) {
this.fullJarExportedDeps = exportedDeps;
return this;
}
public DefaultJavaLibraryBuilder setProvidedDeps(ImmutableSortedSet<BuildTarget> providedDeps) {
this.fullJarProvidedDeps = buildRuleResolver.getAllRules(providedDeps);
return this;
}
public DefaultJavaLibraryBuilder setTrackClassUsage(boolean trackClassUsage) {
this.trackClassUsage = trackClassUsage;
return this;
}
public DefaultJavaLibraryBuilder setResourcesRoot(Optional<Path> resourcesRoot) {
this.resourcesRoot = resourcesRoot;
return this;
}
public DefaultJavaLibraryBuilder setManifestFile(Optional<SourcePath> manifestFile) {
this.manifestFile = manifestFile;
return this;
}
public DefaultJavaLibraryBuilder setMavenCoords(Optional<String> mavenCoords) {
this.mavenCoords = mavenCoords;
return this;
}
public DefaultJavaLibraryBuilder setTests(ImmutableSortedSet<BuildTarget> tests) {
this.tests = tests;
return this;
}
public DefaultJavaLibraryBuilder setClassesToRemoveFromJar(
ImmutableSet<Pattern> classesToRemoveFromJar) {
this.classesToRemoveFromJar = classesToRemoveFromJar;
return this;
}
protected DefaultJavaLibraryBuilder setCompileAgainstAbis(boolean compileAgainstAbis) {
this.compileAgainstAbis = compileAgainstAbis;
return this;
}
public final DefaultJavaLibrary build() throws NoSuchBuildTargetException {
BuilderHelper helper = newHelper();
return helper.build();
}
public final BuildRule buildAbi() throws NoSuchBuildTargetException {
return newHelper().buildAbi();
}
protected BuilderHelper newHelper() {
return new BuilderHelper();
}
protected class BuilderHelper {
@Nullable private BuildRuleParams finalParams;
@Nullable private ImmutableSortedSet<BuildRule> finalFullJarDeclaredDeps;
@Nullable private ImmutableSortedSet<BuildRule> compileTimeClasspathUnfilteredFullDeps;
@Nullable private ImmutableSortedSet<BuildRule> compileTimeClasspathFullDeps;
@Nullable private ImmutableSortedSet<BuildRule> compileTimeClasspathAbiDeps;
@Nullable private ImmutableSortedSet<SourcePath> abiInputs;
@Nullable private CompileToJarStepFactory compileStepFactory;
@Nullable private BuildTarget abiJar;
protected DefaultJavaLibrary build() throws NoSuchBuildTargetException {
return new DefaultJavaLibrary(
getFinalParams(),
sourcePathResolver,
ruleFinder,
srcs,
resources,
generatedSourceFolder,
proguardConfig,
postprocessClassesCommands,
getFinalFullJarDeclaredDeps(),
fullJarExportedDeps,
fullJarProvidedDeps,
getFinalCompileTimeClasspathSourcePaths(),
getAbiInputs(),
getAbiJar(),
trackClassUsage,
getCompileStepFactory(),
resourcesRoot,
manifestFile,
mavenCoords,
tests,
classesToRemoveFromJar);
}
protected BuildRule buildAbi() throws NoSuchBuildTargetException {
BuildTarget buildTarget = params.getBuildTarget();
if (HasJavaAbi.isClassAbiTarget(buildTarget)) {
return buildAbiFromClasses();
} else if (HasJavaAbi.isSourceAbiTarget(buildTarget)) {
return buildAbiFromSource();
} else if (HasJavaAbi.isVerifiedSourceAbiTarget(buildTarget)) {
BuildRule classAbi = buildAbiFromClasses();
BuildRule sourceAbi = buildAbiFromSource();
buildRuleResolver.addToIndex(classAbi);
buildRuleResolver.addToIndex(sourceAbi);
return new CompareAbis(
params.copyReplacingDeclaredAndExtraDeps(
() -> ImmutableSortedSet.of(classAbi, sourceAbi), ImmutableSortedSet::of),
sourcePathResolver,
classAbi.getSourcePathToOutput(),
sourceAbi.getSourcePathToOutput(),
javaBuckConfig.getSourceAbiVerificationMode());
}
throw new AssertionError(
String.format("%s is not an ABI target but went down the ABI codepath", buildTarget));
}
protected BuildTarget getAbiJar() {
if (abiJar == null) {
BuildTarget libraryTarget = params.getBuildTarget();
if (shouldBuildAbiFromSource()) {
JavaBuckConfig.SourceAbiVerificationMode sourceAbiVerificationMode =
javaBuckConfig.getSourceAbiVerificationMode();
abiJar =
sourceAbiVerificationMode == JavaBuckConfig.SourceAbiVerificationMode.OFF
? HasJavaAbi.getSourceAbiJar(libraryTarget)
: HasJavaAbi.getVerifiedSourceAbiJar(libraryTarget);
} else {
abiJar = HasJavaAbi.getClassAbiJar(libraryTarget);
}
}
return abiJar;
}
private boolean shouldBuildAbiFromSource() {
return isCompilingJava()
&& sourceAbisEnabled()
&& argsAllowSourceAbis()
&& pluginsSupportSourceAbis();
}
private boolean isCompilingJava() {
return getCompileStepFactory() instanceof JavacToJarStepFactory;
}
private boolean sourceAbisEnabled() {
return javaBuckConfig != null && javaBuckConfig.shouldGenerateAbisFromSource();
}
private boolean argsAllowSourceAbis() {
return Preconditions.checkNotNull(args).getGenerateAbiFromSource().orElse(true);
}
private boolean pluginsSupportSourceAbis() {
ImmutableList<ResolvedJavacPluginProperties> annotationProcessors =
Preconditions.checkNotNull(javacOptions)
.getAnnotationProcessingParams()
.getAnnotationProcessors(params.getProjectFilesystem(), sourcePathResolver);
for (ResolvedJavacPluginProperties annotationProcessor : annotationProcessors) {
if (!annotationProcessor.getDoesNotAffectAbi()
&& !annotationProcessor.getSupportAbiGenerationFromSource()) {
// Processor is ABI-affecting but cannot run during ABI generation from source; disallow
return false;
}
}
return true;
}
private BuildRule buildAbiFromSource() throws NoSuchBuildTargetException {
BuildTarget libraryTarget = HasJavaAbi.getLibraryTarget(params.getBuildTarget());
BuildTarget abiTarget = HasJavaAbi.getSourceAbiJar(libraryTarget);
JavacToJarStepFactory compileStepFactory = (JavacToJarStepFactory) getCompileStepFactory();
return new CalculateAbiFromSource(
getFinalParams().withBuildTarget(abiTarget),
ruleFinder,
srcs,
resources,
getFinalCompileTimeClasspathSourcePaths(),
compileStepFactory,
resourcesRoot,
manifestFile,
classesToRemoveFromJar);
}
private BuildRule buildAbiFromClasses() throws NoSuchBuildTargetException {
BuildTarget libraryTarget = HasJavaAbi.getLibraryTarget(params.getBuildTarget());
BuildTarget abiTarget = HasJavaAbi.getClassAbiJar(libraryTarget);
BuildRule libraryRule = buildRuleResolver.requireRule(libraryTarget);
return CalculateAbiFromClasses.of(
abiTarget,
ruleFinder,
params,
Preconditions.checkNotNull(libraryRule.getSourcePathToOutput()),
javaBuckConfig != null
&& javaBuckConfig.getSourceAbiVerificationMode()
!= JavaBuckConfig.SourceAbiVerificationMode.OFF);
}
protected final BuildRuleParams getFinalParams() throws NoSuchBuildTargetException {
if (finalParams == null) {
finalParams = buildFinalParams();
}
return finalParams;
}
protected final ImmutableSortedSet<BuildRule> getFinalFullJarDeclaredDeps() {
if (finalFullJarDeclaredDeps == null) {
finalFullJarDeclaredDeps =
ImmutableSortedSet.copyOf(
Iterables.concat(
params.getDeclaredDeps().get(),
getCompileStepFactory().getDeclaredDeps(ruleFinder)));
}
return finalFullJarDeclaredDeps;
}
protected final ImmutableSortedSet<SourcePath> getFinalCompileTimeClasspathSourcePaths()
throws NoSuchBuildTargetException {
ImmutableSortedSet<BuildRule> buildRules =
compileAgainstAbis ? getCompileTimeClasspathAbiDeps() : getCompileTimeClasspathFullDeps();
return buildRules
.stream()
.map(BuildRule::getSourcePathToOutput)
.filter(Objects::nonNull)
.collect(MoreCollectors.toImmutableSortedSet());
}
protected final ImmutableSortedSet<BuildRule> getCompileTimeClasspathFullDeps() {
if (compileTimeClasspathFullDeps == null) {
compileTimeClasspathFullDeps =
getCompileTimeClasspathUnfilteredFullDeps()
.stream()
.filter(dep -> dep instanceof HasJavaAbi)
.collect(MoreCollectors.toImmutableSortedSet());
}
return compileTimeClasspathFullDeps;
}
protected final ImmutableSortedSet<BuildRule> getCompileTimeClasspathAbiDeps()
throws NoSuchBuildTargetException {
if (compileTimeClasspathAbiDeps == null) {
compileTimeClasspathAbiDeps = buildCompileTimeClasspathAbiDeps();
}
return compileTimeClasspathAbiDeps;
}
protected final ImmutableSortedSet<SourcePath> getAbiInputs()
throws NoSuchBuildTargetException {
if (abiInputs == null) {
abiInputs = buildAbiInputs();
}
return abiInputs;
}
protected final CompileToJarStepFactory getCompileStepFactory() {
if (compileStepFactory == null) {
compileStepFactory = buildCompileStepFactory();
}
return compileStepFactory;
}
protected BuildRuleParams buildFinalParams() throws NoSuchBuildTargetException {
ImmutableSortedSet<BuildRule> compileTimeClasspathAbiDeps = getCompileTimeClasspathAbiDeps();
ImmutableSortedSet.Builder<BuildRule> declaredDepsBuilder = ImmutableSortedSet.naturalOrder();
ImmutableSortedSet.Builder<BuildRule> extraDepsBuilder = ImmutableSortedSet.naturalOrder();
if (compileAgainstAbis) {
declaredDepsBuilder.addAll(
getAbiRulesWherePossible(buildRuleResolver, getFinalFullJarDeclaredDeps()));
// We remove provided and exported deps since we'll be adding the ABI rules of these and
// don't want to end up with both full & ABI rules
extraDepsBuilder.addAll(
Sets.difference(
params.getExtraDeps().get(), Sets.union(fullJarProvidedDeps, fullJarExportedDeps)));
} else {
declaredDepsBuilder.addAll(getFinalFullJarDeclaredDeps());
extraDepsBuilder
.addAll(params.getExtraDeps().get())
.addAll(
Sets.difference(
getCompileTimeClasspathUnfilteredFullDeps(), params.getBuildDeps()));
}
ImmutableSortedSet<BuildRule> declaredDeps = declaredDepsBuilder.build();
// The extra deps contain rules that may not come from the deps-related arguments of the
// target, but are required for building this rule. Some default extra deps may be provided
// and exported rules, annotation processor related rules, gen_aidl rules, gen rules, and zip
// rules. The compile time classpath deps and deps from the compile step factory are manually
// added as these are required for building this rule.
// Extra deps remain separate from the declared deps because there are places where the
// declared deps are grabbed and are expected to reflect the actual deps argument of the
// target. In addition, when compiling against ABIs, extra deps shouldn't be translated to
// their ABI rules as their full JARs are required (with exception of classpath rules).
ImmutableSortedSet<BuildRule> extraDeps =
extraDepsBuilder
.addAll(Sets.difference(compileTimeClasspathAbiDeps, declaredDeps))
.addAll(getCompileStepFactory().getExtraDeps(ruleFinder))
.build();
return params.copyReplacingDeclaredAndExtraDeps(() -> declaredDeps, () -> extraDeps);
}
protected final ImmutableSortedSet<BuildRule> getCompileTimeClasspathUnfilteredFullDeps() {
if (compileTimeClasspathUnfilteredFullDeps == null) {
Iterable<BuildRule> firstOrderDeps =
Iterables.concat(
getFinalFullJarDeclaredDeps(), fullJarExportedDeps, fullJarProvidedDeps);
ImmutableSortedSet<BuildRule> rulesExportedByDependencies =
BuildRules.getExportedRules(firstOrderDeps);
compileTimeClasspathUnfilteredFullDeps =
RichStream.from(Iterables.concat(firstOrderDeps, rulesExportedByDependencies))
.collect(MoreCollectors.toImmutableSortedSet());
}
return compileTimeClasspathUnfilteredFullDeps;
}
protected ImmutableSortedSet<BuildRule> buildCompileTimeClasspathAbiDeps()
throws NoSuchBuildTargetException {
return JavaLibraryRules.getAbiRules(buildRuleResolver, getCompileTimeClasspathFullDeps());
}
protected ImmutableSortedSet<SourcePath> buildAbiInputs() throws NoSuchBuildTargetException {
return getCompileTimeClasspathAbiDeps()
.stream()
.map(BuildRule::getSourcePathToOutput)
.collect(MoreCollectors.toImmutableSortedSet());
}
protected CompileToJarStepFactory buildCompileStepFactory() {
return new JavacToJarStepFactory(
JavacFactory.create(ruleFinder, Preconditions.checkNotNull(javaBuckConfig), args),
Preconditions.checkNotNull(javacOptions),
javacOptionsAmender);
}
}
}