blob: 2e078eecb628d2d7837e0e14e864b242b9ca78ee [file] [log] [blame]
/*
* 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.android;
import com.facebook.buck.java.CalculateAbiStep;
import com.facebook.buck.java.HasJavaAbi;
import com.facebook.buck.java.JavacOptions;
import com.facebook.buck.java.JavacStep;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.model.HasBuildTarget;
import com.facebook.buck.rules.AbiRule;
import com.facebook.buck.rules.AbstractBuildRule;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildOutputInitializer;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildableContext;
import com.facebook.buck.rules.InitializableFromDisk;
import com.facebook.buck.rules.OnDiskBuildInfo;
import com.facebook.buck.rules.RuleKey;
import com.facebook.buck.rules.Sha1HashCode;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.fs.MakeCleanDirectoryStep;
import com.facebook.buck.step.fs.WriteFileStep;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Buildable that takes in a list of {@link HasAndroidResourceDeps} and for each of these rules,
* first creates an {@code R.java} file using {@link MergeAndroidResourcesStep} and compiles it to
* generate a corresponding {@code R.class} file. These are called "dummy" {@code R.java} files
* since these are later merged together into a single {@code R.java} file by {@link AaptStep}.
*/
public class DummyRDotJava extends AbstractBuildRule
implements AbiRule, HasJavaAbi, InitializableFromDisk<DummyRDotJava.BuildOutput> {
private final ImmutableList<HasAndroidResourceDeps> androidResourceDeps;
private final JavacOptions javacOptions;
private final BuildOutputInitializer<BuildOutput> buildOutputInitializer;
public DummyRDotJava(
BuildRuleParams params,
SourcePathResolver resolver,
Set<HasAndroidResourceDeps> androidResourceDeps,
JavacOptions javacOptions) {
super(params, resolver);
// Sort the input so that we get a stable ABI for the same set of resources.
this.androidResourceDeps = FluentIterable.from(androidResourceDeps)
.toSortedList(HasBuildTarget.BUILD_TARGET_COMPARATOR);
this.javacOptions = javacOptions;
this.buildOutputInitializer = new BuildOutputInitializer<>(params.getBuildTarget(), this);
}
@Override
public ImmutableCollection<Path> getInputsToCompareToOutput() {
return ImmutableSet.of();
}
@Override
public ImmutableList<Step> getBuildSteps(
BuildContext context,
final BuildableContext buildableContext) {
ImmutableList.Builder<Step> steps = ImmutableList.builder();
final Path rDotJavaSrcFolder = getRDotJavaSrcFolder(getBuildTarget());
steps.add(new MakeCleanDirectoryStep(rDotJavaSrcFolder));
// Generate the .java files and record where they will be written in javaSourceFilePaths.
Set<Path> javaSourceFilePaths;
if (androidResourceDeps.isEmpty()) {
// In this case, the user is likely running a Robolectric test that does not happen to
// depend on any resources. However, if Robolectric doesn't find an R.java file, it flips
// out, so we have to create one, anyway.
// TODO(mbolin): Stop hardcoding com.facebook. This should match the package in the
// associated TestAndroidManifest.xml file.
Path emptyRDotJava = rDotJavaSrcFolder.resolve("com/facebook/R.java");
steps.add(new MakeCleanDirectoryStep(emptyRDotJava.getParent()));
steps.add(new WriteFileStep(
"package com.facebook;\n public class R {}\n",
emptyRDotJava));
javaSourceFilePaths = ImmutableSet.of(emptyRDotJava);
} else {
MergeAndroidResourcesStep mergeStep = MergeAndroidResourcesStep.createStepForDummyRDotJava(
androidResourceDeps,
rDotJavaSrcFolder);
steps.add(mergeStep);
javaSourceFilePaths =
ImmutableSet.copyOf(mergeStep.getRDotJavaFiles());
}
// Clear out the directory where the .class files will be generated.
final Path rDotJavaClassesFolder = getRDotJavaBinFolder();
steps.add(new MakeCleanDirectoryStep(rDotJavaClassesFolder));
Path pathToAbiOutputDir = getPathToAbiOutputDir(getBuildTarget());
steps.add(new MakeCleanDirectoryStep(pathToAbiOutputDir));
Path pathToAbiOutputFile = pathToAbiOutputDir.resolve("abi.jar");
// Compile the .java files.
final JavacStep javacStep =
RDotJava.createJavacStepForDummyRDotJavaFiles(
javaSourceFilePaths,
rDotJavaClassesFolder,
javacOptions,
getBuildTarget());
steps.add(javacStep);
buildableContext.recordArtifactsInDirectory(rDotJavaClassesFolder);
steps.add(new CalculateAbiStep(buildableContext, rDotJavaClassesFolder, pathToAbiOutputFile));
return steps.build();
}
@Override
public Sha1HashCode getAbiKeyForDeps() {
return HasAndroidResourceDeps.ABI_HASHER.apply(androidResourceDeps);
}
@Override
public RuleKey.Builder appendDetailsToRuleKey(RuleKey.Builder builder) {
return javacOptions.appendToRuleKey(builder, "javacOptions");
}
private static Path getRDotJavaSrcFolder(BuildTarget buildTarget) {
return BuildTargets.getBinPath(buildTarget, "__%s_rdotjava_src__");
}
private static Path getRDotJavaBinFolder(BuildTarget buildTarget) {
return BuildTargets.getBinPath(buildTarget, "__%s_rdotjava_bin__");
}
private static Path getPathToAbiOutputDir(BuildTarget buildTarget) {
return BuildTargets.getGenPath(buildTarget, "__%s_dummyrdotjava_abi__");
}
@Nullable
@Override
public Path getPathToOutputFile() {
return null;
}
public Path getRDotJavaBinFolder() {
return getRDotJavaBinFolder(getBuildTarget());
}
public ImmutableList<HasAndroidResourceDeps> getAndroidResourceDeps() {
return androidResourceDeps;
}
@VisibleForTesting
JavacOptions getJavacOptions() {
return javacOptions;
}
@Override
public BuildOutput initializeFromDisk(OnDiskBuildInfo onDiskBuildInfo) {
Optional<Sha1HashCode> abiKey = onDiskBuildInfo.getHash(AbiRule.ABI_KEY_ON_DISK_METADATA);
if (!abiKey.isPresent()) {
throw new IllegalStateException(String.format(
"Should not be initializing %s from disk if ABI key is not written",
this));
}
return new BuildOutput(abiKey.get());
}
@Override
public BuildOutputInitializer<BuildOutput> getBuildOutputInitializer() {
return buildOutputInitializer;
}
@Override
public Sha1HashCode getAbiKey() {
return buildOutputInitializer.getBuildOutput().rDotTxtSha1;
}
public static class BuildOutput {
@VisibleForTesting
final Sha1HashCode rDotTxtSha1;
public BuildOutput(Sha1HashCode rDotTxtSha1) {
this.rDotTxtSha1 = rDotTxtSha1;
}
}
}