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.error.HarnessRuntimeException;
24 import com.android.tradefed.invoker.logger.InvocationMetricLogger;
25 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
26 import com.android.tradefed.invoker.tracing.CloseableTraceScope;
27 import com.android.tradefed.log.LogUtil.CLog;
28 import com.android.tradefed.result.error.InfraErrorIdentifier;
29 import com.android.tradefed.service.TradefedFeatureClient;
30 import com.android.tradefed.testtype.suite.ResolvePartialDownload;
31 import com.android.tradefed.util.FileUtil;
32 import com.android.tradefed.util.MultiMap;
33 import com.android.tradefed.util.UniqueMultiMap;
34 
35 import com.google.common.base.MoreObjects;
36 import com.google.common.base.Objects;
37 import com.google.common.base.Strings;
38 import com.proto.tradefed.feature.FeatureResponse;
39 
40 import java.io.File;
41 import java.io.IOException;
42 import java.io.ObjectInputStream;
43 import java.io.ObjectOutputStream;
44 import java.lang.reflect.InvocationTargetException;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.Collection;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.Hashtable;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Set;
54 import java.util.stream.Collectors;
55 
56 /**
57  * Generic implementation of a {@link IBuildInfo} that should be associated
58  * with a {@link ITestDevice}.
59  */
60 public class BuildInfo implements IBuildInfo {
61     private static final long serialVersionUID = BuildSerializedVersion.VERSION;
62     private static final String BUILD_ALIAS_KEY = "build_alias";
63 
64     private String mBuildId = UNKNOWN_BUILD_ID;
65     private String mTestTag = "stub";
66     private String mBuildTargetName = "stub";
67     private final UniqueMultiMap<String, String> mBuildAttributes =
68             new UniqueMultiMap<String, String>();
69     // TODO: once deployed make non-transient
70     private Map<String, VersionedFile> mVersionedFileMap;
71     private transient MultiMap<String, VersionedFile> mVersionedFileMultiMap;
72     private String mBuildFlavor = null;
73     private String mBuildBranch = null;
74     private String mDeviceSerial = null;
75 
76     /** File handling properties: Some files of the BuildInfo might requires special handling */
77     private final Set<BuildInfoProperties> mProperties = new HashSet<>();
78     /** Whether to stage remote files. */
79     private boolean mStageRemoteFile = true;
80 
81     private static final String[] FILE_NOT_TO_CLONE =
82             new String[] {
83                 BuildInfoFileKey.TESTDIR_IMAGE.getFileKey(),
84                 BuildInfoFileKey.HOST_LINKED_DIR.getFileKey(),
85                 BuildInfoFileKey.TARGET_LINKED_DIR.getFileKey(),
86             };
87 
88     /**
89      * Creates a {@link BuildInfo} using default attribute values.
90      */
BuildInfo()91     public BuildInfo() {
92         mVersionedFileMap = new Hashtable<String, VersionedFile>();
93         mVersionedFileMultiMap = new MultiMap<String, VersionedFile>();
94     }
95 
96     /**
97      * Creates a {@link BuildInfo}
98      *
99      * @param buildId the build id
100      * @param buildTargetName the build target name
101      */
BuildInfo(String buildId, String buildTargetName)102     public BuildInfo(String buildId, String buildTargetName) {
103         this();
104         mBuildId = buildId;
105         mBuildTargetName = buildTargetName;
106     }
107 
108     /**
109      * Creates a {@link BuildInfo}, populated with attributes given in another build.
110      *
111      * @param buildToCopy
112      */
BuildInfo(BuildInfo buildToCopy)113     BuildInfo(BuildInfo buildToCopy) {
114         this(buildToCopy.getBuildId(), buildToCopy.getBuildTargetName());
115         addAllBuildAttributes(buildToCopy);
116         try {
117             addAllFiles(buildToCopy);
118         } catch (IOException e) {
119             throw new RuntimeException(e);
120         }
121     }
122 
123     /**
124      * {@inheritDoc}
125      */
126     @Override
getBuildId()127     public String getBuildId() {
128         return mBuildId;
129     }
130 
131     /**
132      * {@inheritDoc}
133      */
134     @Override
setBuildId(String buildId)135     public void setBuildId(String buildId) {
136         mBuildId = buildId;
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         if (attributeValue == null) {
198             attributeValue = "";
199         }
200         mBuildAttributes.put(attributeName, attributeValue);
201     }
202 
203     @Override
removeBuildAttribute(String attributeName)204     public void removeBuildAttribute(String attributeName) {
205         mBuildAttributes.remove(attributeName);
206     }
207 
208     /** {@inheritDoc} */
209     @Override
addBuildAttributes(Map<String, String> buildAttributes)210     public void addBuildAttributes(Map<String, String> buildAttributes) {
211         mBuildAttributes.putAll(buildAttributes);
212     }
213 
214     /**
215      * Helper method to copy build attributes, branch, and flavor from other build.
216      */
addAllBuildAttributes(BuildInfo build)217     protected void addAllBuildAttributes(BuildInfo build) {
218         mBuildAttributes.putAll(build.getAttributesMultiMap());
219         setBuildFlavor(build.getBuildFlavor());
220         setBuildBranch(build.getBuildBranch());
221         setTestTag(build.getTestTag());
222     }
223 
getAttributesMultiMap()224     protected MultiMap<String, String> getAttributesMultiMap() {
225         return mBuildAttributes;
226     }
227 
228     /**
229      * Helper method to copy all files from the other build.
230      *
231      * <p>Creates new hardlinks to the files so that each build will have a unique file path to the
232      * file.
233      *
234      * @throws IOException if an exception is thrown when creating the hardlink.
235      */
addAllFiles(BuildInfo build)236     protected void addAllFiles(BuildInfo build) throws IOException {
237         for (Map.Entry<String, VersionedFile> fileEntry : build.getVersionedFileMap().entrySet()) {
238             File origFile = fileEntry.getValue().getFile();
239             if (applyBuildProperties(fileEntry.getValue(), build, this)) {
240                 continue;
241             }
242             if (fileEntry.getKey().startsWith(IBuildInfo.REMOTE_FILE_PREFIX)) {
243                 setFile(
244                         fileEntry.getKey(),
245                         new File(fileEntry.getValue().getFile().getPath()),
246                         fileEntry.getValue().getVersion());
247                 continue;
248             }
249             File copyFile;
250             if (origFile.isDirectory()) {
251                 copyFile = FileUtil.createTempDir(fileEntry.getKey());
252                 FileUtil.recursiveHardlink(origFile, copyFile, false);
253             } else {
254                 // Only using createTempFile to create a unique dest filename
255                 copyFile = FileUtil.createTempFile(fileEntry.getKey(),
256                         FileUtil.getExtension(origFile.getName()));
257                 copyFile.delete();
258                 FileUtil.hardlinkFile(origFile, copyFile);
259             }
260             setFile(fileEntry.getKey(), copyFile, fileEntry.getValue().getVersion());
261         }
262     }
263 
264     /**
265      * Allow to apply some of the {@link com.android.tradefed.build.IBuildInfo.BuildInfoProperties}
266      * and possibly do a different handling.
267      *
268      * @param origFileConsidered The currently looked at {@link VersionedFile}.
269      * @param build the original build being cloned
270      * @param receiver the build receiving the information.
271      * @return True if we applied the properties and further handling should be skipped. False
272      *     otherwise.
273      */
applyBuildProperties( VersionedFile origFileConsidered, IBuildInfo build, IBuildInfo receiver)274     protected boolean applyBuildProperties(
275             VersionedFile origFileConsidered, IBuildInfo build, IBuildInfo receiver) {
276         // If the no copy on sharding is set, that means the tests dir will be shared and should
277         // not be copied.
278         if (getProperties().contains(BuildInfoProperties.DO_NOT_COPY_ON_SHARDING)) {
279             for (String name : FILE_NOT_TO_CLONE) {
280                 if (origFileConsidered.getFile().equals(build.getFile(name))) {
281                     receiver.setFile(
282                             name, origFileConsidered.getFile(), origFileConsidered.getVersion());
283                     return true;
284                 }
285             }
286         }
287         if (getProperties().contains(BuildInfoProperties.DO_NOT_COPY_IMAGE_FILE)) {
288             if (origFileConsidered.equals(build.getVersionedFile(BuildInfoFileKey.DEVICE_IMAGE))) {
289                 CLog.d("Skip copying of device_image.");
290                 return true;
291             }
292         }
293         return false;
294     }
295 
getVersionedFileMap()296     protected Map<String, VersionedFile> getVersionedFileMap() {
297         return mVersionedFileMultiMap.getUniqueMap();
298     }
299 
getVersionedFileMapFull()300     protected MultiMap<String, VersionedFile> getVersionedFileMapFull() {
301         return new MultiMap<>(mVersionedFileMultiMap);
302     }
303 
304     /** {@inheritDoc} */
305     @Override
getVersionedFileKeys()306     public Set<String> getVersionedFileKeys() {
307         return mVersionedFileMultiMap.keySet();
308     }
309 
310     /**
311      * {@inheritDoc}
312      */
313     @Override
getFile(String name)314     public File getFile(String name) {
315         List<VersionedFile> fileRecords = mVersionedFileMultiMap.get(name);
316         if (fileRecords == null || fileRecords.isEmpty()) {
317             return null;
318         }
319         return fileRecords.get(0).getFile();
320     }
321 
322     /** {@inheritDoc} */
323     @Override
getFile(BuildInfoFileKey key)324     public File getFile(BuildInfoFileKey key) {
325         return getFile(key.getFileKey());
326     }
327 
328     /** {@inheritDoc} */
329     @Override
getVersionedFile(String name)330     public final VersionedFile getVersionedFile(String name) {
331         List<VersionedFile> fileRecords = mVersionedFileMultiMap.get(name);
332         if (fileRecords == null || fileRecords.isEmpty()) {
333             return null;
334         }
335         return fileRecords.get(0);
336     }
337 
338     /** {@inheritDoc} */
339     @Override
getVersionedFile(BuildInfoFileKey key)340     public VersionedFile getVersionedFile(BuildInfoFileKey key) {
341         return getVersionedFile(key.getFileKey());
342     }
343 
344     /** {@inheritDoc} */
345     @Override
getVersionedFiles(BuildInfoFileKey key)346     public final List<VersionedFile> getVersionedFiles(BuildInfoFileKey key) {
347         if (!key.isList()) {
348             throw new UnsupportedOperationException(
349                     String.format("Key %s does not support list of files.", key.getFileKey()));
350         }
351         return mVersionedFileMultiMap.get(key.getFileKey());
352     }
353 
354     /**
355      * {@inheritDoc}
356      */
357     @Override
getFiles()358     public Collection<VersionedFile> getFiles() {
359         return mVersionedFileMultiMap.values();
360     }
361 
362     /**
363      * {@inheritDoc}
364      */
365     @Override
getVersion(String name)366     public String getVersion(String name) {
367         List<VersionedFile> fileRecords = mVersionedFileMultiMap.get(name);
368         if (fileRecords == null || fileRecords.isEmpty()) {
369             return null;
370         }
371         return fileRecords.get(0).getVersion();
372     }
373 
374     /** {@inheritDoc} */
375     @Override
getVersion(BuildInfoFileKey key)376     public String getVersion(BuildInfoFileKey key) {
377         return getVersion(key.getFileKey());
378     }
379 
380     /**
381      * {@inheritDoc}
382      */
383     @Override
setFile(String name, File file, String version)384     public void setFile(String name, File file, String version) {
385         if (file == null) {
386             CLog.w("Tried to add to build info file name '%s' which is null.", name);
387             return;
388         }
389         if (!mVersionedFileMap.containsKey(name)) {
390             mVersionedFileMap.put(name, new VersionedFile(file, version));
391         }
392         if (mVersionedFileMultiMap.containsKey(name)) {
393             BuildInfoFileKey key = BuildInfoFileKey.fromString(name);
394             // If the key is a list, we will add it to the map.
395             if (key == null || !key.isList()) {
396                 CLog.e(
397                         "Device build already contains a file for %s in thread %s",
398                         name, Thread.currentThread().getName());
399                 return;
400             }
401         }
402         mVersionedFileMultiMap.put(name, new VersionedFile(file, version));
403     }
404 
405     /** {@inheritDoc} */
406     @Override
setFile(BuildInfoFileKey key, File file, String version)407     public void setFile(BuildInfoFileKey key, File file, String version) {
408         setFile(key.getFileKey(), file, version);
409     }
410 
411     /** {@inheritDoc} */
412     @Override
getAppPackageFiles()413     public List<VersionedFile> getAppPackageFiles() {
414         List<VersionedFile> origList = getVersionedFiles(BuildInfoFileKey.PACKAGE_FILES);
415         List<VersionedFile> listCopy = new ArrayList<VersionedFile>();
416         if (origList != null) {
417             listCopy.addAll(origList);
418         }
419         return listCopy;
420     }
421 
422     /** {@inheritDoc} */
423     @Override
addAppPackageFile(File appPackageFile, String version)424     public void addAppPackageFile(File appPackageFile, String version) {
425         setFile(BuildInfoFileKey.PACKAGE_FILES, appPackageFile, version);
426     }
427 
428     /**
429      * {@inheritDoc}
430      */
431     @Override
cleanUp()432     public void cleanUp() {
433         for (VersionedFile fileRecord : mVersionedFileMultiMap.values()) {
434             FileUtil.recursiveDelete(fileRecord.getFile());
435         }
436         mVersionedFileMultiMap.clear();
437     }
438 
439     /** {@inheritDoc} */
440     @Override
cleanUp(List<File> doNotClean)441     public void cleanUp(List<File> doNotClean) {
442         if (doNotClean == null) {
443             cleanUp();
444         }
445         for (VersionedFile fileRecord : mVersionedFileMultiMap.values()) {
446             if (!doNotClean.contains(fileRecord.getFile())) {
447                 FileUtil.recursiveDelete(fileRecord.getFile());
448             }
449         }
450         refreshVersionedFiles();
451     }
452 
453     /**
454      * Run through all the {@link VersionedFile} and remove from the map the one that do not exists.
455      */
refreshVersionedFiles()456     private void refreshVersionedFiles() {
457         Set<String> keys = new HashSet<>(mVersionedFileMultiMap.keySet());
458         for (String key : keys) {
459             for (VersionedFile file : mVersionedFileMultiMap.get(key)) {
460                 if (!file.getFile().exists()) {
461                     mVersionedFileMultiMap.remove(key);
462                 }
463             }
464         }
465     }
466 
467     /**
468      * {@inheritDoc}
469      */
470     @Override
clone()471     public IBuildInfo clone() {
472         BuildInfo copy = null;
473         try {
474             copy =
475                     this.getClass()
476                             .getDeclaredConstructor(String.class, String.class)
477                             .newInstance(getBuildId(), getBuildTargetName());
478         } catch (InstantiationException
479                 | IllegalAccessException
480                 | IllegalArgumentException
481                 | InvocationTargetException
482                 | NoSuchMethodException
483                 | SecurityException e) {
484             CLog.e("Failed to clone the build info.");
485             throw new RuntimeException(e);
486         }
487         copy.addAllBuildAttributes(this);
488         copy.setProperties(this.getProperties().toArray(new BuildInfoProperties[0]));
489         try {
490             copy.addAllFiles(this);
491         } catch (IOException e) {
492             throw new RuntimeException(e);
493         }
494         copy.setBuildBranch(mBuildBranch);
495         copy.setBuildFlavor(mBuildFlavor);
496         copy.setDeviceSerial(mDeviceSerial);
497 
498         return copy;
499     }
500 
501     /**
502      * {@inheritDoc}
503      */
504     @Override
getBuildFlavor()505     public String getBuildFlavor() {
506         return mBuildFlavor;
507     }
508 
509     /**
510      * {@inheritDoc}
511      */
512     @Override
setBuildFlavor(String buildFlavor)513     public void setBuildFlavor(String buildFlavor) {
514         mBuildFlavor = buildFlavor;
515     }
516 
517     /**
518      * {@inheritDoc}
519      */
520     @Override
getBuildBranch()521     public String getBuildBranch() {
522         return mBuildBranch;
523     }
524 
525     /**
526      * {@inheritDoc}
527      */
528     @Override
setBuildBranch(String branch)529     public void setBuildBranch(String branch) {
530         mBuildBranch = branch;
531     }
532 
533     /**
534      * {@inheritDoc}
535      */
536     @Override
setDeviceSerial(String serial)537     public void setDeviceSerial(String serial) {
538         mDeviceSerial = serial;
539     }
540 
541     /**
542      * {@inheritDoc}
543      */
544     @Override
hashCode()545     public int hashCode() {
546         return Objects.hashCode(mBuildAttributes, mBuildBranch, mBuildFlavor, mBuildId,
547                 mBuildTargetName, mTestTag, mDeviceSerial);
548     }
549 
550     /**
551      * {@inheritDoc}
552      */
553     @Override
equals(Object obj)554     public boolean equals(Object obj) {
555         if (this == obj) {
556             return true;
557         }
558         if (obj == null) {
559             return false;
560         }
561         if (getClass() != obj.getClass()) {
562             return false;
563         }
564         BuildInfo other = (BuildInfo) obj;
565         return Objects.equal(mBuildAttributes, other.mBuildAttributes)
566                 && Objects.equal(mBuildBranch, other.mBuildBranch)
567                 && Objects.equal(mBuildFlavor, other.mBuildFlavor)
568                 && Objects.equal(mBuildId, other.mBuildId)
569                 && Objects.equal(mBuildTargetName, other.mBuildTargetName)
570                 && Objects.equal(mTestTag, other.mTestTag)
571                 && Objects.equal(mDeviceSerial, other.mDeviceSerial);
572     }
573 
574     /**
575      * {@inheritDoc}
576      */
577     @Override
toString()578     public String toString() {
579         return MoreObjects.toStringHelper(this.getClass())
580                 .omitNullValues()
581                 .add("build_alias", getBuildAttributes().get(BUILD_ALIAS_KEY))
582                 .add("bid", mBuildId)
583                 .add("target", mBuildTargetName)
584                 .add("build_flavor", mBuildFlavor)
585                 .add("branch", mBuildBranch)
586                 .add("serial", mDeviceSerial)
587                 .toString();
588     }
589 
590     /** {@inheritDoc} */
591     @Override
toProto()592     public BuildInformation.BuildInfo toProto() {
593         BuildInformation.BuildInfo.Builder protoBuilder = BuildInformation.BuildInfo.newBuilder();
594         if (getBuildId() != null) {
595             protoBuilder.setBuildId(getBuildId());
596         }
597         if (getBuildFlavor() != null) {
598             protoBuilder.setBuildFlavor(getBuildFlavor());
599         }
600         if (getBuildBranch() != null) {
601             protoBuilder.setBranch(getBuildBranch());
602         }
603         // Attributes
604         protoBuilder.putAllAttributes(getBuildAttributes());
605         // Populate the versioned file
606         for (String fileKey : mVersionedFileMultiMap.keySet()) {
607             KeyBuildFilePair.Builder buildFile = KeyBuildFilePair.newBuilder();
608             buildFile.setBuildFileKey(fileKey);
609             for (VersionedFile vFile : mVersionedFileMultiMap.get(fileKey)) {
610                 BuildFile.Builder fileInformation = BuildFile.newBuilder();
611                 fileInformation.setVersion(Strings.nullToEmpty(vFile.getVersion()));
612                 if (fileKey.startsWith(IBuildInfo.REMOTE_FILE_PREFIX)) {
613                     // Remote file doesn't exist on local cache, so don't save absolute path.
614                     fileInformation.setLocalPath(vFile.getFile().toString());
615                 } else {
616                     fileInformation.setLocalPath(vFile.getFile().getAbsolutePath());
617                 }
618                 buildFile.addFile(fileInformation);
619             }
620             protoBuilder.addVersionedFile(buildFile);
621         }
622         protoBuilder.setBuildInfoClass(this.getClass().getCanonicalName());
623         return protoBuilder.build();
624     }
625 
626     /** Copy all the {@link VersionedFile} from a given build to this one. */
copyAllFileFrom(BuildInfo build)627     public final void copyAllFileFrom(BuildInfo build) {
628         MultiMap<String, VersionedFile> versionedMap = build.getVersionedFileMapFull();
629         for (String versionedFile : versionedMap.keySet()) {
630             for (VersionedFile vFile : versionedMap.get(versionedFile)) {
631                 setFile(versionedFile, vFile.getFile(), vFile.getVersion());
632             }
633         }
634     }
635 
636     /** Special serialization to handle the new underlying type. */
writeObject(ObjectOutputStream outputStream)637     private void writeObject(ObjectOutputStream outputStream) throws IOException {
638         outputStream.defaultWriteObject();
639         outputStream.writeObject(mVersionedFileMultiMap);
640     }
641 
642     /** Special java method that allows for custom deserialization. */
readObject(ObjectInputStream in)643     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
644         in.defaultReadObject();
645         try {
646             mVersionedFileMultiMap = (MultiMap<String, VersionedFile>) in.readObject();
647         } catch (IOException | ClassNotFoundException e) {
648             mVersionedFileMultiMap = new MultiMap<>();
649         }
650     }
651 
652     /** Inverse operation to {@link #toProto()} to get the instance back. */
fromProto(BuildInformation.BuildInfo protoBuild)653     public static IBuildInfo fromProto(BuildInformation.BuildInfo protoBuild) {
654         IBuildInfo buildInfo;
655         String buildClass = protoBuild.getBuildInfoClass();
656         if (buildClass.isEmpty()) {
657             buildInfo = new BuildInfo();
658         } else {
659             // Restore the original type of build info.
660             try {
661                 buildInfo =
662                         Class.forName(buildClass)
663                                 .asSubclass(BuildInfo.class)
664                                 .getDeclaredConstructor()
665                                 .newInstance();
666             } catch (InstantiationException
667                     | IllegalAccessException
668                     | ClassNotFoundException
669                     | InvocationTargetException
670                     | NoSuchMethodException e) {
671                 throw new RuntimeException(e);
672             }
673         }
674         // Build id
675         if (!protoBuild.getBuildId().isEmpty()) {
676             buildInfo.setBuildId(protoBuild.getBuildId());
677         }
678         // Build Flavor
679         if (!protoBuild.getBuildFlavor().isEmpty()) {
680             buildInfo.setBuildFlavor(protoBuild.getBuildFlavor());
681         }
682         // Build Branch
683         if (!protoBuild.getBranch().isEmpty()) {
684             buildInfo.setBuildBranch(protoBuild.getBranch());
685         }
686         // Attributes
687         for (String key : protoBuild.getAttributesMap().keySet()) {
688             buildInfo.addBuildAttribute(key, protoBuild.getAttributesMap().get(key));
689         }
690         // Versioned File
691         for (KeyBuildFilePair filePair : protoBuild.getVersionedFileList()) {
692             for (BuildFile buildFile : filePair.getFileList()) {
693                 buildInfo.setFile(
694                         filePair.getBuildFileKey(),
695                         new File(buildFile.getLocalPath()),
696                         buildFile.getVersion());
697             }
698         }
699         return buildInfo;
700     }
701 
702     /** {@inheritDoc} */
703     @Override
getRemoteFiles()704     public Set<File> getRemoteFiles() {
705         Set<File> remoteFiles = new HashSet<>();
706         for (String fileKey : mVersionedFileMultiMap.keySet()) {
707             if (fileKey.startsWith(IBuildInfo.REMOTE_FILE_PREFIX)) {
708                 // Remote file is not versioned, there should be only one entry.
709                 remoteFiles.add(mVersionedFileMultiMap.get(fileKey).get(0).getFile());
710             }
711         }
712         return remoteFiles;
713     }
714 
715     /** {@inheritDoc} */
716     @Override
stageRemoteFile(String fileName, File workingDir)717     public File stageRemoteFile(String fileName, File workingDir) {
718         if (!mStageRemoteFile) {
719             CLog.w("Staging remote files is disabled. Skip staging file: %s", fileName);
720             return null;
721         }
722         if (getRemoteFiles().isEmpty()) {
723             return null;
724         }
725         InvocationMetricLogger.addInvocationMetrics(
726                 InvocationMetricKey.STAGE_TESTS_INDIVIDUAL_DOWNLOADS, fileName);
727         List<String> includeFilters = Arrays.asList(String.format("/%s?($|/)", fileName));
728 
729         try (CloseableTraceScope stage = new CloseableTraceScope("stageRemoteFile:" + fileName);
730                 TradefedFeatureClient client = new TradefedFeatureClient()) {
731             Map<String, String> args = new HashMap<>();
732             args.put(ResolvePartialDownload.DESTINATION_DIR, workingDir.getAbsolutePath());
733             args.put(ResolvePartialDownload.INCLUDE_FILTERS, String.join(";", includeFilters));
734             // TODO: Remove exclude filter when we support not specifying it. For now put a
735             // placeholder that will exclude nothing.
736             args.put(ResolvePartialDownload.EXCLUDE_FILTERS, "doesntmatch");
737             args.put("use-cas", "false");
738             String remotePaths =
739                     getRemoteFiles().stream()
740                             .map(p -> p.toString())
741                             .collect(Collectors.joining(";"));
742             args.put(ResolvePartialDownload.REMOTE_PATHS, remotePaths);
743             long startTime = System.currentTimeMillis();
744             FeatureResponse rep =
745                     client.triggerFeature(
746                             ResolvePartialDownload.RESOLVE_PARTIAL_DOWNLOAD_FEATURE_NAME, args);
747             InvocationMetricLogger.addInvocationPairMetrics(
748                     InvocationMetricKey.STAGE_REMOTE_TIME, startTime, System.currentTimeMillis());
749             if (rep.hasErrorInfo()) {
750                 throw new HarnessRuntimeException(
751                         rep.getErrorInfo().getErrorTrace(),
752                         InfraErrorIdentifier.ARTIFACT_DOWNLOAD_ERROR);
753             }
754         }
755 
756         return FileUtil.findFile(workingDir, fileName);
757     }
758 
759     /** {@inheritDoc} */
760     @Override
allowStagingRemoteFile(boolean stageRemoteFile)761     public void allowStagingRemoteFile(boolean stageRemoteFile) {
762         mStageRemoteFile = stageRemoteFile;
763     }
764 }
765