1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tradefed.cache;
18 
19 import build.bazel.remote.execution.v2.Digest;
20 import build.bazel.remote.execution.v2.Directory;
21 import build.bazel.remote.execution.v2.DirectoryNode;
22 import build.bazel.remote.execution.v2.FileNode;
23 import com.google.auto.value.AutoValue;
24 import java.io.File;
25 import java.io.IOException;
26 import java.util.Arrays;
27 import java.util.TreeSet;
28 
29 /** A merkle tree representation as defined by the remote execution api. */
30 @AutoValue
31 public abstract class MerkleTree {
32 
33     /** Builds a merkle tree for the {@code directory}. */
buildFromDir(File directory)34     public static MerkleTree buildFromDir(File directory) throws IOException {
35         if (!directory.exists() || !directory.isDirectory()) {
36             throw new IllegalArgumentException("Directory does not exist or is not a Directory!");
37         }
38 
39         Directory.Builder rootBuilder = Directory.newBuilder();
40 
41         // Sort the files, so that two equivalent directory messages have matching digests.
42         TreeSet<File> files = new TreeSet(Arrays.asList(directory.listFiles()));
43         for (File f : files) {
44             if (f.isFile()) {
45                 rootBuilder.addFiles(
46                         FileNode.newBuilder()
47                                 .setDigest(DigestCalculator.compute(f))
48                                 .setName(f.getName())
49                                 .setIsExecutable(f.canExecute()));
50             }
51             if (f.isDirectory()) {
52                 MerkleTree childTree = buildFromDir(f);
53                 rootBuilder.addDirectories(
54                         DirectoryNode.newBuilder()
55                                 .setDigest(childTree.rootDigest())
56                                 .setName(childTree.rootName()));
57             }
58         }
59 
60         return new AutoValue_MerkleTree(
61                 directory.getName(), DigestCalculator.compute(rootBuilder.build()));
62     }
63 
64     /** The name of the root {@link Directory} of this Merkle tree. */
rootName()65     public abstract String rootName();
66 
67     /**
68      * The {@link Digest} of the root {@link Directory} of this Merkle tree. Note, this is only
69      * consumed by the cache client.
70      */
rootDigest()71     public abstract Digest rootDigest();
72 }
73