1 /* 2 * Copyright (C) 2023 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 package com.android.tradefed.build.content; 17 18 import com.android.tradefed.invoker.tracing.CloseableTraceScope; 19 import com.android.tradefed.log.LogUtil.CLog; 20 21 import com.google.gson.Gson; 22 import com.google.gson.JsonArray; 23 import com.google.gson.JsonElement; 24 import com.google.gson.JsonObject; 25 26 import java.io.BufferedReader; 27 import java.io.File; 28 import java.io.FileReader; 29 import java.io.IOException; 30 import java.util.ArrayList; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.stream.Collectors; 34 35 /** This describes the structure of the content and its descriptor as generated by the CAS tool */ 36 public class ArtifactDetails { 37 public String artifact; // Name of field has to match the cas tool output 38 public List<ArtifactFileDescriptor> details; // Name of field has to match the cas tool output 39 40 public static class ArtifactFileDescriptor { 41 public String digest; 42 public String path; 43 public long size; 44 } 45 parseFile(File input, String targetArtifact)46 public static ArtifactDetails parseFile(File input, String targetArtifact) throws IOException { 47 return parseFile(input, targetArtifact, null, null); 48 } 49 50 /** Parses cas_content_details.json and extract information for the entry considered. */ parseFile( File input, String targetArtifact, String baseBuildId, String currentBuildId)51 public static ArtifactDetails parseFile( 52 File input, String targetArtifact, String baseBuildId, String currentBuildId) 53 throws IOException { 54 try (CloseableTraceScope ignored = new CloseableTraceScope("parse_artifacts_details")) { 55 Gson gson = new Gson(); 56 JsonArray mainArray = null; 57 try (BufferedReader reader = new BufferedReader(new FileReader(input))) { 58 mainArray = gson.fromJson(reader, JsonArray.class); 59 } 60 String namedArtifact = targetArtifact; 61 if (currentBuildId != null && targetArtifact.contains(currentBuildId)) { 62 namedArtifact = namedArtifact.replace(currentBuildId, baseBuildId); 63 } 64 for (JsonElement e : mainArray) { 65 JsonObject o = e.getAsJsonObject(); 66 JsonElement name = o.asMap().get("artifact"); 67 if (name.getAsString().equals(namedArtifact)) { 68 ArtifactDetails d = gson.fromJson(e, ArtifactDetails.class); 69 if (d == null) { 70 throw new RuntimeException("Failed to parse for content."); 71 } 72 return d; 73 } 74 } 75 throw new RuntimeException(namedArtifact + " entry was not found."); 76 } 77 } 78 79 /** Obtain the list of modification between a base and the current build contents. */ diffContents( ArtifactDetails base, ArtifactDetails current)80 public static List<ArtifactFileDescriptor> diffContents( 81 ArtifactDetails base, ArtifactDetails current) { 82 try (CloseableTraceScope ignored = new CloseableTraceScope("diff_contents")) { 83 if (!base.artifact.equals(current.artifact)) { 84 CLog.w( 85 "Not comparing the same artifact entries ! %s != %s", 86 base.artifact, current.artifact); 87 } 88 Map<String, ArtifactFileDescriptor> mappingBase = 89 base.details.stream() 90 .map(e -> e) 91 .collect(Collectors.toMap(e -> e.path, e -> e)); 92 Map<String, ArtifactFileDescriptor> mappingCurrent = 93 current.details.stream() 94 .map(e -> e) 95 .collect(Collectors.toMap(e -> e.path, e -> e)); 96 List<ArtifactFileDescriptor> affected = new ArrayList<>(); 97 int modified = 0; 98 int deleted = 0; 99 int added = 0; 100 int unchanged = 0; 101 for (ArtifactFileDescriptor be : base.details) { 102 if (mappingCurrent.containsKey(be.path)) { 103 ArtifactFileDescriptor pe = mappingCurrent.get(be.path); 104 if (!be.digest.equals(pe.digest)) { 105 CLog.d("diff: %s", be.path); 106 modified++; 107 affected.add(pe); 108 } else { 109 unchanged++; 110 } 111 } else { 112 affected.add(be); 113 CLog.d("deleted: %s", be.path); 114 deleted++; 115 } 116 } 117 for (ArtifactFileDescriptor pe : current.details) { 118 if (!mappingBase.containsKey(pe.path)) { 119 CLog.d("added: %s", pe.path); 120 added++; 121 affected.add(pe); 122 } 123 } 124 CLog.d( 125 "Summary: unchanged:%s, modified:%s, deleted:%s, added:%s", 126 unchanged, modified, deleted, added); 127 return affected; 128 } 129 } 130 } 131