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