1 /* 2 * Copyright (C) 2010 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; 17 18 import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey; 19 import com.android.tradefed.device.ITestDevice; 20 import com.android.tradefed.log.LogUtil.CLog; 21 import com.android.tradefed.util.FileUtil; 22 import com.android.tradefed.util.MultiMap; 23 import com.android.tradefed.util.UniqueMultiMap; 24 25 import com.google.common.base.MoreObjects; 26 import com.google.common.base.Objects; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.lang.reflect.InvocationTargetException; 31 import java.util.Arrays; 32 import java.util.Collection; 33 import java.util.HashSet; 34 import java.util.Hashtable; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Set; 38 39 /** 40 * Generic implementation of a {@link IBuildInfo} that should be associated 41 * with a {@link ITestDevice}. 42 */ 43 public class BuildInfo implements IBuildInfo { 44 private static final long serialVersionUID = BuildSerializedVersion.VERSION; 45 private static final String BUILD_ALIAS_KEY = "build_alias"; 46 47 private String mBuildId = UNKNOWN_BUILD_ID; 48 private String mTestTag = "stub"; 49 private String mBuildTargetName = "stub"; 50 private final UniqueMultiMap<String, String> mBuildAttributes = 51 new UniqueMultiMap<String, String>(); 52 private Map<String, VersionedFile> mVersionedFileMap; 53 private String mBuildFlavor = null; 54 private String mBuildBranch = null; 55 private String mDeviceSerial = null; 56 57 /** File handling properties: Some files of the BuildInfo might requires special handling */ 58 private final Set<BuildInfoProperties> mProperties = new HashSet<>(); 59 60 /** 61 * Creates a {@link BuildInfo} using default attribute values. 62 */ 63 public BuildInfo() { 64 mVersionedFileMap = new Hashtable<String, VersionedFile>(); 65 } 66 67 /** 68 * Creates a {@link BuildInfo} 69 * 70 * @param buildId the build id 71 * @param buildTargetName the build target name 72 */ 73 public BuildInfo(String buildId, String buildTargetName) { 74 mBuildId = buildId; 75 mBuildTargetName = buildTargetName; 76 mVersionedFileMap = new Hashtable<String, VersionedFile>(); 77 } 78 79 /** 80 * Creates a {@link BuildInfo} 81 * 82 * @param buildId the build id 83 * @param testTag the test tag name 84 * @param buildTargetName the build target name 85 * @deprecated use {@link #BuildInfo(String, String)} instead. test-tag should not be mandatory 86 * when instantiating the build info. 87 */ 88 @Deprecated 89 public BuildInfo(String buildId, String testTag, String buildTargetName) { 90 mBuildId = buildId; 91 mTestTag = testTag; 92 mBuildTargetName = buildTargetName; 93 mVersionedFileMap = new Hashtable<String, VersionedFile>(); 94 } 95 96 /** 97 * Creates a {@link BuildInfo}, populated with attributes given in another build. 98 * 99 * @param buildToCopy 100 */ 101 BuildInfo(BuildInfo buildToCopy) { 102 this(buildToCopy.getBuildId(), buildToCopy.getBuildTargetName()); 103 addAllBuildAttributes(buildToCopy); 104 try { 105 addAllFiles(buildToCopy); 106 } catch (IOException e) { 107 throw new RuntimeException(e); 108 } 109 } 110 111 /** 112 * {@inheritDoc} 113 */ 114 @Override 115 public String getBuildId() { 116 return mBuildId; 117 } 118 119 /** 120 * {@inheritDoc} 121 */ 122 @Override 123 public void setBuildId(String buildId) { 124 mBuildId = buildId; 125 } 126 127 /** 128 * {@inheritDoc} 129 */ 130 @Override 131 public void setTestTag(String testTag) { 132 mTestTag = testTag; 133 } 134 135 /** 136 * {@inheritDoc} 137 */ 138 @Override 139 public String getTestTag() { 140 return mTestTag; 141 } 142 143 /** 144 * {@inheritDoc} 145 */ 146 @Override 147 public String getDeviceSerial() { 148 return mDeviceSerial; 149 } 150 151 /** 152 * {@inheritDoc} 153 */ 154 @Override 155 public Map<String, String> getBuildAttributes() { 156 return mBuildAttributes.getUniqueMap(); 157 } 158 159 /** {@inheritDoc} */ 160 @Override 161 public void setProperties(BuildInfoProperties... properties) { 162 mProperties.clear(); 163 mProperties.addAll(Arrays.asList(properties)); 164 } 165 166 /** {@inheritDoc} */ 167 @Override 168 public Set<BuildInfoProperties> getProperties() { 169 return new HashSet<>(mProperties); 170 } 171 172 /** 173 * {@inheritDoc} 174 */ 175 @Override 176 public String getBuildTargetName() { 177 return mBuildTargetName; 178 } 179 180 /** 181 * {@inheritDoc} 182 */ 183 @Override 184 public void addBuildAttribute(String attributeName, String attributeValue) { 185 mBuildAttributes.put(attributeName, attributeValue); 186 } 187 188 /** 189 * Helper method to copy build attributes, branch, and flavor from other build. 190 */ 191 protected void addAllBuildAttributes(BuildInfo build) { 192 mBuildAttributes.putAll(build.getAttributesMultiMap()); 193 setBuildFlavor(build.getBuildFlavor()); 194 setBuildBranch(build.getBuildBranch()); 195 setTestTag(build.getTestTag()); 196 } 197 198 protected MultiMap<String, String> getAttributesMultiMap() { 199 return mBuildAttributes; 200 } 201 202 /** 203 * Helper method to copy all files from the other build. 204 * 205 * <p>Creates new hardlinks to the files so that each build will have a unique file path to the 206 * file. 207 * 208 * @throws IOException if an exception is thrown when creating the hardlink. 209 */ 210 protected void addAllFiles(BuildInfo build) throws IOException { 211 for (Map.Entry<String, VersionedFile> fileEntry : build.getVersionedFileMap().entrySet()) { 212 File origFile = fileEntry.getValue().getFile(); 213 if (applyBuildProperties(fileEntry.getValue(), build, this)) { 214 continue; 215 } 216 File copyFile; 217 if (origFile.isDirectory()) { 218 copyFile = FileUtil.createTempDir(fileEntry.getKey()); 219 FileUtil.recursiveHardlink(origFile, copyFile); 220 } else { 221 // Only using createTempFile to create a unique dest filename 222 copyFile = FileUtil.createTempFile(fileEntry.getKey(), 223 FileUtil.getExtension(origFile.getName())); 224 copyFile.delete(); 225 FileUtil.hardlinkFile(origFile, copyFile); 226 } 227 setFile(fileEntry.getKey(), copyFile, fileEntry.getValue().getVersion()); 228 } 229 } 230 231 /** 232 * Allow to apply some of the {@link com.android.tradefed.build.IBuildInfo.BuildInfoProperties} 233 * and possibly do a different handling. 234 * 235 * @param origFileConsidered The currently looked at {@link VersionedFile}. 236 * @param build the original build being cloned 237 * @param receiver the build receiving the information. 238 * @return True if we applied the properties and further handling should be skipped. False 239 * otherwise. 240 */ 241 protected boolean applyBuildProperties( 242 VersionedFile origFileConsidered, IBuildInfo build, IBuildInfo receiver) { 243 return false; 244 } 245 246 protected Map<String, VersionedFile> getVersionedFileMap() { 247 return mVersionedFileMap; 248 } 249 250 /** 251 * {@inheritDoc} 252 */ 253 @Override 254 public File getFile(String name) { 255 VersionedFile fileRecord = mVersionedFileMap.get(name); 256 if (fileRecord != null) { 257 return fileRecord.getFile(); 258 } 259 return null; 260 } 261 262 /** {@inheritDoc} */ 263 @Override 264 public File getFile(BuildInfoFileKey key) { 265 return getFile(key.getFileKey()); 266 } 267 268 /** {@inheritDoc} */ 269 @Override 270 public VersionedFile getVersionedFile(String name) { 271 return mVersionedFileMap.get(name); 272 } 273 274 /** {@inheritDoc} */ 275 @Override 276 public VersionedFile getVersionedFile(BuildInfoFileKey key) { 277 return getVersionedFile(key.getFileKey()); 278 } 279 280 /** 281 * {@inheritDoc} 282 */ 283 @Override 284 public Collection<VersionedFile> getFiles() { 285 return mVersionedFileMap.values(); 286 } 287 288 /** 289 * {@inheritDoc} 290 */ 291 @Override 292 public String getVersion(String name) { 293 VersionedFile fileRecord = mVersionedFileMap.get(name); 294 if (fileRecord != null) { 295 return fileRecord.getVersion(); 296 } 297 return null; 298 } 299 300 /** {@inheritDoc} */ 301 @Override 302 public String getVersion(BuildInfoFileKey key) { 303 return getVersion(key.getFileKey()); 304 } 305 306 /** 307 * {@inheritDoc} 308 */ 309 @Override 310 public void setFile(String name, File file, String version) { 311 if (mVersionedFileMap.containsKey(name)) { 312 CLog.e("Device build already contains a file for %s in thread %s", name, 313 Thread.currentThread().getName()); 314 return; 315 } 316 mVersionedFileMap.put(name, new VersionedFile(file, version)); 317 } 318 319 /** {@inheritDoc} */ 320 @Override 321 public void setFile(BuildInfoFileKey key, File file, String version) { 322 setFile(key.getFileKey(), file, version); 323 } 324 325 /** 326 * {@inheritDoc} 327 */ 328 @Override 329 public void cleanUp() { 330 for (VersionedFile fileRecord : mVersionedFileMap.values()) { 331 FileUtil.recursiveDelete(fileRecord.getFile()); 332 } 333 mVersionedFileMap.clear(); 334 } 335 336 /** {@inheritDoc} */ 337 @Override 338 public void cleanUp(List<File> doNotClean) { 339 if (doNotClean == null) { 340 cleanUp(); 341 } 342 for (VersionedFile fileRecord : mVersionedFileMap.values()) { 343 if (!doNotClean.contains(fileRecord.getFile())) { 344 FileUtil.recursiveDelete(fileRecord.getFile()); 345 } 346 } 347 refreshVersionedFiles(); 348 } 349 350 /** 351 * Run through all the {@link VersionedFile} and remove from the map the one that do not exists. 352 */ 353 private void refreshVersionedFiles() { 354 Set<String> keys = new HashSet<>(mVersionedFileMap.keySet()); 355 for (String key : keys) { 356 if (!mVersionedFileMap.get(key).getFile().exists()) { 357 mVersionedFileMap.remove(key); 358 } 359 } 360 } 361 362 /** 363 * {@inheritDoc} 364 */ 365 @Override 366 public IBuildInfo clone() { 367 BuildInfo copy = null; 368 try { 369 copy = 370 this.getClass() 371 .getDeclaredConstructor(String.class, String.class) 372 .newInstance(getBuildId(), getBuildTargetName()); 373 } catch (InstantiationException 374 | IllegalAccessException 375 | IllegalArgumentException 376 | InvocationTargetException 377 | NoSuchMethodException 378 | SecurityException e) { 379 CLog.e("Failed to clone the build info."); 380 throw new RuntimeException(e); 381 } 382 copy.addAllBuildAttributes(this); 383 copy.setProperties(this.getProperties().toArray(new BuildInfoProperties[0])); 384 try { 385 copy.addAllFiles(this); 386 } catch (IOException e) { 387 throw new RuntimeException(e); 388 } 389 copy.setBuildBranch(mBuildBranch); 390 copy.setBuildFlavor(mBuildFlavor); 391 392 return copy; 393 } 394 395 /** 396 * {@inheritDoc} 397 */ 398 @Override 399 public String getBuildFlavor() { 400 return mBuildFlavor; 401 } 402 403 /** 404 * {@inheritDoc} 405 */ 406 @Override 407 public void setBuildFlavor(String buildFlavor) { 408 mBuildFlavor = buildFlavor; 409 } 410 411 /** 412 * {@inheritDoc} 413 */ 414 @Override 415 public String getBuildBranch() { 416 return mBuildBranch; 417 } 418 419 /** 420 * {@inheritDoc} 421 */ 422 @Override 423 public void setBuildBranch(String branch) { 424 mBuildBranch = branch; 425 } 426 427 /** 428 * {@inheritDoc} 429 */ 430 @Override 431 public void setDeviceSerial(String serial) { 432 mDeviceSerial = serial; 433 } 434 435 /** 436 * {@inheritDoc} 437 */ 438 @Override 439 public int hashCode() { 440 return Objects.hashCode(mBuildAttributes, mBuildBranch, mBuildFlavor, mBuildId, 441 mBuildTargetName, mTestTag, mDeviceSerial); 442 } 443 444 /** 445 * {@inheritDoc} 446 */ 447 @Override 448 public boolean equals(Object obj) { 449 if (this == obj) { 450 return true; 451 } 452 if (obj == null) { 453 return false; 454 } 455 if (getClass() != obj.getClass()) { 456 return false; 457 } 458 BuildInfo other = (BuildInfo) obj; 459 return Objects.equal(mBuildAttributes, other.mBuildAttributes) && 460 Objects.equal(mBuildBranch, other.mBuildBranch) && 461 Objects.equal(mBuildFlavor, other.mBuildFlavor) && 462 Objects.equal(mBuildId, other.mBuildId) && 463 Objects.equal(mBuildTargetName, other.mBuildTargetName) && 464 Objects.equal(mTestTag, other.mTestTag) && 465 Objects.equal(mDeviceSerial, other.mDeviceSerial); 466 } 467 468 /** 469 * {@inheritDoc} 470 */ 471 @Override 472 public String toString() { 473 return MoreObjects.toStringHelper(this.getClass()) 474 .omitNullValues() 475 .add("build_alias", getBuildAttributes().get(BUILD_ALIAS_KEY)) 476 .add("bid", mBuildId) 477 .add("target", mBuildTargetName) 478 .add("build_flavor", mBuildFlavor) 479 .add("branch", mBuildBranch) 480 .add("serial", mDeviceSerial) 481 .toString(); 482 } 483 } 484