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