xref: /aosp_15_r20/external/bazelbuild-rules_android/src/tools/mi/deployment_oss/deploy_binary.go (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
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