• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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