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