com.facebook.buck.android.DexProducedFromJavaLibrary.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.android.DexProducedFromJavaLibrary.java

Source

/*
 * 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.android.DexProducedFromJavaLibrary.BuildOutput;
import com.facebook.buck.dalvik.EstimateLinearAllocStep;
import com.facebook.buck.java.JavaLibrary;
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.AbstractBuildable;
import com.facebook.buck.rules.BuildContext;
import com.facebook.buck.rules.BuildOutputInitializer;
import com.facebook.buck.rules.Buildable;
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.step.AbstractExecutionStep;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.fs.MkdirStep;
import com.facebook.buck.step.fs.RmStep;
import com.google.common.annotations.VisibleForTesting;
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 com.google.common.collect.ImmutableSortedMap;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;

import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;

/**
 * {@link DexProducedFromJavaLibrary} is a {@link Buildable} that serves a
 * very specific purpose: it takes a {@link JavaLibrary} and dexes the output of the
 * {@link JavaLibrary} 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 DexProducedFromJavaLibrary extends AbstractBuildable
        implements AbiRule, HasBuildTarget, InitializableFromDisk<BuildOutput> {

    @VisibleForTesting
    static final String LINEAR_ALLOC_KEY_ON_DISK_METADATA = "linearalloc";

    private final BuildTarget buildTarget;
    private final JavaLibrary javaLibrary;
    private final BuildOutputInitializer<BuildOutput> buildOutputInitializer;

    @VisibleForTesting
    DexProducedFromJavaLibrary(BuildTarget buildTarget, JavaLibrary javaLibrary) {
        this.buildTarget = Preconditions.checkNotNull(buildTarget);
        this.javaLibrary = Preconditions.checkNotNull(javaLibrary);
        this.buildOutputInitializer = new BuildOutputInitializer<>(buildTarget, this);
    }

    @Override
    public Collection<Path> getInputsToCompareToOutput() {
        // The deps of this rule already capture all of the inputs that should affect the cache key.
        return ImmutableList.of();
    }

    @Override
    public RuleKey.Builder appendDetailsToRuleKey(RuleKey.Builder builder) {
        return builder;
    }

    @Override
    public List<Step> getBuildSteps(BuildContext context, final BuildableContext buildableContext) {
        ImmutableList.Builder<Step> steps = ImmutableList.builder();

        steps.add(new RmStep(getPathToDex(), /* shouldForceDeletion */ true));

        // Make sure that the buck-out/gen/ directory exists for this.buildTarget.
        steps.add(new MkdirStep(getPathToDex().getParent()));

        // If there are classes, run dx.
        final boolean hasClassesToDx = !javaLibrary.getClassNamesToHashes().isEmpty();
        final Supplier<Integer> linearAllocEstimate;
        if (hasClassesToDx) {
            Path pathToOutputFile = javaLibrary.getPathToOutputFile();
            EstimateLinearAllocStep estimate = new EstimateLinearAllocStep(pathToOutputFile);
            steps.add(estimate);
            linearAllocEstimate = estimate;

            // 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.
            DxStep dx = new DxStep(getPathToDex(), Collections.singleton(pathToOutputFile),
                    EnumSet.of(DxStep.Option.NO_OPTIMIZE, DxStep.Option.FORCE_JUMBO));
            steps.add(dx);
        } else {
            linearAllocEstimate = Suppliers.ofInstance(0);
        }

        // Run a step to record artifacts and metadata. The values recorded depend upon whether dx was
        // run.
        String stepName = hasClassesToDx ? "record_dx_success" : "record_empty_dx";
        AbstractExecutionStep recordArtifactAndMetadataStep = new AbstractExecutionStep(stepName) {
            @Override
            public int execute(ExecutionContext context) {
                if (hasClassesToDx) {
                    buildableContext.recordArtifact(getPathToDex());
                }

                buildableContext.addMetadata(LINEAR_ALLOC_KEY_ON_DISK_METADATA,
                        String.valueOf(linearAllocEstimate.get()));
                return 0;
            }
        };
        steps.add(recordArtifactAndMetadataStep);

        return steps.build();
    }

    @Override
    public BuildOutput initializeFromDisk(OnDiskBuildInfo onDiskBuildInfo) {
        int linearAllocEstimate = Integer
                .parseInt(onDiskBuildInfo.getValue(LINEAR_ALLOC_KEY_ON_DISK_METADATA).get());
        return new BuildOutput(linearAllocEstimate);
    }

    @Override
    public BuildOutputInitializer<BuildOutput> getBuildOutputInitializer() {
        return buildOutputInitializer;
    }

    @Override
    public BuildTarget getBuildTarget() {
        return buildTarget;
    }

    static class BuildOutput {
        private final int linearAllocEstimate;

        private BuildOutput(int linearAllocEstimate) {
            this.linearAllocEstimate = linearAllocEstimate;
        }
    }

    @Override
    @Nullable
    public Path getPathToOutputFile() {
        // A .dex file is not guaranteed to be generated, so we return null to be conservative.
        return null;
    }

    public Path getPathToDex() {
        return BuildTargets.getGenPath(buildTarget, "%s.dex.jar");
    }

    public boolean hasOutput() {
        return !getClassNames().isEmpty();
    }

    ImmutableSortedMap<String, HashCode> getClassNames() {
        // 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).
        return javaLibrary.getClassNamesToHashes();
    }

    int getLinearAllocEstimate() {
        return buildOutputInitializer.getBuildOutput().linearAllocEstimate;
    }

    /**
     * The only dep for this rule should be {@link #javaLibrary}. Therefore, the ABI key for the deps
     * of this buildable is the hash of the {@code .class} files for {@link #javaLibrary}.
     */
    @Override
    public Sha1HashCode getAbiKeyForDeps() {
        return computeAbiKey(javaLibrary.getClassNamesToHashes());
    }

    @VisibleForTesting
    static Sha1HashCode computeAbiKey(ImmutableSortedMap<String, HashCode> classNames) {
        Hasher hasher = Hashing.sha1().newHasher();
        for (Map.Entry<String, HashCode> entry : classNames.entrySet()) {
            hasher.putUnencodedChars(entry.getKey());
            hasher.putByte((byte) 0);
            hasher.putUnencodedChars(entry.getValue().toString());
            hasher.putByte((byte) 0);
        }
        return new Sha1HashCode(hasher.hash().toString());
    }
}