// Copyright 2015 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. package com.google.devtools.build.lib.rules.android; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.actions.ActionExecutionContext; import com.google.devtools.build.lib.actions.ActionKeyContext; import com.google.devtools.build.lib.actions.ActionOwner; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.ExecException; import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.util.Fingerprint; import com.google.devtools.common.options.EnumConverter; import com.google.devtools.common.options.Option; import com.google.devtools.common.options.OptionDocumentationCategory; import com.google.devtools.common.options.OptionEffectTag; import com.google.devtools.common.options.OptionsBase; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; /** * An action that writes the a parameter file to {@code incremental_install.py} based on the command * line arguments to {@code bazel mobile-install}. */ @Immutable // note that it accesses data non-hermetically during the execution phase public final class WriteAdbArgsAction extends AbstractFileWriteAction { private static final String GUID = "16720416-3c01-4b0a-a543-ead7e563a1ca"; /** Options of the {@code mobile-install} command pertaining to the way {@code adb} is invoked. */ public static final class Options extends OptionsBase { @Option( name = "adb", defaultValue = "", documentationCategory = OptionDocumentationCategory.TOOLCHAIN, effectTags = {OptionEffectTag.CHANGES_INPUTS}, help = "adb binary to use for the 'mobile-install' command. If unspecified, the one in " + "the Android SDK specified by the --android_sdk command line option (or the " + "default SDK if --android_sdk is not specified) is used." ) public String adb; @Option( name = "adb_arg", allowMultiple = true, defaultValue = "", documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.ACTION_COMMAND_LINES}, help = "Extra arguments to pass to adb. Usually used to designate a device to install to." ) public List adbArgs; @Option( name = "device", defaultValue = "", documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.ACTION_COMMAND_LINES}, help = "The adb device serial number. If not specified, the first device will be used." ) public String device; @Option( name = "incremental_install_verbosity", defaultValue = "", documentationCategory = OptionDocumentationCategory.LOGGING, effectTags = {OptionEffectTag.BAZEL_MONITORING}, help = "The verbosity for incremental install. Set to 1 for debug logging." ) public String incrementalInstallVerbosity; @Option( name = "start", converter = StartTypeConverter.class, defaultValue = "NO", documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.EXECUTION}, help = "How the app should be started after installing it. Set to WARM to preserve " + "and restore application state on incremental installs." ) public StartType start; @Option( name = "start_app", defaultValue = "null", documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.EXECUTION}, help = "Whether to start the app after installing it.", expansion = {"--start=COLD"} ) public Void startApp; @Option( name = "debug_app", defaultValue = "null", documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS, effectTags = {OptionEffectTag.EXECUTION}, help = "Whether to wait for the debugger before starting the app.", expansion = {"--start=DEBUG"} ) public Void debugApp; } public WriteAdbArgsAction(ActionOwner owner, Artifact outputFile) { super(owner, ImmutableList.of(), outputFile, false); } @Override public DeterministicWriter newDeterministicWriter(ActionExecutionContext ctx) throws IOException, InterruptedException, ExecException { Options options = ctx.getOptions().getOptions(Options.class); final List args = new ArrayList<>(options.adbArgs); final String adb = options.adb; final String device = options.device; final String incrementalInstallVerbosity = options.incrementalInstallVerbosity; final StartType start = options.start; final String userHomeDirectory = ctx.getContext(WriteAdbArgsActionContext.class).getUserHomeDirectory(); return new DeterministicWriter() { @Override public void writeOutputFile(OutputStream out) throws IOException { PrintStream ps = new PrintStream(out, false, "UTF-8"); if (!adb.isEmpty()) { ps.printf("--adb=%s\n", adb); } if (!device.isEmpty()) { args.add("-s"); args.add(device); } for (String arg : args) { ps.printf("--extra_adb_arg=%s\n", arg); } if (!incrementalInstallVerbosity.isEmpty()) { ps.printf("--verbosity=%s\n", incrementalInstallVerbosity); } ps.printf("--start=%s\n", start.name().toLowerCase()); if (userHomeDirectory != null) { ps.printf("--user_home_dir=%s\n", userHomeDirectory); } ps.flush(); } }; } @Override public boolean isVolatile() { return true; } @Override public boolean executeUnconditionally() { // In theory, we only need to re-execute if the --adb_args command line arg changes, but we // cannot express this. We also can't put the ADB args in the configuration, because that would // mean re-analysis on every change, and then the "build" command would also have this argument, // which is not optimal. return true; } @Override protected void computeKey(ActionKeyContext actionKeyContext, Fingerprint fp) { fp.addString(GUID); } /** Specifies how the app should be started/stopped. */ public enum StartType { /** The app will not be restarted after install. */ NO, /** The app will be restarted from a clean state after install. */ COLD, /** * The app will save its state before installing, and be restored from that state after * installing. */ WARM, /** The app will wait for debugger to attach before restarting from clean state after install */ DEBUG } /** Converter for the --start option. */ public static class StartTypeConverter extends EnumConverter { public StartTypeConverter() { super(StartType.class, "start type"); } } }