1// Copyright 2018 The Bazel Authors. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// The deploy_binary command unpacks a workspace and deploys it to a device. 16package main 17 18import ( 19 "context" 20 "flag" 21 "io/ioutil" 22 "log" 23 "os" 24 "os/exec" 25 "strings" 26 "time" 27 28 glog "github.com/golang/glog" 29 30 _ "src/common/golang/flagfile" 31 "src/common/golang/flags" 32 "src/common/golang/pprint" 33 "src/tools/mi/deployment_oss/deployment" 34) 35 36var ( 37 adbArgs = flags.NewStringList("adb_arg", "Options for the adb binary.") 38 adbPath = flag.String("adb", "/usr/bin/adb", "Path to the adb binary to use with mobile-install.") 39 device = flag.String("device", "", "The adb device serial number.") 40 javaHome = flag.String("java_home", "", "Path to JDK.") 41 launchActivity = flag.String("launch_activity", "", "Activity to launch via am start -n package/.activity_to_launch.") 42 appPackagePath = flag.String("manifest_package_name_path", "", "Path to file containing the manifest package name.") 43 splits = flags.NewStringList("splits", "The list of split apk paths.") 44 start = flag.String("start", "", "start_type from mobile-install.") 45 startType = flag.String("start_type", "", "start_type (deprecated, use --start).") 46 useADBRoot = flag.Bool("use_adb_root", true, "whether (true) or not (false) to use root permissions.") 47 userID = flag.Int("user", 0, "User id to install the app for.") 48 49 // Studio deployer args 50 studioDeployerPath = flag.String("studio_deployer", "", "Path to the Android Studio deployer.") 51 optimisticInstall = flag.Bool("optimistic-install", false, "If true, try to push changes to the device without installing.") 52 studioVerboseLog = flag.Bool("studio-verbose-log", false, "If true, enable verbose logging for the Android Studio deployer") 53 54 // Need to double up on launch_app due as the built-in flag module does not support noXX for bool flags. 55 // Some users are using --nolaunch_app, so we need to explicitly declare this flag 56 launchApp = flag.Bool("launch_app", true, "Launch the app after the sync is done.") 57 noLaunchApp = flag.Bool("nolaunch_app", false, "Don't launch the app after the sync is done.") 58 noDeploy = flag.Bool("nodeploy", false, "Don't deploy or launch the app, useful for testing.") 59 60 // Unused flags: Relevant only for Google-internal use cases, but need to exist in the flag parser 61 buildID = flag.String("build_id", "", "The id of the build. Set by Bazel, the user should not use this flag.") 62) 63 64func resolveDeviceSerialAndPort(ctx context.Context, device string) (deviceSerialFlag, port string) { 65 switch { 66 case strings.Contains(device, ":tcp:"): 67 parts := strings.SplitN(device, ":tcp:", 2) 68 deviceSerialFlag = parts[0] 69 port = parts[1] 70 case strings.HasPrefix(device, "tcp:"): 71 port = strings.TrimPrefix(device, "tcp:") 72 default: 73 deviceSerialFlag = device 74 } 75 return deviceSerialFlag, port 76} 77 78func determineDeviceSerial(deviceSerialFlag, deviceSerialEnv, deviceSerialADBArg string) string { 79 var deviceSerial string 80 switch { 81 case deviceSerialFlag != "": 82 deviceSerial = deviceSerialFlag 83 case deviceSerialEnv != "": 84 deviceSerial = deviceSerialEnv 85 case deviceSerialADBArg != "": 86 deviceSerial = deviceSerialADBArg 87 } 88 return deviceSerial 89} 90 91// ReadFile reads file from a given path 92func readFile(path string) []byte { 93 data, err := ioutil.ReadFile(path) 94 if err != nil { 95 log.Fatalf("Error reading file %q: %v", path, err) 96 } 97 return data 98} 99 100func parseRepeatedFlag(n string, a *flags.StringList) { 101 var l []string 102 for _, f := range os.Args { 103 if strings.HasPrefix(f, n) { 104 l = append(l, strings.TrimPrefix(f, n)) 105 } 106 } 107 if len(l) > 1 { 108 *a = l 109 } 110} 111 112// Flush all the metrics to Streamz before the program exits. 113func flushAndExitf(ctx context.Context, unused1, unused2, unused3, unused4, format string, args ...any) { 114 glog.Exitf(format, args...) 115} 116 117func main() { 118 ctx := context.Background() 119 120 flag.Parse() 121 122 pprint.Info("Deploying using OSS mobile-install!") 123 124 if *noDeploy { 125 pprint.Warning("--nodeploy set, not deploying application.") 126 return 127 } 128 129 // Override --launch_app if --nolaunch_app is passed 130 if *noLaunchApp { 131 *launchApp = false 132 } 133 134 if *appPackagePath == "" { 135 glog.Exitf("--manifest_package_name is required") 136 } 137 138 // Resolve the device serial and port. 139 var deviceSerialFlag, port string 140 if *device != "" { 141 deviceSerialFlag, port = resolveDeviceSerialAndPort(ctx, *device) 142 } 143 deviceSerialEnv := os.Getenv("ANDROID_SERIAL") 144 145 // TODO(b/66490815): Remove once adb_arg flag is deprecated. 146 // Check for a device serial in adb_arg. If deviceSerial has not been specified, the value 147 // found here will become the deviceSerial. If the deviceSerial has been specified the value 148 // found here will be ignored but we will notify the user the device chosen. 149 var deviceSerialADBArg string 150 for i, arg := range *adbArgs { 151 if strings.TrimSpace(arg) == "-s" && len(*adbArgs) > i+1 { 152 deviceSerialADBArg = (*adbArgs)[i+1] 153 } 154 } 155 156 // TODO(timpeut): Delete after the next blaze release 157 // Ignore the device passed by --adb_arg if it matches the device passed by --device. 158 if deviceSerialADBArg == *device { 159 deviceSerialADBArg = "" 160 } 161 162 // Determine which value to use as the deviceSerial. 163 deviceSerial := determineDeviceSerial(deviceSerialFlag, deviceSerialEnv, deviceSerialADBArg) 164 165 // Warn user of the multiple device serial specification, that is not equal to the first. 166 if (deviceSerialEnv != "" && deviceSerialEnv != deviceSerial) || 167 (deviceSerialADBArg != "" && deviceSerialADBArg != deviceSerial) { 168 pprint.Warning("A device serial was specified more than once with --device, $ANDROID_SERIAL or --adb_arg, using %s.", deviceSerial) 169 } 170 171 appPackage := strings.TrimSpace(string(readFile(*appPackagePath))) 172 173 startTime := time.Now() 174 175 pprint.Info("Installing application using the Android Studio deployer ...") 176 if err := deployment.AndroidStudioSync(ctx, deviceSerial, port, appPackage, *splits, *studioDeployerPath, *adbPath, *javaHome, *optimisticInstall, *studioVerboseLog, *userID, *useADBRoot); err != nil { 177 flushAndExitf(ctx, "", "", "", "", "Got error installing using the Android Studio deployer: %v", err) 178 } 179 180 deploymentTime := time.Since(startTime) 181 pprint.Info("Took %.2f seconds to sync changes", deploymentTime.Seconds()) 182 183 if *startType != "" { 184 *start = *startType 185 } 186 187 // Wait for the debugger if debug mode selected 188 if *start == "DEBUG" { 189 waitCmd := exec.Command(*adbPath, "shell", "am", "set-debug-app", "-w", appPackage) 190 if err := waitCmd.Wait(); err != nil { 191 pprint.Error("Unable to wait for debugger: %s", err.Error()) 192 } 193 } 194 195 if *launchApp { 196 pprint.Info("Finished deploying changes. Launching app") 197 var launchCmd *exec.Cmd 198 if *launchActivity != "" { 199 launchCmd = exec.Command(*adbPath, "shell", "am", "start", "-a", "android.intent.action.MAIN", "-n", appPackage+"/"+*launchActivity) 200 } else { 201 launchCmd = exec.Command(*adbPath, "shell", "monkey", "-p", appPackage, "1") 202 pprint.Warning( 203 "No or multiple main activities found, falling back to Monkey launcher. Specify the activity you want with `-- --launch_activity` or `-- --nolaunch_app` to launch nothing.") 204 } 205 206 if err := launchCmd.Run(); err != nil { 207 pprint.Warning("Unable to launch app. Specify an activity with --launch_activity.") 208 pprint.Warning("Original error: %s", err.Error()) 209 } 210 } else { 211 // Always stop the app since classloader needs to be reloaded. 212 stopCmd := exec.Command(*adbPath, "shell", "am", "force-stop", appPackage) 213 if err := stopCmd.Run(); err != nil { 214 pprint.Error("Unable to stop app: %s", err.Error()) 215 } 216 } 217} 218