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.build.proto.BuildInformation; 20 import com.android.tradefed.build.proto.BuildInformation.BuildFile; 21 import com.android.tradefed.build.proto.BuildInformation.KeyBuildFilePair; 22 import com.android.tradefed.device.ITestDevice; 23 import com.android.tradefed.log.LogUtil.CLog; 24 import com.android.tradefed.util.FileUtil; 25 import com.android.tradefed.util.MultiMap; 26 import com.android.tradefed.util.UniqueMultiMap; 27 28 import com.google.common.base.MoreObjects; 29 import com.google.common.base.Objects; 30 31 import java.io.File; 32 import java.io.IOException; 33 import java.io.ObjectInputStream; 34 import java.io.ObjectOutputStream; 35 import java.lang.reflect.InvocationTargetException; 36 import java.util.Arrays; 37 import java.util.Collection; 38 import java.util.HashSet; 39 import java.util.Hashtable; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 44 /** 45 * Generic implementation of a {@link IBuildInfo} that should be associated 46 * with a {@link ITestDevice}. 47 */ 48 public class BuildInfo implements IBuildInfo { 49 private static final long serialVersionUID = BuildSerializedVersion.VERSION; 50 private static final String BUILD_ALIAS_KEY = "build_alias"; 51 52 private String mBuildId = UNKNOWN_BUILD_ID; 53 private String mTestTag = "stub"; 54 private String mBuildTargetName = "stub"; 55 private final UniqueMultiMap<String, String> mBuildAttributes = 56 new UniqueMultiMap<String, String>(); 57 // TODO: once deployed make non-transient 58 private Map<String, VersionedFile> mVersionedFileMap; 59 private transient MultiMap<String, VersionedFile> mVersionedFileMultiMap; 60 private String mBuildFlavor = null; 61 private String mBuildBranch = null; 62 private String mDeviceSerial = null; 63 /** Whether or not the build info describes a test resource */ 64 private boolean mTestResourceBuild = false; 65 66 /** File handling properties: Some files of the BuildInfo might requires special handling */ 67 private final Set<BuildInfoProperties> mProperties = new HashSet<>(); 68 69 private static final String[] FILE_NOT_TO_CLONE = 70 new String[] { 71 BuildInfoFileKey.TESTDIR_IMAGE.getFileKey(), 72 BuildInfoFileKey.HOST_LINKED_DIR.getFileKey(), 73 BuildInfoFileKey.TARGET_LINKED_DIR.getFileKey(), 74 }; 75 76 /** 77 * Creates a {@link BuildInfo} using default attribute values. 78 */ BuildInfo()79 public BuildInfo() { 80 mVersionedFileMap = new Hashtable<String, VersionedFile>(); 81 mVersionedFileMultiMap = new MultiMap<String, VersionedFile>(); 82 } 83 84 /** 85 * Creates a {@link BuildInfo} 86 * 87 * @param buildId the build id 88 * @param buildTargetName the build target name 89 */ BuildInfo(String buildId, String buildTargetName)90 public BuildInfo(String buildId, String buildTargetName) { 91 this(); 92 mBuildId = buildId; 93 mBuildTargetName = buildTargetName; 94 } 95 96 /** 97 * Creates a {@link BuildInfo}, populated with attributes given in another build. 98 * 99 * @param buildToCopy 100 */ BuildInfo(BuildInfo buildToCopy)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 getBuildId()115 public String getBuildId() { 116 return mBuildId; 117 } 118 119 /** 120 * {@inheritDoc} 121 */ 122 @Override setBuildId(String buildId)123 public void setBuildId(String buildId) { 124 mBuildId = buildId; 125 } 126 127 /** {@inheritDoc} */ 128 @Override isTestResourceBuild()129 public boolean isTestResourceBuild() { 130 return mTestResourceBuild; 131 } 132 133 /** {@inheritDoc} */ 134 @Override setTestResourceBuild(boolean testResourceBuild)135 public void setTestResourceBuild(boolean testResourceBuild) { 136 mTestResourceBuild = testResourceBuild; 137 } 138 139 /** 140 * {@inheritDoc} 141 */ 142 @Override setTestTag(String testTag)143 public void setTestTag(String testTag) { 144 mTestTag = testTag; 145 } 146 147 /** 148 * {@inheritDoc} 149 */ 150 @Override getTestTag()151 public String getTestTag() { 152 return mTestTag; 153 } 154 155 /** 156 * {@inheritDoc} 157 */ 158 @Override getDeviceSerial()159 public String getDeviceSerial() { 160 return mDeviceSerial; 161 } 162 163 /** 164 * {@inheritDoc} 165 */ 166 @Override getBuildAttributes()167 public Map<String, String> getBuildAttributes() { 168 return mBuildAttributes.getUniqueMap(); 169 } 170 171 /** {@inheritDoc} */ 172 @Override setProperties(BuildInfoProperties... properties)173 public void setProperties(BuildInfoProperties... properties) { 174 mProperties.clear(); 175 mProperties.addAll(Arrays.asList(properties)); 176 } 177 178 /** {@inheritDoc} */ 179 @Override getProperties()180 public Set<BuildInfoProperties> getProperties() { 181 return new HashSet<>(mProperties); 182 } 183 184 /** 185 * {@inheritDoc} 186 */ 187 @Override getBuildTargetName()188 public String getBuildTargetName() { 189 return mBuildTargetName; 190 } 191 192 /** 193 * {@inheritDoc} 194 */ 195 @Override addBuildAttribute(String attributeName, String attributeValue)196 public void addBuildAttribute(String attributeName, String attributeValue) { 197 mBuildAttributes.put(attributeName, attributeValue); 198 } 199 200 /** {@inheritDoc} */ 201 @Override addBuildAttributes(Map<String, String> buildAttributes)202 public void addBuildAttributes(Map<String, String> buildAttributes) { 203 mBuildAttributes.putAll(buildAttributes); 204 } 205 206 /** 207 * Helper method to copy build attributes, branch, and flavor from other build. 208 */ addAllBuildAttributes(BuildInfo build)209 protected void addAllBuildAttributes(BuildInfo build) { 210 mBuildAttributes.putAll(build.getAttributesMultiMap()); 211 setBuildFlavor(build.getBuildFlavor()); 212 setBuildBranch(build.getBuildBranch()); 213 setTestTag(build.getTestTag()); 214 setTestResourceBuild(build.isTestResourceBuild()); 215 } 216 getAttributesMultiMap()217 protected MultiMap<String, String> getAttributesMultiMap() { 218 return mBuildAttributes; 219 } 220 221 /** 222 * Helper method to copy all files from the other build. 223 * 224 * <p>Creates new hardlinks to the files so that each build will have a unique file path to the 225 * file. 226 * 227 * @throws IOException if an exception is thrown when creating the hardlink. 228 */ addAllFiles(BuildInfo build)229 protected void addAllFiles(BuildInfo build) throws IOException { 230 for (Map.Entry<String, VersionedFile> fileEntry : build.getVersionedFileMap().entrySet()) { 231 File origFile = fileEntry.getValue().getFile(); 232 if (applyBuildProperties(fileEntry.getValue(), build, this)) { 233 continue; 234 } 235 File copyFile; 236 if (origFile.isDirectory()) { 237 copyFile = FileUtil.createTempDir(fileEntry.getKey()); 238 FileUtil.recursiveHardlink(origFile, copyFile); 239 } else { 240 // Only using createTempFile to create a unique dest filename 241 copyFile = FileUtil.createTempFile(fileEntry.getKey(), 242 FileUtil.getExtension(origFile.getName())); 243 copyFile.delete(); 244 FileUtil.hardlinkFile(origFile, copyFile); 245 } 246 setFile(fileEntry.getKey(), copyFile, fileEntry.getValue().getVersion()); 247 } 248 } 249 250 /** 251 * Allow to apply some of the {@link com.android.tradefed.build.IBuildInfo.BuildInfoProperties} 252 * and possibly do a different handling. 253 * 254 * @param origFileConsidered The currently looked at {@link VersionedFile}. 255 * @param build the original build being cloned 256 * @param receiver the build receiving the information. 257 * @return True if we applied the properties and further handling should be skipped. False 258 * otherwise. 259 */ applyBuildProperties( VersionedFile origFileConsidered, IBuildInfo build, IBuildInfo receiver)260 protected boolean applyBuildProperties( 261 VersionedFile origFileConsidered, IBuildInfo build, IBuildInfo receiver) { 262 // If the no copy on sharding is set, that means the tests dir will be shared and should 263 // not be copied. 264 if (getProperties().contains(BuildInfoProperties.DO_NOT_COPY_ON_SHARDING)) { 265 for (String name : FILE_NOT_TO_CLONE) { 266 if (origFileConsidered.getFile().equals(build.getFile(name))) { 267 receiver.setFile( 268 name, origFileConsidered.getFile(), origFileConsidered.getVersion()); 269 return true; 270 } 271 } 272 } 273 if (getProperties().contains(BuildInfoProperties.DO_NOT_COPY_IMAGE_FILE)) { 274 if (origFileConsidered.equals(build.getVersionedFile(BuildInfoFileKey.DEVICE_IMAGE))) { 275 CLog.d("Skip copying of device_image."); 276 return true; 277 } 278 } 279 return false; 280 } 281 getVersionedFileMap()282 protected Map<String, VersionedFile> getVersionedFileMap() { 283 return mVersionedFileMultiMap.getUniqueMap(); 284 } 285 getVersionedFileMapFull()286 protected MultiMap<String, VersionedFile> getVersionedFileMapFull() { 287 return new MultiMap<>(mVersionedFileMultiMap); 288 } 289 290 /** {@inheritDoc} */ 291 @Override getVersionedFileKeys()292 public Set<String> getVersionedFileKeys() { 293 return mVersionedFileMultiMap.keySet(); 294 } 295 296 /** 297 * {@inheritDoc} 298 */ 299 @Override getFile(String name)300 public File getFile(String name) { 301 List<VersionedFile> fileRecords = mVersionedFileMultiMap.get(name); 302 if (fileRecords == null || fileRecords.isEmpty()) { 303 return null; 304 } 305 return fileRecords.get(0).getFile(); 306 } 307 308 /** {@inheritDoc} */ 309 @Override getFile(BuildInfoFileKey key)310 public File getFile(BuildInfoFileKey key) { 311 return getFile(key.getFileKey()); 312 } 313 314 /** {@inheritDoc} */ 315 @Override getVersionedFile(String name)316 public final VersionedFile getVersionedFile(String name) { 317 List<VersionedFile> fileRecords = mVersionedFileMultiMap.get(name); 318 if (fileRecords == null || fileRecords.isEmpty()) { 319 return null; 320 } 321 return fileRecords.get(0); 322 } 323 324 /** {@inheritDoc} */ 325 @Override getVersionedFile(BuildInfoFileKey key)326 public VersionedFile getVersionedFile(BuildInfoFileKey key) { 327 return getVersionedFile(key.getFileKey()); 328 } 329 330 /** {@inheritDoc} */ 331 @Override getVersionedFiles(BuildInfoFileKey key)332 public final List<VersionedFile> getVersionedFiles(BuildInfoFileKey key) { 333 if (!key.isList()) { 334 throw new UnsupportedOperationException( 335 String.format("Key %s does not support list of files.", key.getFileKey())); 336 } 337 return mVersionedFileMultiMap.get(key.getFileKey()); 338 } 339 340 /** 341 * {@inheritDoc} 342 */ 343 @Override getFiles()344 public Collection<VersionedFile> getFiles() { 345 return mVersionedFileMultiMap.values(); 346 } 347 348 /** 349 * {@inheritDoc} 350 */ 351 @Override getVersion(String name)352 public String getVersion(String name) { 353 List<VersionedFile> fileRecords = mVersionedFileMultiMap.get(name); 354 if (fileRecords == null || fileRecords.isEmpty()) { 355 return null; 356 } 357 return fileRecords.get(0).getVersion(); 358 } 359 360 /** {@inheritDoc} */ 361 @Override getVersion(BuildInfoFileKey key)362 public String getVersion(BuildInfoFileKey key) { 363 return getVersion(key.getFileKey()); 364 } 365 366 /** 367 * {@inheritDoc} 368 */ 369 @Override setFile(String name, File file, String version)370 public void setFile(String name, File file, String version) { 371 if (!mVersionedFileMap.containsKey(name)) { 372 mVersionedFileMap.put(name, new VersionedFile(file, version)); 373 } 374 if (mVersionedFileMultiMap.containsKey(name)) { 375 BuildInfoFileKey key = BuildInfoFileKey.fromString(name); 376 // If the key is a list, we will add it to the map. 377 if (key == null || !key.isList()) { 378 CLog.e( 379 "Device build already contains a file for %s in thread %s", 380 name, Thread.currentThread().getName()); 381 return; 382 } 383 } 384 mVersionedFileMultiMap.put(name, new VersionedFile(file, version)); 385 } 386 387 /** {@inheritDoc} */ 388 @Override setFile(BuildInfoFileKey key, File file, String version)389 public void setFile(BuildInfoFileKey key, File file, String version) { 390 setFile(key.getFileKey(), file, version); 391 } 392 393 /** 394 * {@inheritDoc} 395 */ 396 @Override cleanUp()397 public void cleanUp() { 398 for (VersionedFile fileRecord : mVersionedFileMultiMap.values()) { 399 FileUtil.recursiveDelete(fileRecord.getFile()); 400 } 401 mVersionedFileMultiMap.clear(); 402 } 403 404 /** {@inheritDoc} */ 405 @Override cleanUp(List<File> doNotClean)406 public void cleanUp(List<File> doNotClean) { 407 if (doNotClean == null) { 408 cleanUp(); 409 } 410 for (VersionedFile fileRecord : mVersionedFileMultiMap.values()) { 411 if (!doNotClean.contains(fileRecord.getFile())) { 412 FileUtil.recursiveDelete(fileRecord.getFile()); 413 } 414 } 415 refreshVersionedFiles(); 416 } 417 418 /** 419 * Run through all the {@link VersionedFile} and remove from the map the one that do not exists. 420 */ refreshVersionedFiles()421 private void refreshVersionedFiles() { 422 Set<String> keys = new HashSet<>(mVersionedFileMultiMap.keySet()); 423 for (String key : keys) { 424 for (VersionedFile file : mVersionedFileMultiMap.get(key)) { 425 if (!file.getFile().exists()) { 426 mVersionedFileMultiMap.remove(key); 427 } 428 } 429 } 430 } 431 432 /** 433 * {@inheritDoc} 434 */ 435 @Override clone()436 public IBuildInfo clone() { 437 BuildInfo copy = null; 438 try { 439 copy = 440 this.getClass() 441 .getDeclaredConstructor(String.class, String.class) 442 .newInstance(getBuildId(), getBuildTargetName()); 443 } catch (InstantiationException 444 | IllegalAccessException 445 | IllegalArgumentException 446 | InvocationTargetException 447 | NoSuchMethodException 448 | SecurityException e) { 449 CLog.e("Failed to clone the build info."); 450 throw new RuntimeException(e); 451 } 452 copy.addAllBuildAttributes(this); 453 copy.setProperties(this.getProperties().toArray(new BuildInfoProperties[0])); 454 try { 455 copy.addAllFiles(this); 456 } catch (IOException e) { 457 throw new RuntimeException(e); 458 } 459 copy.setBuildBranch(mBuildBranch); 460 copy.setBuildFlavor(mBuildFlavor); 461 462 return copy; 463 } 464 465 /** 466 * {@inheritDoc} 467 */ 468 @Override getBuildFlavor()469 public String getBuildFlavor() { 470 return mBuildFlavor; 471 } 472 473 /** 474 * {@inheritDoc} 475 */ 476 @Override setBuildFlavor(String buildFlavor)477 public void setBuildFlavor(String buildFlavor) { 478 mBuildFlavor = buildFlavor; 479 } 480 481 /** 482 * {@inheritDoc} 483 */ 484 @Override getBuildBranch()485 public String getBuildBranch() { 486 return mBuildBranch; 487 } 488 489 /** 490 * {@inheritDoc} 491 */ 492 @Override setBuildBranch(String branch)493 public void setBuildBranch(String branch) { 494 mBuildBranch = branch; 495 } 496 497 /** 498 * {@inheritDoc} 499 */ 500 @Override setDeviceSerial(String serial)501 public void setDeviceSerial(String serial) { 502 mDeviceSerial = serial; 503 } 504 505 /** 506 * {@inheritDoc} 507 */ 508 @Override hashCode()509 public int hashCode() { 510 return Objects.hashCode(mBuildAttributes, mBuildBranch, mBuildFlavor, mBuildId, 511 mBuildTargetName, mTestTag, mDeviceSerial); 512 } 513 514 /** 515 * {@inheritDoc} 516 */ 517 @Override equals(Object obj)518 public boolean equals(Object obj) { 519 if (this == obj) { 520 return true; 521 } 522 if (obj == null) { 523 return false; 524 } 525 if (getClass() != obj.getClass()) { 526 return false; 527 } 528 BuildInfo other = (BuildInfo) obj; 529 return Objects.equal(mBuildAttributes, other.mBuildAttributes) && 530 Objects.equal(mBuildBranch, other.mBuildBranch) && 531 Objects.equal(mBuildFlavor, other.mBuildFlavor) && 532 Objects.equal(mBuildId, other.mBuildId) && 533 Objects.equal(mBuildTargetName, other.mBuildTargetName) && 534 Objects.equal(mTestTag, other.mTestTag) && 535 Objects.equal(mDeviceSerial, other.mDeviceSerial); 536 } 537 538 /** 539 * {@inheritDoc} 540 */ 541 @Override toString()542 public String toString() { 543 return MoreObjects.toStringHelper(this.getClass()) 544 .omitNullValues() 545 .add("build_alias", getBuildAttributes().get(BUILD_ALIAS_KEY)) 546 .add("bid", mBuildId) 547 .add("target", mBuildTargetName) 548 .add("build_flavor", mBuildFlavor) 549 .add("branch", mBuildBranch) 550 .add("serial", mDeviceSerial) 551 .toString(); 552 } 553 554 /** {@inheritDoc} */ 555 @Override toProto()556 public BuildInformation.BuildInfo toProto() { 557 BuildInformation.BuildInfo.Builder protoBuilder = BuildInformation.BuildInfo.newBuilder(); 558 if (getBuildId() != null) { 559 protoBuilder.setBuildId(getBuildId()); 560 } 561 if (getBuildFlavor() != null) { 562 protoBuilder.setBuildFlavor(getBuildFlavor()); 563 } 564 if (getBuildBranch() != null) { 565 protoBuilder.setBranch(getBuildBranch()); 566 } 567 // Attributes 568 protoBuilder.putAllAttributes(getBuildAttributes()); 569 // Populate the versioned file 570 for (String fileKey : mVersionedFileMultiMap.keySet()) { 571 KeyBuildFilePair.Builder buildFile = KeyBuildFilePair.newBuilder(); 572 buildFile.setBuildFileKey(fileKey); 573 for (VersionedFile vFile : mVersionedFileMultiMap.get(fileKey)) { 574 BuildFile.Builder fileInformation = BuildFile.newBuilder(); 575 fileInformation.setVersion(vFile.getVersion()); 576 fileInformation.setLocalPath(vFile.getFile().getAbsolutePath()); 577 buildFile.addFile(fileInformation); 578 } 579 protoBuilder.addVersionedFile(buildFile); 580 } 581 protoBuilder.setBuildInfoClass(this.getClass().getCanonicalName()); 582 // Test resource 583 protoBuilder.setIsTestResource(isTestResourceBuild()); 584 return protoBuilder.build(); 585 } 586 587 /** Copy all the {@link VersionedFile} from a given build to this one. */ copyAllFileFrom(BuildInfo build)588 public final void copyAllFileFrom(BuildInfo build) { 589 MultiMap<String, VersionedFile> versionedMap = build.getVersionedFileMapFull(); 590 for (String versionedFile : versionedMap.keySet()) { 591 for (VersionedFile vFile : versionedMap.get(versionedFile)) { 592 setFile(versionedFile, vFile.getFile(), vFile.getVersion()); 593 } 594 } 595 } 596 597 /** Special serialization to handle the new underlying type. */ writeObject(ObjectOutputStream outputStream)598 private void writeObject(ObjectOutputStream outputStream) throws IOException { 599 outputStream.defaultWriteObject(); 600 outputStream.writeObject(mVersionedFileMultiMap); 601 } 602 603 /** Special java method that allows for custom deserialization. */ readObject(ObjectInputStream in)604 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 605 in.defaultReadObject(); 606 try { 607 mVersionedFileMultiMap = (MultiMap<String, VersionedFile>) in.readObject(); 608 } catch (IOException | ClassNotFoundException e) { 609 mVersionedFileMultiMap = new MultiMap<>(); 610 } 611 } 612 613 /** Inverse operation to {@link #toProto()} to get the instance back. */ fromProto(BuildInformation.BuildInfo protoBuild)614 public static IBuildInfo fromProto(BuildInformation.BuildInfo protoBuild) { 615 IBuildInfo buildInfo; 616 String buildClass = protoBuild.getBuildInfoClass(); 617 if (buildClass.isEmpty()) { 618 buildInfo = new BuildInfo(); 619 } else { 620 // Restore the original type of build info. 621 try { 622 buildInfo = (IBuildInfo) Class.forName(buildClass).newInstance(); 623 } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { 624 throw new RuntimeException(e); 625 } 626 } 627 // Build id 628 if (!protoBuild.getBuildId().isEmpty()) { 629 buildInfo.setBuildId(protoBuild.getBuildId()); 630 } 631 // Build Flavor 632 if (!protoBuild.getBuildFlavor().isEmpty()) { 633 buildInfo.setBuildFlavor(protoBuild.getBuildFlavor()); 634 } 635 // Build Branch 636 if (!protoBuild.getBranch().isEmpty()) { 637 buildInfo.setBuildBranch(protoBuild.getBranch()); 638 } 639 // Attributes 640 for (String key : protoBuild.getAttributes().keySet()) { 641 buildInfo.addBuildAttribute(key, protoBuild.getAttributes().get(key)); 642 } 643 // Versioned File 644 for (KeyBuildFilePair filePair : protoBuild.getVersionedFileList()) { 645 for (BuildFile buildFile : filePair.getFileList()) { 646 buildInfo.setFile( 647 filePair.getBuildFileKey(), 648 new File(buildFile.getLocalPath()), 649 buildFile.getVersion()); 650 } 651 } 652 // Test resource 653 buildInfo.setTestResourceBuild(protoBuild.getIsTestResource()); 654 return buildInfo; 655 } 656 657 /** 658 * Get test resource from a list of builds. 659 * 660 * @param testResourceBuildInfos An list of {@link IBuildInfo}. 661 * @param testResourceName the test resource name 662 * @return the test resource file. 663 */ getTestResource( List<IBuildInfo> testResourceBuildInfos, String testResourceName)664 public static File getTestResource( 665 List<IBuildInfo> testResourceBuildInfos, String testResourceName) { 666 if (testResourceBuildInfos == null) { 667 return null; 668 } 669 for (IBuildInfo buildInfo : testResourceBuildInfos) { 670 File testResourceFile = buildInfo.getFile(testResourceName); 671 if (testResourceFile != null) { 672 return testResourceFile; 673 } 674 } 675 return null; 676 } 677 } 678