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 24 import com.google.auto.value.AutoValue; 25 26 import java.io.File; 27 import java.io.IOException; 28 import java.util.Arrays; 29 import java.util.LinkedHashMap; 30 import java.util.TreeSet; 31 32 /** A merkle tree representation as defined by the remote execution api. */ 33 @AutoValue 34 public abstract class MerkleTree { 35 36 /** Builds a merkle tree for the {@code directory}. */ buildFromDir(File directory)37 public static MerkleTree buildFromDir(File directory) throws IOException { 38 if (!directory.exists() || !directory.isDirectory()) { 39 throw new IllegalArgumentException( 40 String.format("Directory %s does not exist or is not a Directory!", directory)); 41 } 42 43 LinkedHashMap<Digest, File> digestToFile = new LinkedHashMap<>(); 44 LinkedHashMap<Digest, Directory> digestToSubdir = new LinkedHashMap<>(); 45 Directory.Builder rootBuilder = Directory.newBuilder(); 46 47 // Sort the files, so that two equivalent directory messages have matching digests. 48 TreeSet<File> files = new TreeSet<>(Arrays.asList(directory.listFiles())); 49 for (File f : files) { 50 if (f.isFile()) { 51 Digest digest = DigestCalculator.compute(f); 52 digestToFile.putIfAbsent(digest, f); 53 rootBuilder.addFiles( 54 FileNode.newBuilder() 55 .setDigest(digest) 56 .setName(f.getName()) 57 .setIsExecutable(f.canExecute())); 58 } 59 if (f.isDirectory()) { 60 MerkleTree childTree = buildFromDir(f); 61 rootBuilder.addDirectories( 62 DirectoryNode.newBuilder() 63 .setDigest(childTree.rootDigest()) 64 .setName(childTree.rootName())); 65 digestToSubdir.putIfAbsent(childTree.rootDigest(), childTree.root()); 66 childTree.digestToSubdir().forEach(digestToSubdir::putIfAbsent); 67 childTree.digestToFile().forEach(digestToFile::putIfAbsent); 68 } 69 } 70 71 Directory root = rootBuilder.build(); 72 return new AutoValue_MerkleTree( 73 directory.getName(), 74 root, 75 DigestCalculator.compute(root), 76 digestToFile, 77 digestToSubdir); 78 } 79 80 /** The name of the root {@link Directory} of this Merkle tree. */ rootName()81 public abstract String rootName(); 82 83 /** The root {@link Directory} of this Merkle tree. */ root()84 public abstract Directory root(); 85 86 /** 87 * The {@link Digest} of the root {@link Directory} of this Merkle tree. Note, this is only 88 * consumed by the cache client. 89 */ rootDigest()90 public abstract Digest rootDigest(); 91 92 /** The map of digests to files within this merkle tree. */ digestToFile()93 public abstract LinkedHashMap<Digest, File> digestToFile(); 94 95 /** The map of digests to Sub-directories within this merkle tree. */ digestToSubdir()96 public abstract LinkedHashMap<Digest, Directory> digestToSubdir(); 97 } 98