1 /*
2  * Copyright (C) 2013 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 
17 package com.android.tradefed.build;
18 
19 import com.android.annotations.VisibleForTesting;
20 import com.android.tradefed.build.IBuildInfo.BuildInfoProperties;
21 import com.android.tradefed.config.Option;
22 import com.android.tradefed.config.OptionClass;
23 import com.android.tradefed.device.DeviceNotAvailableException;
24 import com.android.tradefed.device.ITestDevice;
25 import com.android.tradefed.device.StubDevice;
26 import com.android.tradefed.invoker.ExecutionFiles;
27 import com.android.tradefed.invoker.ExecutionFiles.FilesKey;
28 import com.android.tradefed.invoker.logger.CurrentInvocation;
29 import com.android.tradefed.invoker.logger.CurrentInvocation.InvocationInfo;
30 import com.android.tradefed.result.error.InfraErrorIdentifier;
31 import com.android.tradefed.util.BuildInfoUtil;
32 import com.android.tradefed.util.FileUtil;
33 
34 import java.io.File;
35 import java.io.IOException;
36 import java.util.LinkedHashMap;
37 import java.util.Map;
38 import java.util.Map.Entry;
39 
40 /**
41  * A {@link IDeviceBuildProvider} that bootstraps build info from the test device
42  *
43  * <p>
44  * This is typically used for devices with an externally supplied build, i.e. not generated by
45  * in-house build system. Certain information, specifically the branch, is not actually available
46  * from the device, therefore it's artificially generated.
47  *
48  * <p>All build meta data info comes from various ro.* property fields on device
49  *
50  * <p>Currently this build provider generates meta data as follows:
51  * <ul>
52  * <li>branch:
53  * $(ro.product.brand)-$(ro.product.name)-$(ro.product.device)-$(ro.build.version.release),
54  * for example:
55  * <ul>
56  *   <li>for Google Play edition Samsung S4 running Android 4.2: samsung-jgedlteue-jgedlte-4.2
57  *   <li>for Nexus 7 running Android 4.2: google-nakasi-grouper-4.2
58  * </ul>
59  * <li>build flavor: as provided by {@link ITestDevice#getBuildFlavor()}
60  * <li>build alias: as provided by {@link ITestDevice#getBuildAlias()}
61  * <li>build id: as provided by {@link ITestDevice#getBuildId()}
62  */
63 @OptionClass(alias = "bootstrap-build")
64 public class BootstrapBuildProvider implements IDeviceBuildProvider {
65 
66     @Option(name="build-target", description="build target name to supply.")
67     private String mBuildTargetName = "bootstrapped";
68 
69     @Option(name="branch", description="build branch name to supply.")
70     private String mBranch = null;
71 
72     @Option(
73         name = "build-id",
74         description = "Specify the build id to report instead of the one from the device."
75     )
76     private String mBuildId = null;
77 
78     @Option(name="shell-available-timeout",
79             description="Time to wait in seconds for device shell to become available. " +
80             "Default to 300 seconds.")
81     private long mShellAvailableTimeout = 5 * 60;
82 
83     @Option(name="tests-dir", description="Path to top directory of expanded tests zip")
84     private File mTestsDir = null;
85 
86     @Option(
87             name = "extra-file",
88             description =
89                     "The extra file to be added to the Build Provider. "
90                             + "Can be repeated. For example --extra-file file_key_1=/path/to/file")
91     private Map<String, File> mExtraFiles = new LinkedHashMap<>();
92 
93     @Override
getBuild()94     public IBuildInfo getBuild() throws BuildRetrievalError {
95         throw new UnsupportedOperationException("Call getBuild(ITestDevice)");
96     }
97 
98     @Override
cleanUp(IBuildInfo info)99     public void cleanUp(IBuildInfo info) {
100     }
101 
102     @Override
getBuild(ITestDevice device)103     public IBuildInfo getBuild(ITestDevice device) throws BuildRetrievalError,
104             DeviceNotAvailableException {
105         IBuildInfo info = new DeviceBuildInfo(mBuildId, mBuildTargetName);
106         addFiles(info, mExtraFiles);
107         info.setProperties(BuildInfoProperties.DO_NOT_COPY_ON_SHARDING);
108         if (!(device.getIDevice() instanceof StubDevice)) {
109             if (!device.waitForDeviceShell(mShellAvailableTimeout * 1000)) {
110                 throw new DeviceNotAvailableException(
111                         String.format(
112                                 "Shell did not become available in %d seconds",
113                                 mShellAvailableTimeout),
114                         device.getSerialNumber());
115             }
116         } else {
117             // In order to avoid issue with a null branch, use a placeholder stub for StubDevice.
118             mBranch = "stub";
119         }
120         BuildInfoUtil.bootstrapDeviceBuildAttributes(
121                 info,
122                 device,
123                 mBuildId,
124                 null /* override build flavor */,
125                 mBranch,
126                 null /* override build alias */);
127         if (mTestsDir != null && mTestsDir.isDirectory()) {
128             info.setFile("testsdir", mTestsDir, info.getBuildId());
129         }
130         // Avoid tests dir being null, by creating a temporary dir.
131         boolean createdTestDir = false;
132         if (mTestsDir == null) {
133             createdTestDir = true;
134             try {
135                 mTestsDir =
136                         FileUtil.createTempDir(
137                                 "bootstrap-test-dir",
138                                 CurrentInvocation.getInfo(InvocationInfo.WORK_FOLDER));
139             } catch (IOException e) {
140                 throw new BuildRetrievalError(
141                         e.getMessage(), e, InfraErrorIdentifier.FAIL_TO_CREATE_FILE);
142             }
143             ((IDeviceBuildInfo) info).setTestsDir(mTestsDir, "1");
144         }
145         if (getInvocationFiles() != null) {
146             getInvocationFiles()
147                     .put(
148                             FilesKey.TESTS_DIRECTORY,
149                             mTestsDir,
150                             !createdTestDir /* shouldNotDelete */);
151         }
152         return info;
153     }
154 
155     /**
156      * Add file to build info.
157      *
158      * @param buildInfo the {@link IBuildInfo} the build info
159      * @param fileMaps the {@link Map} of file_key and file object to be added to the buildInfo
160      */
addFiles(IBuildInfo buildInfo, Map<String, File> fileMaps)161     private void addFiles(IBuildInfo buildInfo, Map<String, File> fileMaps) {
162         for (final Entry<String, File> entry : fileMaps.entrySet()) {
163             buildInfo.setFile(entry.getKey(), entry.getValue(), "0");
164         }
165     }
166 
167     @VisibleForTesting
getInvocationFiles()168     ExecutionFiles getInvocationFiles() {
169         return CurrentInvocation.getInvocationFiles();
170     }
171 
getTestsDir()172     public final File getTestsDir() {
173         return mTestsDir;
174     }
175 }
176