com.google.devtools.build.lib.skyframe.TreeArtifactValue.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.skyframe.TreeArtifactValue.java

Source

// Copyright 2016 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.lib.skyframe;

import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
import com.google.devtools.build.lib.actions.cache.DigestUtils;
import com.google.devtools.build.lib.actions.cache.Metadata;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.skyframe.SkyValue;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;

/**
 * Value for TreeArtifacts, which contains a digest and the {@link FileArtifactValue}s of its child
 * {@link TreeFileArtifact}s.
 */
class TreeArtifactValue implements SkyValue {
    private static final Function<Artifact, PathFragment> PARENT_RELATIVE_PATHS = new Function<Artifact, PathFragment>() {
        @Override
        public PathFragment apply(Artifact artifact) {
            return artifact.getParentRelativePath();
        }
    };

    private final byte[] digest;
    private final Map<TreeFileArtifact, FileArtifactValue> childData;

    private TreeArtifactValue(byte[] digest, Map<TreeFileArtifact, FileArtifactValue> childData) {
        this.digest = digest;
        this.childData = ImmutableMap.copyOf(childData);
    }

    /**
     * Returns a TreeArtifactValue out of the given Artifact-relative path fragments
     * and their corresponding FileArtifactValues.
     */
    static TreeArtifactValue create(Map<TreeFileArtifact, FileArtifactValue> childFileValues) {
        Map<String, Metadata> digestBuilder = Maps.newHashMapWithExpectedSize(childFileValues.size());
        for (Map.Entry<TreeFileArtifact, FileArtifactValue> e : childFileValues.entrySet()) {
            digestBuilder.put(e.getKey().getParentRelativePath().getPathString(),
                    new Metadata(e.getValue().getDigest()));
        }

        return new TreeArtifactValue(DigestUtils.fromMetadata(digestBuilder).getDigestBytesUnsafe(),
                ImmutableMap.copyOf(childFileValues));
    }

    FileArtifactValue getSelfData() {
        return FileArtifactValue.createProxy(digest);
    }

    Metadata getMetadata() {
        return new Metadata(digest.clone());
    }

    Set<PathFragment> getChildPaths() {
        return ImmutableSet.copyOf(Iterables.transform(childData.keySet(), PARENT_RELATIVE_PATHS));
    }

    @Nullable
    byte[] getDigest() {
        return digest.clone();
    }

    Iterable<TreeFileArtifact> getChildren() {
        return childData.keySet();
    }

    Map<TreeFileArtifact, FileArtifactValue> getChildValues() {
        return childData;
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(digest);
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }

        if (!(other instanceof TreeArtifactValue)) {
            return false;
        }

        TreeArtifactValue that = (TreeArtifactValue) other;
        if (!Arrays.equals(digest, that.digest)) {
            return false;
        }

        return childData.equals(that.childData);
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(TreeArtifactValue.class).add("digest", digest).add("childData", childData)
                .toString();
    }

    /**
     * A TreeArtifactValue that represents a missing TreeArtifact.
     * This is occasionally useful because Java's concurrent collections disallow null members.
     */
    static final TreeArtifactValue MISSING_TREE_ARTIFACT = new TreeArtifactValue(null,
            ImmutableMap.<TreeFileArtifact, FileArtifactValue>of()) {
        @Override
        FileArtifactValue getSelfData() {
            throw new UnsupportedOperationException();
        }

        @Override
        Iterable<TreeFileArtifact> getChildren() {
            throw new UnsupportedOperationException();
        }

        @Override
        Map<TreeFileArtifact, FileArtifactValue> getChildValues() {
            throw new UnsupportedOperationException();
        }

        @Override
        Metadata getMetadata() {
            throw new UnsupportedOperationException();
        }

        @Override
        Set<PathFragment> getChildPaths() {
            throw new UnsupportedOperationException();
        }

        @Nullable
        @Override
        byte[] getDigest() {
            throw new UnsupportedOperationException();
        }

        @Override
        public int hashCode() {
            return 24; // my favorite number
        }

        @Override
        public boolean equals(Object other) {
            return this == other;
        }

        @Override
        public String toString() {
            return "MISSING_TREE_ARTIFACT";
        }
    };

    private static void explodeDirectory(Artifact treeArtifact, PathFragment pathToExplode,
            ImmutableSet.Builder<PathFragment> valuesBuilder) throws IOException {
        for (Path subpath : treeArtifact.getPath().getRelative(pathToExplode).getDirectoryEntries()) {
            PathFragment canonicalSubpathFragment = pathToExplode.getChild(subpath.getBaseName()).normalize();
            if (subpath.isDirectory()) {
                explodeDirectory(treeArtifact, pathToExplode.getChild(subpath.getBaseName()), valuesBuilder);
            } else if (subpath.isSymbolicLink()) {
                PathFragment linkTarget = subpath.readSymbolicLinkUnchecked();
                if (linkTarget.isAbsolute()) {
                    String errorMessage = String.format(
                            "A TreeArtifact may not contain absolute symlinks, found %s pointing to %s.", subpath,
                            linkTarget);
                    throw new IOException(errorMessage);
                }

                // We visit each path segment of the link target to catch any path traversal outside of the
                // TreeArtifact root directory. For example, for TreeArtifact a/b/c, it is possible to have
                // a symlink, a/b/c/sym_link that points to ../outside_dir/../c/link_target. Although this
                // symlink points to a file under the TreeArtifact, the link target traverses outside of the
                // TreeArtifact into a/b/outside_dir.
                PathFragment intermediatePath = canonicalSubpathFragment.getParentDirectory();
                for (String pathSegment : linkTarget.getSegments()) {
                    intermediatePath = intermediatePath.getRelative(pathSegment).normalize();
                    if (intermediatePath.containsUplevelReferences()) {
                        String errorMessage = String.format(
                                "A TreeArtifact may not contain relative symlinks whose target paths traverse "
                                        + "outside of the TreeArtifact, found %s pointing to %s.",
                                subpath, linkTarget);
                        throw new IOException(errorMessage);
                    }
                }
                valuesBuilder.add(canonicalSubpathFragment);
            } else if (subpath.isFile()) {
                valuesBuilder.add(canonicalSubpathFragment);
            } else {
                // We shouldn't ever reach here.
                throw new IllegalStateException("Could not determine type of file " + subpath);
            }
        }
    }

    /**
     * Recursively get all child files in a directory
     * (excluding child directories themselves, but including all files in them).
     * @throws IOException if there is any problem reading or validating outputs under the given
     *     tree artifact.
     */
    static Set<PathFragment> explodeDirectory(Artifact treeArtifact) throws IOException {
        ImmutableSet.Builder<PathFragment> explodedDirectory = ImmutableSet.builder();
        explodeDirectory(treeArtifact, PathFragment.EMPTY_FRAGMENT, explodedDirectory);
        return explodedDirectory.build();
    }
}