/* * 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.jvm.java; import com.facebook.buck.cxx.CxxPlatform; import com.facebook.buck.cxx.NativeLinkables; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.jvm.core.JavaPackageFinder; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargets; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.rules.BuildContext; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.BuildableContext; import com.facebook.buck.rules.OnDiskBuildInfo; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.step.Step; import com.facebook.buck.step.fs.MakeCleanDirectoryStep; import com.facebook.buck.step.fs.MkdirStep; import com.facebook.buck.util.MoreCollectors; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.hash.HashCode; import java.io.IOException; import java.nio.file.Path; import java.util.List; import java.util.Optional; import java.util.regex.Pattern; import javax.annotation.Nullable; /** Common utilities for working with {@link JavaLibrary} objects. */ public class JavaLibraryRules { /** Utility class: do not instantiate. */ private JavaLibraryRules() {} public static void addCompileToJarSteps( BuildContext context, BuildableContext buildableContext, BuildRule rule, Optional<Path> outputJar, SourcePathRuleFinder ruleFinder, ImmutableSortedSet<SourcePath> srcs, ImmutableSortedSet<SourcePath> resources, ImmutableList<String> postprocessClassesCommands, ImmutableSortedSet<SourcePath> compileTimeClasspathSourcePaths, boolean trackClassUsage, @Nullable Path depFileRelativePath, CompileToJarStepFactory compileStepFactory, Optional<Path> resourcesRoot, Optional<SourcePath> manifestFile, ImmutableSet<Pattern> classesToRemoveFromJar, ImmutableList.Builder<Step> steps) { // Always create the output directory, even if there are no .java files to compile because there // might be resources that need to be copied there. BuildTarget target = rule.getBuildTarget(); Path outputDirectory = DefaultJavaLibrary.getClassesDir(target, rule.getProjectFilesystem()); steps.addAll(MakeCleanDirectoryStep.of(rule.getProjectFilesystem(), outputDirectory)); // We don't want to add provided to the declared or transitive deps, since they're only used at // compile time. ImmutableSortedSet<Path> compileTimeClasspathPaths = compileTimeClasspathSourcePaths .stream() .map(context.getSourcePathResolver()::getAbsolutePath) .collect(MoreCollectors.toImmutableSortedSet()); // If there are resources, then link them to the appropriate place in the classes directory. JavaPackageFinder finder = context.getJavaPackageFinder(); if (resourcesRoot.isPresent()) { finder = new ResourcesRootPackageFinder(resourcesRoot.get(), finder); } steps.add( new CopyResourcesStep( rule.getProjectFilesystem(), context.getSourcePathResolver(), ruleFinder, target, resources, outputDirectory, finder)); steps.addAll( MakeCleanDirectoryStep.of( rule.getProjectFilesystem(), DefaultJavaLibrary.getOutputJarDirPath(target, rule.getProjectFilesystem()))); // Only run javac if there are .java files to compile or we need to shovel the manifest file // into the built jar. if (!srcs.isEmpty()) { ClassUsageFileWriter usedClassesFileWriter; if (trackClassUsage) { usedClassesFileWriter = new DefaultClassUsageFileWriter(depFileRelativePath); buildableContext.recordArtifact(depFileRelativePath); } else { usedClassesFileWriter = NoOpClassUsageFileWriter.instance(); } // This adds the javac command, along with any supporting commands. Path pathToSrcsList = BuildTargets.getGenPath(rule.getProjectFilesystem(), rule.getBuildTarget(), "__%s__srcs"); steps.add(MkdirStep.of(rule.getProjectFilesystem(), pathToSrcsList.getParent())); Path scratchDir = BuildTargets.getGenPath( rule.getProjectFilesystem(), target, "lib__%s____working_directory"); steps.addAll(MakeCleanDirectoryStep.of(rule.getProjectFilesystem(), scratchDir)); Optional<Path> workingDirectory = Optional.of(scratchDir); ImmutableSortedSet<Path> javaSrcs = srcs.stream() .map(context.getSourcePathResolver()::getRelativePath) .collect(MoreCollectors.toImmutableSortedSet()); compileStepFactory.createCompileToJarStep( context, javaSrcs, target, context.getSourcePathResolver(), ruleFinder, rule.getProjectFilesystem(), compileTimeClasspathPaths, outputDirectory, workingDirectory, pathToSrcsList, postprocessClassesCommands, ImmutableSortedSet.of(outputDirectory), /* mainClass */ Optional.empty(), manifestFile.map(context.getSourcePathResolver()::getAbsolutePath), outputJar.get(), usedClassesFileWriter, /* output params */ steps, buildableContext, classesToRemoveFromJar); } if (outputJar.isPresent()) { Path output = outputJar.get(); // No source files, only resources if (srcs.isEmpty()) { steps.add( new JarDirectoryStep( rule.getProjectFilesystem(), output, ImmutableSortedSet.of(outputDirectory), /* mainClass */ null, manifestFile.map(context.getSourcePathResolver()::getAbsolutePath).orElse(null), true, classesToRemoveFromJar)); } buildableContext.recordArtifact(output); } } static void addAccumulateClassNamesStep( JavaLibrary javaLibrary, BuildableContext buildableContext, SourcePathResolver pathResolver, ImmutableList.Builder<Step> steps) { Path pathToClassHashes = JavaLibraryRules.getPathToClassHashes( javaLibrary.getBuildTarget(), javaLibrary.getProjectFilesystem()); steps.add(MkdirStep.of(javaLibrary.getProjectFilesystem(), pathToClassHashes.getParent())); steps.add( new AccumulateClassNamesStep( javaLibrary.getProjectFilesystem(), Optional.ofNullable(javaLibrary.getSourcePathToOutput()) .map(pathResolver::getRelativePath), pathToClassHashes)); buildableContext.recordArtifact(pathToClassHashes); } static JavaLibrary.Data initializeFromDisk( BuildTarget buildTarget, ProjectFilesystem filesystem, OnDiskBuildInfo onDiskBuildInfo) throws IOException { List<String> lines = onDiskBuildInfo.getOutputFileContentsByLine(getPathToClassHashes(buildTarget, filesystem)); ImmutableSortedMap<String, HashCode> classHashes = AccumulateClassNamesStep.parseClassHashes(lines); return new JavaLibrary.Data(classHashes); } private static Path getPathToClassHashes(BuildTarget buildTarget, ProjectFilesystem filesystem) { return BuildTargets.getGenPath(filesystem, buildTarget, "%s.classes.txt"); } /** * @return all the transitive native libraries a rule depends on, represented as a map from their * system-specific library names to their {@link SourcePath} objects. */ public static ImmutableMap<String, SourcePath> getNativeLibraries( Iterable<BuildRule> deps, final CxxPlatform cxxPlatform) throws NoSuchBuildTargetException { // Allow the transitive walk to find NativeLinkables through the BuildRuleParams deps of a // JavaLibrary or CalculateAbi object. The deps may be either one depending if we're compiling // against ABI rules or full rules Predicate<Object> traverse = r -> r instanceof JavaLibrary || r instanceof CalculateAbi; return NativeLinkables.getTransitiveSharedLibraries(cxxPlatform, deps, traverse); } public static ImmutableSortedSet<BuildRule> getAbiRules( BuildRuleResolver resolver, Iterable<BuildRule> inputs) throws NoSuchBuildTargetException { ImmutableSortedSet.Builder<BuildRule> abiRules = ImmutableSortedSet.naturalOrder(); for (BuildRule input : inputs) { if (input instanceof HasJavaAbi && ((HasJavaAbi) input).getAbiJar().isPresent()) { Optional<BuildTarget> abiJarTarget = ((HasJavaAbi) input).getAbiJar(); BuildRule abiJarRule = resolver.requireRule(abiJarTarget.get()); abiRules.add(abiJarRule); } } return abiRules.build(); } public static ImmutableSortedSet<SourcePath> getAbiSourcePaths( BuildRuleResolver resolver, Iterable<BuildRule> inputs) throws NoSuchBuildTargetException { return getAbiRules(resolver, inputs) .stream() .map(BuildRule::getSourcePathToOutput) .collect(MoreCollectors.toImmutableSortedSet()); } /** * Iterates the input BuildRules and translates them to their ABI rules when possible. This is * necessary when constructing a BuildRuleParams object, for example, where we want to translate * rules to their ABI rules, but not skip over BuildRules such as GenAidl, CxxLibrary, NdkLibrary, * AndroidResource, etc. These should still be returned from this method, but without translation. */ public static ImmutableSortedSet<BuildRule> getAbiRulesWherePossible( BuildRuleResolver resolver, Iterable<BuildRule> inputs) throws NoSuchBuildTargetException { ImmutableSortedSet.Builder<BuildRule> abiRules = ImmutableSortedSet.naturalOrder(); for (BuildRule dep : inputs) { if (dep instanceof HasJavaAbi) { Optional<BuildTarget> abiJarTarget = ((HasJavaAbi) dep).getAbiJar(); if (abiJarTarget.isPresent()) { abiRules.add(resolver.requireRule(abiJarTarget.get())); } } else { abiRules.add(dep); } } return abiRules.build(); } }