blob: e48521855439da1795ad6a6b6d5556d1bb690bb4 [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.AccumulateClassNames;
import com.facebook.buck.java.JavaLibraryRule;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.rules.AbstractBuildRuleBuilderParams;
import com.facebook.buck.rules.AbstractBuildable;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.BuildRuleType;
import com.facebook.buck.rules.Buildable;
import com.facebook.buck.rules.BuildableContext;
import com.facebook.buck.rules.OnDiskBuildInfo;
import com.facebook.buck.step.AbstractExecutionStep;
import com.facebook.buck.step.CompositeStep;
import com.facebook.buck.step.ConditionalStep;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.fs.FileExistsAndIsNotEmptyStep;
import com.facebook.buck.step.fs.MkdirStep;
import com.facebook.buck.step.fs.RmStep;
import com.facebook.buck.util.BuckConstant;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import javax.annotation.Nullable;
/**
* {@link DexProducedFromJavaLibraryThatContainsClassFiles} is a {@link Buildable} that serves a
* very specific purpose: it takes a {@link JavaLibraryRule} and the list of classes in the
* {@link JavaLibraryRule} (which is represented by an {@link AccumulateClassNames}), and dexes the
* output of the {@link JavaLibraryRule} if its list of classes is non-empty. Because it is
* expected to be used with pre-dexing, we always pass the {@code --force-jumbo} flag to {@code dx}
* in this buildable.
* <p>
* Most {@link Buildable}s can determine the (possibly null) path to their output file from their
* definition. This is an anomaly because we do not know whether this will write a {@code .dex} file
* until runtime. Unfortunately, because there is no such thing as an empty {@code .dex} file, we
* cannot write a meaningful "dummy .dex" if there are no class files to pass to {@code dx}.
*/
public class DexProducedFromJavaLibraryThatContainsClassFiles extends AbstractBuildable {
/**
* Key used with {@link OnDiskBuildInfo} to identify whether this {@link Buildable} has
* generated a {@code .dex} files. The only expected value to be associated with this key is
* {@code "true"}.
*/
private static final String HAS_DEX_OUTPUT_METADATA = "HAS_DEX_OUTPUT";
private final BuildTarget buildTarget;
private final AccumulateClassNames javaLibraryWithClassesList;
/** This {@link Supplier} will be defined and determined after this buildable is built. */
@Nullable
private Supplier<Boolean> hasOutputFile;
private DexProducedFromJavaLibraryThatContainsClassFiles(BuildTarget buildTarget,
AccumulateClassNames javaLibraryWithClassesList) {
this.buildTarget = Preconditions.checkNotNull(buildTarget);
this.javaLibraryWithClassesList = Preconditions.checkNotNull(javaLibraryWithClassesList);
}
public BuildTarget getBuildTarget() {
return buildTarget;
}
@Override
public Iterable<String> getInputsToCompareToOutput() {
// The deps of this rule already capture all of the inputs that should affect the cache key.
return ImmutableList.of();
}
@Override
public List<Step> getBuildSteps(BuildContext context, final BuildableContext buildableContext)
throws IOException {
ImmutableList.Builder<Step> steps = ImmutableList.builder();
steps.add(new RmStep(getPathToDex().toString(), /* shouldForceDeletion */ true));
// Make sure that the buck-out/gen/ directory exists for this.buildTarget.
steps.add(new MkdirStep(getPathToDex().getParent()));
// Check whether the list of class files in the JavaLibraryRule is empty.
FileExistsAndIsNotEmptyStep fileExistsStep = new FileExistsAndIsNotEmptyStep(
Paths.get(javaLibraryWithClassesList.getPathToOutputFile()));
hasOutputFile = fileExistsStep;
steps.add(fileExistsStep);
// To be conservative, use --force-jumbo for these intermediate .dex files so that they can be
// merged into a final classes.dex that uses jumbo instructions.
JavaLibraryRule javaLibraryRuleToDex = javaLibraryWithClassesList.getJavaLibraryRule();
DxStep dx = new DxStep(getPathToDex().toString(),
Collections.singleton(Paths.get(javaLibraryRuleToDex.getPathToOutputFile())),
EnumSet.of(DxStep.Option.NO_OPTIMIZE, DxStep.Option.FORCE_JUMBO));
AbstractExecutionStep recordArtifactStep = new AbstractExecutionStep("record dx success") {
@Override
public int execute(ExecutionContext context) {
buildableContext.recordArtifact(getPathToDex().getFileName());
buildableContext.addMetadata(HAS_DEX_OUTPUT_METADATA, "true");
return 0;
}
};
CompositeStep dxAndStore = new CompositeStep(ImmutableList.of(dx, recordArtifactStep));
// Make sure that there are .class files to dex before running dx.
ConditionalStep runDxIfThereAreClassFiles = new ConditionalStep(fileExistsStep, dxAndStore);
steps.add(runDxIfThereAreClassFiles);
return steps.build();
}
@Override
@Nullable
public String getPathToOutputFile() {
// A .dex file is not guaranteed to be generated, so we return null to be conservative.
return null;
}
public Path getPathToDex() {
return Paths.get(
BuckConstant.GEN_DIR,
buildTarget.getBasePath(),
buildTarget.getShortName() + ".dex.jar");
}
public boolean hasOutput() {
// TODO(mbolin): Assert that this Buildable has been built. Currently, there is no way to do
// that from a Buildable (but there is from an AbstractCachingBuildRule).
Preconditions.checkNotNull(hasOutputFile);
return hasOutputFile.get();
}
@Override
protected void initializeFromDisk(OnDiskBuildInfo onDiskBuildInfo) {
boolean hasOutput = "true".equals(onDiskBuildInfo.getValue(HAS_DEX_OUTPUT_METADATA).orNull());
this.hasOutputFile = Suppliers.ofInstance(hasOutput);
}
public static Builder newPreDexBuilder(AbstractBuildRuleBuilderParams params) {
return new Builder(params);
}
public static class Builder extends AbstractBuildable.Builder {
private AccumulateClassNames javaLibraryWithClassesList;
private Builder(AbstractBuildRuleBuilderParams params) {
super(params);
}
@Override
protected BuildRuleType getType() {
return BuildRuleType._PRE_DEX;
}
@Override
protected DexProducedFromJavaLibraryThatContainsClassFiles newBuildable(
BuildRuleParams params, BuildRuleResolver resolver) {
return new DexProducedFromJavaLibraryThatContainsClassFiles(params.getBuildTarget(),
javaLibraryWithClassesList);
}
public Builder setPathToClassNamesList(AccumulateClassNames javaLibraryWithClassesList) {
this.javaLibraryWithClassesList = javaLibraryWithClassesList;
return this;
}
}
}