aboutsummaryrefslogtreecommitdiff
path: root/src/tools/mi/deployment_oss/deploy_binary.go
blob: 503c81a67fab614a0eaff06da160601403152902 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// The deploy_binary command unpacks a workspace and deploys it to a device.
package main

import (
	"context"
	"flag"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strings"
	"time"

	glog "github.com/golang/glog"

	_ "src/common/golang/flagfile"
	"src/common/golang/flags"
	"src/common/golang/pprint"
	"src/tools/mi/deployment_oss/deployment"
)

var (
	adbArgs        = flags.NewStringList("adb_arg", "Options for the adb binary.")
	adbPath        = flag.String("adb", "/usr/bin/adb", "Path to the adb binary to use with mobile-install.")
	device         = flag.String("device", "", "The adb device serial number.")
	javaHome       = flag.String("java_home", "", "Path to JDK.")
	launchActivity = flag.String("launch_activity", "", "Activity to launch via am start -n package/.activity_to_launch.")
	appPackagePath = flag.String("manifest_package_name_path", "", "Path to file containing the manifest package name.")
	splits         = flags.NewStringList("splits", "The list of split apk paths.")
	start          = flag.String("start", "", "start_type from mobile-install.")
	startType      = flag.String("start_type", "", "start_type (deprecated, use --start).")
	useADBRoot     = flag.Bool("use_adb_root", true, "whether (true) or not (false) to use root permissions.")
	userID         = flag.Int("user", 0, "User id to install the app for.")

	// Studio deployer args
	studioDeployerPath = flag.String("studio_deployer", "", "Path to the Android Studio deployer.")
	optimisticInstall  = flag.Bool("optimistic-install", false, "If true, try to push changes to the device without installing.")
	studioVerboseLog   = flag.Bool("studio-verbose-log", false, "If true, enable verbose logging for the Android Studio deployer")

	// Need to double up on launch_app due as the built-in flag module does not support noXX for bool flags.
	// Some users are using --nolaunch_app, so we need to explicitly declare this flag
	launchApp   = flag.Bool("launch_app", true, "Launch the app after the sync is done.")
	noLaunchApp = flag.Bool("nolaunch_app", false, "Don't launch the app after the sync is done.")
	noDeploy    = flag.Bool("nodeploy", false, "Don't deploy or launch the app, useful for testing.")

	// Unused flags: Relevant only for Google-internal use cases, but need to exist in the flag parser
	buildID = flag.String("build_id", "", "The id of the build. Set by Bazel, the user should not use this flag.")
)

func resolveDeviceSerialAndPort(ctx context.Context, device string) (deviceSerialFlag, port string) {
	switch {
	case strings.Contains(device, ":tcp:"):
		parts := strings.SplitN(device, ":tcp:", 2)
		deviceSerialFlag = parts[0]
		port = parts[1]
	case strings.HasPrefix(device, "tcp:"):
		port = strings.TrimPrefix(device, "tcp:")
	default:
		deviceSerialFlag = device
	}
	return deviceSerialFlag, port
}

func determineDeviceSerial(deviceSerialFlag, deviceSerialEnv, deviceSerialADBArg string) string {
	var deviceSerial string
	switch {
	case deviceSerialFlag != "":
		deviceSerial = deviceSerialFlag
	case deviceSerialEnv != "":
		deviceSerial = deviceSerialEnv
	case deviceSerialADBArg != "":
		deviceSerial = deviceSerialADBArg
	}
	return deviceSerial
}

// ReadFile reads file from a given path
func readFile(path string) []byte {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		log.Fatalf("Error reading file %q: %v", path, err)
	}
	return data
}

func parseRepeatedFlag(n string, a *flags.StringList) {
	var l []string
	for _, f := range os.Args {
		if strings.HasPrefix(f, n) {
			l = append(l, strings.TrimPrefix(f, n))
		}
	}
	if len(l) > 1 {
		*a = l
	}
}

// Flush all the metrics to Streamz before the program exits.
func flushAndExitf(ctx context.Context, unused1, unused2, unused3, unused4, format string, args ...any) {
	glog.Exitf(format, args...)
}

func main() {
	ctx := context.Background()

	flag.Parse()

	pprint.Info("Deploying using OSS mobile-install!")

	if *noDeploy {
		pprint.Warning("--nodeploy set, not deploying application.")
		return
	}

	// Override --launch_app if --nolaunch_app is passed
	if *noLaunchApp {
		*launchApp = false
	}

	if *appPackagePath == "" {
		glog.Exitf("--manifest_package_name is required")
	}

	// Resolve the device serial and port.
	var deviceSerialFlag, port string
	if *device != "" {
		deviceSerialFlag, port = resolveDeviceSerialAndPort(ctx, *device)
	}
	deviceSerialEnv := os.Getenv("ANDROID_SERIAL")

	// TODO(b/66490815): Remove once adb_arg flag is deprecated.
	// Check for a device serial in adb_arg. If deviceSerial has not been specified, the value
	// found here will become the deviceSerial. If the deviceSerial has been specified the value
	// found here will be ignored but we will notify the user the device chosen.
	var deviceSerialADBArg string
	for i, arg := range *adbArgs {
		if strings.TrimSpace(arg) == "-s" && len(*adbArgs) > i+1 {
			deviceSerialADBArg = (*adbArgs)[i+1]
		}
	}

	// TODO(timpeut): Delete after the next blaze release
	// Ignore the device passed by --adb_arg if it matches the device passed by --device.
	if deviceSerialADBArg == *device {
		deviceSerialADBArg = ""
	}

	// Determine which value to use as the deviceSerial.
	deviceSerial := determineDeviceSerial(deviceSerialFlag, deviceSerialEnv, deviceSerialADBArg)

	// Warn user of the multiple device serial specification, that is not equal to the first.
	if (deviceSerialEnv != "" && deviceSerialEnv != deviceSerial) ||
		(deviceSerialADBArg != "" && deviceSerialADBArg != deviceSerial) {
		pprint.Warning("A device serial was specified more than once with --device, $ANDROID_SERIAL or --adb_arg, using %s.", deviceSerial)
	}

	appPackage := strings.TrimSpace(string(readFile(*appPackagePath)))

	startTime := time.Now()

	pprint.Info("Installing application using the Android Studio deployer ...")
	if err := deployment.AndroidStudioSync(ctx, deviceSerial, port, appPackage, *splits, *studioDeployerPath, *adbPath, *javaHome, *optimisticInstall, *studioVerboseLog, *userID, *useADBRoot); err != nil {
		flushAndExitf(ctx, "", "", "", "", "Got error installing using the Android Studio deployer: %v", err)
	}

	deploymentTime := time.Since(startTime)
	pprint.Info("Took %.2f seconds to sync changes", deploymentTime.Seconds())

	if *startType != "" {
		*start = *startType
	}

	// Wait for the debugger if debug mode selected
	if *start == "DEBUG" {
		waitCmd := exec.Command(*adbPath, "shell", "am", "set-debug-app", "-w", appPackage)
		if err := waitCmd.Wait(); err != nil {
			pprint.Error("Unable to wait for debugger: %s", err.Error())
		}
	}

	if *launchApp {
		pprint.Info("Finished deploying changes. Launching app")
		var launchCmd *exec.Cmd
		if *launchActivity != "" {
			launchCmd = exec.Command(*adbPath, "shell", "am", "start", "-a", "android.intent.action.MAIN", "-n", appPackage+"/"+*launchActivity)
		} else {
			launchCmd = exec.Command(*adbPath, "shell", "monkey", "-p", appPackage, "1")
			pprint.Warning(
				"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.")
		}

		if err := launchCmd.Run(); err != nil {
			pprint.Warning("Unable to launch app. Specify an activity with --launch_activity.")
			pprint.Warning("Original error: %s", err.Error())
		}
	} else {
		// Always stop the app since classloader needs to be reloaded.
		stopCmd := exec.Command(*adbPath, "shell", "am", "force-stop", appPackage)
		if err := stopCmd.Run(); err != nil {
			pprint.Error("Unable to stop app: %s", err.Error())
		}
	}
}