com.facebook.buck.java.AccumulateClassNames.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.java.AccumulateClassNames.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.java;

import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetPattern;
import com.facebook.buck.rules.AbiRule;
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.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.facebook.buck.util.BuckConstant;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
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.collect.ImmutableSortedSet;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;

/**
 * {@link Buildable} that writes the list of {@code .class} files found in a zip or directory to a
 * file.
 */
public class AccumulateClassNames extends AbstractBuildable {

    private static final Splitter CLASS_NAME_AND_HASH_SPLITTER = Splitter
            .on(AccumulateClassNamesStep.CLASS_NAME_HASH_CODE_SEPARATOR);

    private final JavaLibraryRule javaLibraryRule;
    private final Path pathToOutputFile;

    /**
     * This is not set until this {@link Buildable} is built.
     */
    @Nullable
    private Sha1HashCode abiKey;

    /**
     * This will contain the classes info discovered in the course of building this {@link Buildable}.
     * This {@link Supplier} will be defined and determined after this buildable is built.
     */
    @VisibleForTesting
    @Nullable
    Supplier<ImmutableSortedMap<String, HashCode>> classNames;

    @VisibleForTesting
    AccumulateClassNames(BuildTarget buildTarget, JavaLibraryRule javaLibraryRule) {
        Preconditions.checkNotNull(buildTarget);
        this.javaLibraryRule = Preconditions.checkNotNull(javaLibraryRule);
        this.pathToOutputFile = Paths.get(BuckConstant.GEN_DIR, buildTarget.getBasePath(),
                buildTarget.getShortName() + ".classes.txt");
    }

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

    @Override
    public String getPathToOutputFile() {
        return pathToOutputFile.toString();
    }

    public JavaLibraryRule getJavaLibraryRule() {
        return javaLibraryRule;
    }

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

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

        // Make sure that the output directory exists for the output file.
        steps.add(new MkdirStep(pathToOutputFile.getParent()));

        AccumulateClassNamesStep accumulateClassNamesStep = new AccumulateClassNamesStep(
                Paths.get(javaLibraryRule.getPathToOutputFile()), Paths.get(getPathToOutputFile()));
        classNames = accumulateClassNamesStep;
        steps.add(accumulateClassNamesStep);

        AbstractExecutionStep recordAbiStep = new AbstractExecutionStep("record_abi") {
            @Override
            public int execute(ExecutionContext context) {
                AccumulateClassNames.this.abiKey = computeAbiKey(classNames);
                buildableContext.addMetadata(AbiRule.ABI_KEY_ON_DISK_METADATA, abiKey.toString());
                return 0;
            }
        };
        steps.add(recordAbiStep);

        return steps.build();
    }

    /**
     * Sets both {@link #classNames} and {@link #abiKey}.
     */
    @Override
    protected void initializeFromDisk(OnDiskBuildInfo onDiskBuildInfo) {
        // Read the output file, which should now be in place because this rule was downloaded from
        // cache.
        List<String> lines;
        try {
            lines = onDiskBuildInfo.getOutputFileContentsByLine(this);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // Use the contents of the file to create the ImmutableSortedMap<String, HashCode>.
        ImmutableSortedMap.Builder<String, HashCode> classNamesBuilder = ImmutableSortedMap.naturalOrder();
        for (String line : lines) {
            List<String> parts = CLASS_NAME_AND_HASH_SPLITTER.splitToList(line);
            Preconditions.checkState(parts.size() == 2);
            String key = parts.get(0);
            HashCode value = HashCode.fromString(parts.get(1));
            classNamesBuilder.put(key, value);
        }
        this.classNames = Suppliers.ofInstance(classNamesBuilder.build());
        this.abiKey = onDiskBuildInfo.getHash(AbiRule.ABI_KEY_ON_DISK_METADATA).get();
    }

    public 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).
        Preconditions.checkNotNull(classNames);
        return classNames.get();
    }

    public Sha1HashCode getAbiKey() {
        Preconditions.checkNotNull(abiKey);
        return abiKey;
    }

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

    public static Builder newAccumulateClassNamesBuilder(AbstractBuildRuleBuilderParams params) {
        return new Builder(params);
    }

    public static class Builder extends AbstractBuildable.Builder {

        private JavaLibraryRule javaLibraryRule;

        private Builder(AbstractBuildRuleBuilderParams params) {
            super(params);
        }

        @Override
        protected BuildRuleType getType() {
            return BuildRuleType._CLASS_NAMES;
        }

        @Override
        protected AccumulateClassNames newBuildable(BuildRuleParams params, BuildRuleResolver resolver) {
            return new AccumulateClassNames(params.getBuildTarget(), javaLibraryRule);
        }

        public Builder setJavaLibraryToDex(JavaLibraryRule javaLibraryRule) {
            this.javaLibraryRule = javaLibraryRule;
            this.addDep(javaLibraryRule.getBuildTarget());
            return this;
        }

        @Override
        public Builder setBuildTarget(BuildTarget buildTarget) {
            super.setBuildTarget(buildTarget);
            return this;
        }

        @Override
        public Builder addDep(BuildTarget buildTarget) {
            super.addDep(buildTarget);
            return this;
        }

        @Override
        public Builder addVisibilityPattern(BuildTargetPattern visibilityPattern) {
            super.addVisibilityPattern(visibilityPattern);
            return this;
        }
    }
}