summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Joe Onorato <joeo@google.com> 2016-10-21 17:55:42 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2016-10-21 17:55:46 +0000
commit2f99e60d71684d0f0296ce252a0683dc02cd93bd (patch)
treebb0621fe13b3387f11f339aa2edb88e54599ca6a
parent10842783e1955c4a0aa2e640cd4531b17a0b4b1d (diff)
parent0578cbc6c527bc09a38a0fcd8b9642c25c8ea023 (diff)
Merge changes from topic 'am_instrument'
* changes: Add a new build, install, test development (bit) tool am instrument gets protobuf Fix bad type codes in streaming proto compiler
-rw-r--r--cmds/am/Android.mk16
-rw-r--r--cmds/am/proto/instrumentation_data.proto66
-rw-r--r--cmds/am/src/com/android/commands/am/Am.java228
-rw-r--r--cmds/am/src/com/android/commands/am/Instrument.java435
-rw-r--r--tools/bit/Android.mk43
-rw-r--r--tools/bit/aapt.cpp270
-rw-r--r--tools/bit/aapt.h39
-rw-r--r--tools/bit/adb.cpp463
-rw-r--r--tools/bit/adb.h47
-rw-r--r--tools/bit/command.cpp183
-rw-r--r--tools/bit/command.h58
-rw-r--r--tools/bit/main.cpp981
-rw-r--r--tools/bit/make.cpp210
-rw-r--r--tools/bit/make.h50
-rw-r--r--tools/bit/print.cpp155
-rw-r--r--tools/bit/print.h39
-rw-r--r--tools/bit/util.cpp254
-rw-r--r--tools/bit/util.h83
-rw-r--r--tools/streaming_proto/main.cpp17
19 files changed, 3439 insertions, 198 deletions
diff --git a/cmds/am/Android.mk b/cmds/am/Android.mk
index f8350dce7f50..5586dd4e5b18 100644
--- a/cmds/am/Android.mk
+++ b/cmds/am/Android.mk
@@ -3,8 +3,11 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-proto-files-under, proto)
LOCAL_MODULE := am
+LOCAL_PROTOC_OPTIMIZE_TYPE := stream
include $(BUILD_JAVA_LIBRARY)
include $(CLEAR_VARS)
@@ -13,3 +16,14 @@ LOCAL_SRC_FILES := am
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_TAGS := optional
include $(BUILD_PREBUILT)
+
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+ $(call all-proto-files-under, proto)
+LOCAL_MODULE := libinstrumentation
+LOCAL_PROTOC_OPTIMIZE_TYPE := full
+LOCAL_EXPORT_C_INCLUDE_DIRS := \
+ $(call intermediates-dir-for,STATIC_LIBRARIES,libinstrumentation,HOST,,,)/proto/$(LOCAL_PATH)/proto
+include $(BUILD_HOST_STATIC_LIBRARY)
+
diff --git a/cmds/am/proto/instrumentation_data.proto b/cmds/am/proto/instrumentation_data.proto
new file mode 100644
index 000000000000..12a18a2a035f
--- /dev/null
+++ b/cmds/am/proto/instrumentation_data.proto
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.
+ */
+
+syntax = "proto2";
+package android.am;
+
+option java_package = "com.android.commands.am";
+
+message ResultsBundleEntry {
+ optional string key = 1;
+
+ optional string value_string = 2;
+ optional sint32 value_int = 3;
+ optional float value_float = 4;
+ optional double value_double = 5;
+ optional sint64 value_long = 6;
+ optional ResultsBundle value_bundle = 7;
+}
+
+message ResultsBundle {
+ repeated ResultsBundleEntry entries = 1;
+}
+
+message TestStatus {
+ optional sint32 result_code = 3;
+ optional ResultsBundle results = 4;
+}
+
+enum SessionStatusCode {
+ /**
+ * The command ran successfully. This does not imply that the tests passed.
+ */
+ SESSION_FINISHED = 0;
+
+ /**
+ * There was an unrecoverable error running the tests.
+ */
+ SESSION_ABORTED = 1;
+}
+
+message SessionStatus {
+ optional SessionStatusCode status_code = 1;
+ optional string error_text = 2;
+ optional sint32 result_code = 3;
+ optional ResultsBundle results = 4;
+}
+
+message Session {
+ repeated TestStatus test_status = 1;
+ optional SessionStatus session_status = 2;
+}
+
+
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index e197bfcb15f6..91a45496a5b5 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -1,20 +1,18 @@
/*
-**
-** Copyright 2007, The Android Open Source Project
-**
-** 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.
-*/
-
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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.android.commands.am;
@@ -235,6 +233,7 @@ public class Am extends BaseCommand {
" -e <NAME> <VALUE>: set argument <NAME> to <VALUE>. For test runners a\n" +
" common form is [-e <testrunner_flag> <value>[,<value>...]].\n" +
" -p <FILE>: write profiling data to <FILE>\n" +
+ " -m: Write output as protobuf (machine readable)\n" +
" -w: wait for instrumentation to finish before returning. Required for\n" +
" test runners.\n" +
" --user <USER_ID> | current: Specify user instrumentation runs in;\n" +
@@ -543,208 +542,43 @@ public class Am extends BaseCommand {
receiver.waitForFinish();
}
- private void runInstrument() throws Exception {
- String profileFile = null;
- boolean wait = false;
- boolean rawMode = false;
- boolean no_window_animation = false;
- int userId = UserHandle.USER_CURRENT;
- Bundle args = new Bundle();
- String argKey = null, argValue = null;
- IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
- String abi = null;
+ public void runInstrument() throws Exception {
+ Instrument instrument = new Instrument(mAm, mPm);
String opt;
while ((opt=nextOption()) != null) {
if (opt.equals("-p")) {
- profileFile = nextArgRequired();
+ instrument.profileFile = nextArgRequired();
} else if (opt.equals("-w")) {
- wait = true;
+ instrument.wait = true;
} else if (opt.equals("-r")) {
- rawMode = true;
+ instrument.rawMode = true;
+ } else if (opt.equals("-m")) {
+ instrument.proto = true;
} else if (opt.equals("-e")) {
- argKey = nextArgRequired();
- argValue = nextArgRequired();
- args.putString(argKey, argValue);
+ final String argKey = nextArgRequired();
+ final String argValue = nextArgRequired();
+ instrument.args.putString(argKey, argValue);
} else if (opt.equals("--no_window_animation")
|| opt.equals("--no-window-animation")) {
- no_window_animation = true;
+ instrument.noWindowAnimation = true;
} else if (opt.equals("--user")) {
- userId = parseUserArg(nextArgRequired());
+ instrument.userId = parseUserArg(nextArgRequired());
} else if (opt.equals("--abi")) {
- abi = nextArgRequired();
+ instrument.abi = nextArgRequired();
} else {
System.err.println("Error: Unknown option: " + opt);
return;
}
}
- if (userId == UserHandle.USER_ALL) {
+ if (instrument.userId == UserHandle.USER_ALL) {
System.err.println("Error: Can't start instrumentation with user 'all'");
return;
}
- String cnArg = nextArgRequired();
-
- ComponentName cn;
- if (cnArg.contains("/")) {
- cn = ComponentName.unflattenFromString(cnArg);
- if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
- } else {
- List<InstrumentationInfo> infos = mPm.queryInstrumentation(null, 0).getList();
-
- final int numInfos = infos == null ? 0: infos.size();
- List<ComponentName> cns = new ArrayList<>();
- for (int i = 0; i < numInfos; i++) {
- InstrumentationInfo info = infos.get(i);
-
- ComponentName c = new ComponentName(info.packageName, info.name);
- if (cnArg.equals(info.packageName)) {
- cns.add(c);
- }
- }
-
- if (cns.size() == 0) {
- throw new IllegalArgumentException("No instrumentation found for: " + cnArg);
- } else if (cns.size() == 1) {
- cn = cns.get(0);
- } else {
- StringBuilder cnsStr = new StringBuilder();
- final int numCns = cns.size();
- for (int i = 0; i < numCns; i++) {
- cnsStr.append(cns.get(i).flattenToString());
- cnsStr.append(", ");
- }
-
- // Remove last ", "
- cnsStr.setLength(cnsStr.length() - 2);
-
- throw new IllegalArgumentException("Found multiple instrumentations: "
- + cnsStr.toString());
- }
- }
-
- InstrumentationWatcher watcher = null;
- UiAutomationConnection connection = null;
- if (wait) {
- watcher = new InstrumentationWatcher();
- watcher.setRawOutput(rawMode);
- connection = new UiAutomationConnection();
- }
-
- float[] oldAnims = null;
- if (no_window_animation) {
- oldAnims = wm.getAnimationScales();
- wm.setAnimationScale(0, 0.0f);
- wm.setAnimationScale(1, 0.0f);
- }
-
- if (abi != null) {
- final String[] supportedAbis = Build.SUPPORTED_ABIS;
- boolean matched = false;
- for (String supportedAbi : supportedAbis) {
- if (supportedAbi.equals(abi)) {
- matched = true;
- break;
- }
- }
-
- if (!matched) {
- throw new AndroidException(
- "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi);
- }
- }
-
- if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, abi)) {
- throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
- }
-
- if (watcher != null) {
- if (!watcher.waitForFinish()) {
- System.out.println("INSTRUMENTATION_ABORTED: System has crashed.");
- }
- }
-
- if (oldAnims != null) {
- wm.setAnimationScales(oldAnims);
- }
- }
-
- private class InstrumentationWatcher extends IInstrumentationWatcher.Stub {
- private boolean mFinished = false;
- private boolean mRawMode = false;
-
- /**
- * Set or reset "raw mode". In "raw mode", all bundles are dumped. In "pretty mode",
- * if a bundle includes Instrumentation.REPORT_KEY_STREAMRESULT, just print that.
- * @param rawMode true for raw mode, false for pretty mode.
- */
- public void setRawOutput(boolean rawMode) {
- mRawMode = rawMode;
- }
-
- @Override
- public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) {
- synchronized (this) {
- // pretty printer mode?
- String pretty = null;
- if (!mRawMode && results != null) {
- pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
- }
- if (pretty != null) {
- System.out.print(pretty);
- } else {
- if (results != null) {
- for (String key : results.keySet()) {
- System.out.println(
- "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
- }
- }
- System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
- }
- notifyAll();
- }
- }
+ instrument.componentNameArg = nextArgRequired();
- @Override
- public void instrumentationFinished(ComponentName name, int resultCode,
- Bundle results) {
- synchronized (this) {
- // pretty printer mode?
- String pretty = null;
- if (!mRawMode && results != null) {
- pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
- }
- if (pretty != null) {
- System.out.println(pretty);
- } else {
- if (results != null) {
- for (String key : results.keySet()) {
- System.out.println(
- "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
- }
- }
- System.out.println("INSTRUMENTATION_CODE: " + resultCode);
- }
- mFinished = true;
- notifyAll();
- }
- }
-
- public boolean waitForFinish() {
- synchronized (this) {
- while (!mFinished) {
- try {
- if (!mAm.asBinder().pingBinder()) {
- return false;
- }
- wait(1000);
- } catch (InterruptedException e) {
- throw new IllegalStateException(e);
- }
- }
- }
- return true;
- }
+ instrument.run();
}
}
diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java
new file mode 100644
index 000000000000..8eefd256a69d
--- /dev/null
+++ b/cmds/am/src/com/android/commands/am/Instrument.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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.android.commands.am;
+
+import android.app.IActivityManager;
+import android.app.IInstrumentationWatcher;
+import android.app.Instrumentation;
+import android.app.UiAutomationConnection;
+import android.content.ComponentName;
+import android.content.pm.IPackageManager;
+import android.content.pm.InstrumentationInfo;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.AndroidException;
+import android.util.proto.ProtoOutputStream;
+import android.view.IWindowManager;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Runs the am instrument command
+ */
+public class Instrument {
+ private final IActivityManager mAm;
+ private final IPackageManager mPm;
+ private final IWindowManager mWm;
+
+ // Command line arguments
+ public String profileFile = null;
+ public boolean wait = false;
+ public boolean rawMode = false;
+ public boolean proto = false;
+ public boolean noWindowAnimation = false;
+ public String abi = null;
+ public int userId = UserHandle.USER_CURRENT;
+ public Bundle args = new Bundle();
+ // Required
+ public String componentNameArg;
+
+ /**
+ * Construct the instrument command runner.
+ */
+ public Instrument(IActivityManager am, IPackageManager pm) {
+ mAm = am;
+ mPm = pm;
+ mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+ }
+
+ /**
+ * Base class for status reporting.
+ *
+ * All the methods on this interface are called within the synchronized block
+ * of the InstrumentationWatcher, so calls are in order. However, that means
+ * you must be careful not to do blocking operations because you don't know
+ * exactly the locking dependencies.
+ */
+ private interface StatusReporter {
+ /**
+ * Status update for tests.
+ */
+ public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
+ Bundle results);
+
+ /**
+ * The tests finished.
+ */
+ public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
+ Bundle results);
+
+ /**
+ * @param errorText a description of the error
+ * @param commandError True if the error is related to the commandline, as opposed
+ * to a test failing.
+ */
+ public void onError(String errorText, boolean commandError);
+ }
+
+ /**
+ * Printer for the 'classic' text based status reporting.
+ */
+ private class TextStatusReporter implements StatusReporter {
+ private boolean mRawMode;
+
+ /**
+ * Human-ish readable output.
+ *
+ * @param rawMode In "raw mode" (true), all bundles are dumped.
+ * In "pretty mode" (false), if a bundle includes
+ * Instrumentation.REPORT_KEY_STREAMRESULT, just print that.
+ */
+ public TextStatusReporter(boolean rawMode) {
+ mRawMode = rawMode;
+ }
+
+ @Override
+ public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
+ Bundle results) {
+ // pretty printer mode?
+ String pretty = null;
+ if (!mRawMode && results != null) {
+ pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
+ }
+ if (pretty != null) {
+ System.out.print(pretty);
+ } else {
+ if (results != null) {
+ for (String key : results.keySet()) {
+ System.out.println(
+ "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
+ }
+ }
+ System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
+ }
+ }
+
+ @Override
+ public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
+ Bundle results) {
+ // pretty printer mode?
+ String pretty = null;
+ if (!mRawMode && results != null) {
+ pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
+ }
+ if (pretty != null) {
+ System.out.println(pretty);
+ } else {
+ if (results != null) {
+ for (String key : results.keySet()) {
+ System.out.println(
+ "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
+ }
+ }
+ System.out.println("INSTRUMENTATION_CODE: " + resultCode);
+ }
+ }
+
+ @Override
+ public void onError(String errorText, boolean commandError) {
+ // The regular BaseCommand error printing will print the commandErrors.
+ if (!commandError) {
+ System.out.println(errorText);
+ }
+ }
+ }
+
+ /**
+ * Printer for the protobuf based status reporting.
+ */
+ private class ProtoStatusReporter implements StatusReporter {
+ @Override
+ public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
+ Bundle results) {
+ final ProtoOutputStream proto = new ProtoOutputStream();
+
+ final long token = proto.startRepeatedObject(InstrumentationData.Session.TEST_STATUS);
+
+ proto.writeSInt32(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
+ writeBundle(proto, InstrumentationData.TestStatus.RESULTS, results);
+
+ proto.endRepeatedObject(token);
+ writeProtoToStdout(proto);
+ }
+
+ @Override
+ public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
+ Bundle results) {
+ final ProtoOutputStream proto = new ProtoOutputStream();
+
+ final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
+
+ proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
+ InstrumentationData.SESSION_FINISHED);
+ proto.writeSInt32(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
+ writeBundle(proto, InstrumentationData.SessionStatus.RESULTS, results);
+
+ proto.endObject(token);
+ writeProtoToStdout(proto);
+ }
+
+ @Override
+ public void onError(String errorText, boolean commandError) {
+ final ProtoOutputStream proto = new ProtoOutputStream();
+
+ final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
+
+ proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
+ InstrumentationData.SESSION_ABORTED);
+ proto.writeString(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
+
+ proto.endObject(token);
+ writeProtoToStdout(proto);
+ }
+
+ private void writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle) {
+ final long bundleToken = proto.startObject(fieldId);
+
+ for (final String key: bundle.keySet()) {
+ final long entryToken = proto.startRepeatedObject(
+ InstrumentationData.ResultsBundle.ENTRIES);
+
+ proto.writeString(InstrumentationData.ResultsBundleEntry.KEY, key);
+
+ final Object val = bundle.get(key);
+ if (val instanceof String) {
+ proto.writeString(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
+ (String)val);
+ } else if (val instanceof Byte) {
+ proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
+ ((Byte)val).intValue());
+ } else if (val instanceof Double) {
+ proto.writeDouble(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE,
+ ((Double)val).doubleValue());
+ } else if (val instanceof Float) {
+ proto.writeFloat(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT,
+ ((Float)val).floatValue());
+ } else if (val instanceof Integer) {
+ proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
+ ((Integer)val).intValue());
+ } else if (val instanceof Long) {
+ proto.writeSInt64(InstrumentationData.ResultsBundleEntry.VALUE_LONG,
+ ((Long)val).longValue());
+ } else if (val instanceof Short) {
+ proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
+ ((Short)val).intValue());
+ } else if (val instanceof Bundle) {
+ writeBundle(proto, InstrumentationData.ResultsBundleEntry.VALUE_BUNDLE,
+ (Bundle)val);
+ }
+
+ proto.endRepeatedObject(entryToken);
+ }
+
+ proto.endObject(bundleToken);
+ }
+
+ private void writeProtoToStdout(ProtoOutputStream proto) {
+ try {
+ System.out.write(proto.getBytes());
+ System.out.flush();
+ } catch (IOException ex) {
+ System.err.println("Error writing finished response: ");
+ ex.printStackTrace(System.err);
+ }
+ }
+ }
+
+
+ /**
+ * Callbacks from the remote instrumentation instance.
+ */
+ private class InstrumentationWatcher extends IInstrumentationWatcher.Stub {
+ private final StatusReporter mReporter;
+
+ private boolean mFinished = false;
+
+ public InstrumentationWatcher(StatusReporter reporter) {
+ mReporter = reporter;
+ }
+
+ @Override
+ public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) {
+ synchronized (this) {
+ mReporter.onInstrumentationStatusLocked(name, resultCode, results);
+ notifyAll();
+ }
+ }
+
+ @Override
+ public void instrumentationFinished(ComponentName name, int resultCode, Bundle results) {
+ synchronized (this) {
+ mReporter.onInstrumentationFinishedLocked(name, resultCode, results);
+ mFinished = true;
+ notifyAll();
+ }
+ }
+
+ public boolean waitForFinish() {
+ synchronized (this) {
+ while (!mFinished) {
+ try {
+ if (!mAm.asBinder().pingBinder()) {
+ return false;
+ }
+ wait(1000);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Figure out which component they really meant.
+ */
+ private ComponentName parseComponentName(String cnArg) throws Exception {
+ if (cnArg.contains("/")) {
+ ComponentName cn = ComponentName.unflattenFromString(cnArg);
+ if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
+ return cn;
+ } else {
+ List<InstrumentationInfo> infos = mPm.queryInstrumentation(null, 0).getList();
+
+ final int numInfos = infos == null ? 0: infos.size();
+ ArrayList<ComponentName> cns = new ArrayList<>();
+ for (int i = 0; i < numInfos; i++) {
+ InstrumentationInfo info = infos.get(i);
+
+ ComponentName c = new ComponentName(info.packageName, info.name);
+ if (cnArg.equals(info.packageName)) {
+ cns.add(c);
+ }
+ }
+
+ if (cns.size() == 0) {
+ throw new IllegalArgumentException("No instrumentation found for: " + cnArg);
+ } else if (cns.size() == 1) {
+ return cns.get(0);
+ } else {
+ StringBuilder cnsStr = new StringBuilder();
+ final int numCns = cns.size();
+ for (int i = 0; i < numCns; i++) {
+ cnsStr.append(cns.get(i).flattenToString());
+ cnsStr.append(", ");
+ }
+
+ // Remove last ", "
+ cnsStr.setLength(cnsStr.length() - 2);
+
+ throw new IllegalArgumentException("Found multiple instrumentations: "
+ + cnsStr.toString());
+ }
+ }
+ }
+
+ /**
+ * Run the instrumentation.
+ */
+ public void run() throws Exception {
+ StatusReporter reporter = null;
+ float[] oldAnims = null;
+
+ try {
+ // Choose which output we will do.
+ if (proto) {
+ reporter = new ProtoStatusReporter();
+ } else if (wait) {
+ reporter = new TextStatusReporter(rawMode);
+ }
+
+ // Choose whether we have to wait for the results.
+ InstrumentationWatcher watcher = null;
+ UiAutomationConnection connection = null;
+ if (reporter != null) {
+ watcher = new InstrumentationWatcher(reporter);
+ connection = new UiAutomationConnection();
+ }
+
+ // Set the window animation if necessary
+ if (noWindowAnimation) {
+ oldAnims = mWm.getAnimationScales();
+ mWm.setAnimationScale(0, 0.0f);
+ mWm.setAnimationScale(1, 0.0f);
+ }
+
+ // Figure out which component we are tring to do.
+ final ComponentName cn = parseComponentName(componentNameArg);
+
+ // Choose an ABI if necessary
+ if (abi != null) {
+ final String[] supportedAbis = Build.SUPPORTED_ABIS;
+ boolean matched = false;
+ for (String supportedAbi : supportedAbis) {
+ if (supportedAbi.equals(abi)) {
+ matched = true;
+ break;
+ }
+ }
+ if (!matched) {
+ throw new AndroidException(
+ "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi);
+ }
+ }
+
+ // Start the instrumentation
+ if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId,
+ abi)) {
+ throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
+ }
+
+ // If we have been requested to wait, do so until the instrumentation is finished.
+ if (watcher != null) {
+ if (!watcher.waitForFinish()) {
+ reporter.onError("INSTRUMENTATION_ABORTED: System has crashed.", false);
+ return;
+ }
+ }
+ } catch (Exception ex) {
+ // Report failures
+ if (reporter != null) {
+ reporter.onError(ex.getMessage(), true);
+ }
+
+ // And re-throw the exception
+ throw ex;
+ } finally {
+ // Clean up
+ if (oldAnims != null) {
+ mWm.setAnimationScales(oldAnims);
+ }
+ }
+ }
+}
+
diff --git a/tools/bit/Android.mk b/tools/bit/Android.mk
new file mode 100644
index 000000000000..7f691a07b06a
--- /dev/null
+++ b/tools/bit/Android.mk
@@ -0,0 +1,43 @@
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+# ==========================================================
+# Build the host executable: protoc-gen-javastream
+# ==========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bit
+
+LOCAL_SRC_FILES := \
+ aapt.cpp \
+ adb.cpp \
+ command.cpp \
+ main.cpp \
+ make.cpp \
+ print.cpp \
+ util.cpp
+
+LOCAL_STATIC_LIBRARIES := \
+ libexpat \
+ libinstrumentation \
+ libjsoncpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libprotobuf-cpp-full
+
+include $(BUILD_HOST_EXECUTABLE)
+
diff --git a/tools/bit/aapt.cpp b/tools/bit/aapt.cpp
new file mode 100644
index 000000000000..961b47cdfecd
--- /dev/null
+++ b/tools/bit/aapt.cpp
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "aapt.h"
+
+#include "command.h"
+#include "print.h"
+#include "util.h"
+
+#include <regex>
+
+const regex NS_REGEX("( *)N: ([^=]+)=(.*)");
+const regex ELEMENT_REGEX("( *)E: ([^ ]+) \\(line=(\\d+)\\)");
+const regex ATTR_REGEX("( *)A: ([^\\(=]+)[^=]*=\"([^\"]+)\".*");
+
+const string ANDROID_NS("http://schemas.android.com/apk/res/android");
+
+bool
+Apk::HasActivity(const string& className)
+{
+ string fullClassName = full_class_name(package, className);
+ const size_t N = activities.size();
+ for (size_t i=0; i<N; i++) {
+ if (activities[i] == fullClassName) {
+ return true;
+ }
+ }
+ return false;
+}
+
+struct Attribute {
+ string ns;
+ string name;
+ string value;
+};
+
+struct Element {
+ Element* parent;
+ string ns;
+ string name;
+ int lineno;
+ vector<Attribute> attributes;
+ vector<Element*> children;
+
+ /**
+ * Indentation in the xmltree dump. Might not be equal to the distance
+ * from the root because namespace rows (scopes) have their own indentation.
+ */
+ int depth;
+
+ Element();
+ ~Element();
+
+ string GetAttr(const string& ns, const string& name) const;
+ void FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse);
+
+};
+
+Element::Element()
+{
+}
+
+Element::~Element()
+{
+ const size_t N = children.size();
+ for (size_t i=0; i<N; i++) {
+ delete children[i];
+ }
+}
+
+string
+Element::GetAttr(const string& ns, const string& name) const
+{
+ const size_t N = attributes.size();
+ for (size_t i=0; i<N; i++) {
+ const Attribute& attr = attributes[i];
+ if (attr.ns == ns && attr.name == name) {
+ return attr.value;
+ }
+ }
+ return string();
+}
+
+void
+Element::FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse)
+{
+ const size_t N = children.size();
+ for (size_t i=0; i<N; i++) {
+ Element* child = children[i];
+ if (child->ns == ns && child->name == name) {
+ result->push_back(child);
+ }
+ if (recurse) {
+ child->FindElements(ns, name, result, recurse);
+ }
+ }
+}
+
+struct Scope {
+ Scope* parent;
+ int depth;
+ map<string,string> namespaces;
+
+ Scope(Scope* parent, int depth);
+};
+
+Scope::Scope(Scope* p, int d)
+ :parent(p),
+ depth(d)
+{
+ if (p != NULL) {
+ namespaces = p->namespaces;
+ }
+}
+
+
+string
+full_class_name(const string& packageName, const string& className)
+{
+ if (className.length() == 0) {
+ return "";
+ }
+ if (className[0] == '.') {
+ return packageName + className;
+ }
+ if (className.find('.') == string::npos) {
+ return packageName + "." + className;
+ }
+ return className;
+}
+
+string
+pretty_component_name(const string& packageName, const string& className)
+{
+ if (starts_with(packageName, className)) {
+ size_t pn = packageName.length();
+ size_t cn = className.length();
+ if (cn > pn && className[pn] == '.') {
+ return packageName + "/" + string(className, pn, string::npos);
+ }
+ }
+ return packageName + "/" + className;
+}
+
+int
+inspect_apk(Apk* apk, const string& filename)
+{
+ // Load the manifest xml
+ Command cmd("aapt");
+ cmd.AddArg("dump");
+ cmd.AddArg("xmltree");
+ cmd.AddArg(filename);
+ cmd.AddArg("AndroidManifest.xml");
+
+ int err;
+
+ string output = get_command_output(cmd, &err, false);
+ check_error(err);
+
+ // Parse the manifest xml
+ Scope* scope = new Scope(NULL, -1);
+ Element* root = NULL;
+ Element* current = NULL;
+ vector<string> lines;
+ split_lines(&lines, output);
+ for (size_t i=0; i<lines.size(); i++) {
+ const string& line = lines[i];
+ smatch match;
+ if (regex_match(line, match, NS_REGEX)) {
+ int depth = match[1].length() / 2;
+ while (depth < scope->depth) {
+ Scope* tmp = scope;
+ scope = scope->parent;
+ delete tmp;
+ }
+ scope = new Scope(scope, depth);
+ scope->namespaces[match[2]] = match[3];
+ } else if (regex_match(line, match, ELEMENT_REGEX)) {
+ Element* element = new Element();
+
+ string str = match[2];
+ size_t colon = str.find(':');
+ if (colon == string::npos) {
+ element->name = str;
+ } else {
+ element->ns = scope->namespaces[string(str, 0, colon)];
+ element->name.assign(str, colon+1, string::npos);
+ }
+ element->lineno = atoi(match[3].str().c_str());
+ element->depth = match[1].length() / 2;
+
+ if (root == NULL) {
+ current = element;
+ root = element;
+ } else {
+ while (element->depth <= current->depth && current->parent != NULL) {
+ current = current->parent;
+ }
+ element->parent = current;
+ current->children.push_back(element);
+ current = element;
+ }
+ } else if (regex_match(line, match, ATTR_REGEX)) {
+ if (current != NULL) {
+ Attribute attr;
+ string str = match[2];
+ size_t colon = str.find(':');
+ if (colon == string::npos) {
+ attr.name = str;
+ } else {
+ attr.ns = scope->namespaces[string(str, 0, colon)];
+ attr.name.assign(str, colon+1, string::npos);
+ }
+ attr.value = match[3];
+ current->attributes.push_back(attr);
+ }
+ }
+ }
+ while (scope != NULL) {
+ Scope* tmp = scope;
+ scope = scope->parent;
+ delete tmp;
+ }
+
+ // Package name
+ apk->package = root->GetAttr("", "package");
+ if (apk->package.size() == 0) {
+ print_error("%s:%d: Manifest root element doesn't contain a package attribute",
+ filename.c_str(), root->lineno);
+ delete root;
+ return 1;
+ }
+
+ // Instrumentation runner
+ vector<Element*> instrumentation;
+ root->FindElements("", "instrumentation", &instrumentation, true);
+ if (instrumentation.size() > 0) {
+ // TODO: How could we deal with multiple instrumentation tags?
+ // We'll just pick the first one.
+ apk->runner = instrumentation[0]->GetAttr(ANDROID_NS, "name");
+ }
+
+ // Activities
+ vector<Element*> activities;
+ root->FindElements("", "activity", &activities, true);
+ for (size_t i=0; i<activities.size(); i++) {
+ string name = activities[i]->GetAttr(ANDROID_NS, "name");
+ if (name.size() == 0) {
+ continue;
+ }
+ apk->activities.push_back(full_class_name(apk->package, name));
+ }
+
+ delete root;
+ return 0;
+}
+
diff --git a/tools/bit/aapt.h b/tools/bit/aapt.h
new file mode 100644
index 000000000000..6aeb03f18744
--- /dev/null
+++ b/tools/bit/aapt.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef AAPT_H
+#define AAPT_H
+
+#include <string>
+#include <vector>
+
+using namespace std;
+
+struct Apk
+{
+ string package;
+ string runner;
+ vector<string> activities;
+
+ bool HasActivity(const string& className);
+};
+
+string full_class_name(const string& packageName, const string& className);
+string pretty_component_name(const string& packageName, const string& className);
+
+int inspect_apk(Apk* apk, const string& filename);
+
+#endif // AAPT_H
diff --git a/tools/bit/adb.cpp b/tools/bit/adb.cpp
new file mode 100644
index 000000000000..eb96dae2189c
--- /dev/null
+++ b/tools/bit/adb.cpp
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "adb.h"
+
+#include "command.h"
+#include "print.h"
+#include "util.h"
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <limits.h>
+
+#include <iostream>
+#include <istream>
+#include <streambuf>
+
+using namespace std;
+
+struct Buffer: public streambuf
+{
+ Buffer(char* begin, size_t size);
+};
+
+Buffer::Buffer(char* begin, size_t size)
+{
+ this->setg(begin, begin, begin + size);
+}
+
+int
+run_adb(const char* first, ...)
+{
+ Command cmd("adb");
+
+ if (first == NULL) {
+ return 0;
+ }
+
+ cmd.AddArg(first);
+
+ va_list args;
+ va_start(args, first);
+ while (true) {
+ const char* arg = va_arg(args, char*);
+ if (arg == NULL) {
+ break;
+ }
+ cmd.AddArg(arg);
+ }
+ va_end(args);
+
+ return run_command(cmd);
+}
+
+string
+get_system_property(const string& name, int* err)
+{
+ Command cmd("adb");
+ cmd.AddArg("shell");
+ cmd.AddArg("getprop");
+ cmd.AddArg(name);
+
+ return trim(get_command_output(cmd, err, false));
+}
+
+
+static uint64_t
+read_varint(int fd, int* err, bool* done)
+{
+ uint32_t bits = 0;
+ uint64_t result = 0;
+ while (true) {
+ uint8_t byte;
+ ssize_t amt = read(fd, &byte, 1);
+ if (amt == 0) {
+ *done = true;
+ return result;
+ } else if (amt < 0) {
+ return *err = errno;
+ }
+ result |= uint64_t(byte & 0x7F) << bits;
+ if ((byte & 0x80) == 0) {
+ return result;
+ }
+ bits += 7;
+ if (bits > 64) {
+ *err = -1;
+ return 0;
+ }
+ }
+}
+
+static char*
+read_sized_buffer(int fd, int* err, size_t* resultSize)
+{
+ bool done = false;
+ uint64_t size = read_varint(fd, err, &done);
+ if (*err != 0 || done) {
+ return NULL;
+ }
+ if (size == 0) {
+ *resultSize = 0;
+ return NULL;
+ }
+ // 10 MB seems like a reasonable limit.
+ if (size > 10*1024*1024) {
+ print_error("result buffer too large: %llu", size);
+ return NULL;
+ }
+ char* buf = (char*)malloc(size);
+ if (buf == NULL) {
+ print_error("Can't allocate a buffer of size for test results: %llu", size);
+ return NULL;
+ }
+ int pos = 0;
+ while (size - pos > 0) {
+ ssize_t amt = read(fd, buf+pos, size-pos);
+ if (amt == 0) {
+ // early end of pipe
+ print_error("Early end of pipe.");
+ *err = -1;
+ free(buf);
+ return NULL;
+ } else if (amt < 0) {
+ // error
+ *err = errno;
+ free(buf);
+ return NULL;
+ }
+ pos += amt;
+ }
+ *resultSize = (size_t)size;
+ return buf;
+}
+
+static int
+read_sized_proto(int fd, Message* message)
+{
+ int err = 0;
+ size_t size;
+ char* buf = read_sized_buffer(fd, &err, &size);
+ if (err != 0) {
+ if (buf != NULL) {
+ free(buf);
+ }
+ return err;
+ } else if (size == 0) {
+ if (buf != NULL) {
+ free(buf);
+ }
+ return 0;
+ } else if (buf == NULL) {
+ return -1;
+ }
+ Buffer buffer(buf, size);
+ istream in(&buffer);
+
+ err = message->ParseFromIstream(&in) ? 0 : -1;
+
+ free(buf);
+ return err;
+}
+
+static int
+skip_bytes(int fd, ssize_t size, char* scratch, int scratchSize)
+{
+ while (size > 0) {
+ ssize_t amt = size < scratchSize ? size : scratchSize;
+ fprintf(stderr, "skipping %lu/%ld bytes\n", size, amt);
+ amt = read(fd, scratch, amt);
+ if (amt == 0) {
+ // early end of pipe
+ print_error("Early end of pipe.");
+ return -1;
+ } else if (amt < 0) {
+ // error
+ return errno;
+ }
+ size -= amt;
+ }
+ return 0;
+}
+
+static int
+skip_unknown_field(int fd, uint64_t tag, char* scratch, int scratchSize) {
+ bool done;
+ int err;
+ uint64_t size;
+ switch (tag & 0x7) {
+ case 0: // varint
+ read_varint(fd, &err, &done);
+ if (err != 0) {
+ return err;
+ } else if (done) {
+ return -1;
+ } else {
+ return 0;
+ }
+ case 1:
+ return skip_bytes(fd, 8, scratch, scratchSize);
+ case 2:
+ size = read_varint(fd, &err, &done);
+ if (err != 0) {
+ return err;
+ } else if (done) {
+ return -1;
+ }
+ if (size > INT_MAX) {
+ // we'll be here a long time but this keeps it from overflowing
+ return -1;
+ }
+ return skip_bytes(fd, (ssize_t)size, scratch, scratchSize);
+ case 5:
+ return skip_bytes(fd, 4, scratch, scratchSize);
+ default:
+ print_error("bad wire type for tag 0x%lx\n", tag);
+ return -1;
+ }
+}
+
+static int
+read_instrumentation_results(int fd, char* scratch, int scratchSize,
+ InstrumentationCallbacks* callbacks)
+{
+ bool done = false;
+ int err = 0;
+ string result;
+ while (true) {
+ uint64_t tag = read_varint(fd, &err, &done);
+ if (done) {
+ // Done reading input (this is the only place that a stream end isn't an error).
+ return 0;
+ } else if (err != 0) {
+ return err;
+ } else if (tag == 0xa) { // test_status
+ TestStatus status;
+ err = read_sized_proto(fd, &status);
+ if (err != 0) {
+ return err;
+ }
+ callbacks->OnTestStatus(status);
+ } else if (tag == 0x12) { // session_status
+ SessionStatus status;
+ err = read_sized_proto(fd, &status);
+ if (err != 0) {
+ return err;
+ }
+ callbacks->OnSessionStatus(status);
+ } else {
+ err = skip_unknown_field(fd, tag, scratch, scratchSize);
+ if (err != 0) {
+ return err;
+ }
+ }
+ }
+ return 0;
+}
+
+int
+run_instrumentation_test(const string& packageName, const string& runner, const string& className,
+ InstrumentationCallbacks* callbacks)
+{
+ Command cmd("adb");
+ cmd.AddArg("shell");
+ cmd.AddArg("am");
+ cmd.AddArg("instrument");
+ cmd.AddArg("-w");
+ cmd.AddArg("-m");
+ if (className.length() > 0) {
+ cmd.AddArg("-e");
+ cmd.AddArg("class");
+ cmd.AddArg(className);
+ }
+ cmd.AddArg(packageName + "/" + runner);
+
+ print_command(cmd);
+
+ int fds[2];
+ pipe(fds);
+
+ pid_t pid = fork();
+
+ if (pid == -1) {
+ // fork error
+ return errno;
+ } else if (pid == 0) {
+ // child
+ while ((dup2(fds[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
+ close(fds[1]);
+ close(fds[0]);
+ const char* prog = cmd.GetProg();
+ char* const* argv = cmd.GetArgv();
+ char* const* env = cmd.GetEnv();
+ execvpe(prog, argv, env);
+ print_error("Unable to run command: %s", prog);
+ exit(1);
+ } else {
+ // parent
+ close(fds[1]);
+ string result;
+ const int size = 16*1024;
+ char* buf = (char*)malloc(size);
+ int err = read_instrumentation_results(fds[0], buf, size, callbacks);
+ free(buf);
+ int status;
+ waitpid(pid, &status, 0);
+ if (err != 0) {
+ return err;
+ }
+ if (WIFEXITED(status)) {
+ return WEXITSTATUS(status);
+ } else {
+ return -1;
+ }
+ }
+}
+
+/**
+ * Get the second to last bundle in the args list. Stores the last name found
+ * in last. If the path is not found or if the args list is empty, returns NULL.
+ */
+static const ResultsBundleEntry *
+find_penultimate_entry(const ResultsBundle& bundle, va_list args)
+{
+ const ResultsBundle* b = &bundle;
+ const char* arg = va_arg(args, char*);
+ while (arg) {
+ string last = arg;
+ arg = va_arg(args, char*);
+ bool found = false;
+ for (int i=0; i<b->entries_size(); i++) {
+ const ResultsBundleEntry& e = b->entries(i);
+ if (e.key() == last) {
+ if (arg == NULL) {
+ return &e;
+ } else if (e.has_value_bundle()) {
+ b = &e.value_bundle();
+ found = true;
+ }
+ }
+ }
+ if (!found) {
+ return NULL;
+ }
+ if (arg == NULL) {
+ return NULL;
+ }
+ }
+ return NULL;
+}
+
+string
+get_bundle_string(const ResultsBundle& bundle, bool* found, ...)
+{
+ va_list args;
+ va_start(args, found);
+ const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args);
+ va_end(args);
+ if (entry == NULL) {
+ *found = false;
+ return string();
+ }
+ if (entry->has_value_string()) {
+ *found = true;
+ return entry->value_string();
+ }
+ *found = false;
+ return string();
+}
+
+int32_t
+get_bundle_int(const ResultsBundle& bundle, bool* found, ...)
+{
+ va_list args;
+ va_start(args, found);
+ const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args);
+ va_end(args);
+ if (entry == NULL) {
+ *found = false;
+ return 0;
+ }
+ if (entry->has_value_int()) {
+ *found = true;
+ return entry->value_int();
+ }
+ *found = false;
+ return 0;
+}
+
+float
+get_bundle_float(const ResultsBundle& bundle, bool* found, ...)
+{
+ va_list args;
+ va_start(args, found);
+ const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args);
+ va_end(args);
+ if (entry == NULL) {
+ *found = false;
+ return 0;
+ }
+ if (entry->has_value_float()) {
+ *found = true;
+ return entry->value_float();
+ }
+ *found = false;
+ return 0;
+}
+
+double
+get_bundle_double(const ResultsBundle& bundle, bool* found, ...)
+{
+ va_list args;
+ va_start(args, found);
+ const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args);
+ va_end(args);
+ if (entry == NULL) {
+ *found = false;
+ return 0;
+ }
+ if (entry->has_value_double()) {
+ *found = true;
+ return entry->value_double();
+ }
+ *found = false;
+ return 0;
+}
+
+int64_t
+get_bundle_long(const ResultsBundle& bundle, bool* found, ...)
+{
+ va_list args;
+ va_start(args, found);
+ const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args);
+ va_end(args);
+ if (entry == NULL) {
+ *found = false;
+ return 0;
+ }
+ if (entry->has_value_long()) {
+ *found = true;
+ return entry->value_long();
+ }
+ *found = false;
+ return 0;
+}
+
diff --git a/tools/bit/adb.h b/tools/bit/adb.h
new file mode 100644
index 000000000000..dca80c853b45
--- /dev/null
+++ b/tools/bit/adb.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef ADB_H
+#define ADB_H
+
+#include "instrumentation_data.pb.h"
+
+#include <string>
+
+using namespace android::am;
+using namespace google::protobuf;
+using namespace std;
+
+class InstrumentationCallbacks {
+public:
+ virtual void OnTestStatus(TestStatus& status) = 0;
+ virtual void OnSessionStatus(SessionStatus& status) = 0;
+};
+
+int run_adb(const char* first, ...);
+
+string get_system_property(const string& name, int* err);
+
+int run_instrumentation_test(const string& packageName, const string& runner,
+ const string& className, InstrumentationCallbacks* callbacks);
+
+string get_bundle_string(const ResultsBundle& bundle, bool* found, ...);
+int32_t get_bundle_int(const ResultsBundle& bundle, bool* found, ...);
+float get_bundle_float(const ResultsBundle& bundle, bool* found, ...);
+double get_bundle_double(const ResultsBundle& bundle, bool* found, ...);
+int64_t get_bundle_long(const ResultsBundle& bundle, bool* found, ...);
+
+#endif // ADB_H
diff --git a/tools/bit/command.cpp b/tools/bit/command.cpp
new file mode 100644
index 000000000000..c5c12b4fad72
--- /dev/null
+++ b/tools/bit/command.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "command.h"
+
+#include "print.h"
+#include "util.h"
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+Command::Command(const string& prog)
+ :prog(prog)
+{
+}
+
+Command::~Command()
+{
+}
+
+void
+Command::AddArg(const string& arg)
+{
+ args.push_back(arg);
+}
+
+void
+Command::AddEnv(const string& name, const string& value)
+{
+ env[name] = value;
+}
+
+const char*
+Command::GetProg() const
+{
+ return prog.c_str();
+}
+
+char *const *
+Command::GetArgv() const
+{
+ const int N = args.size();
+ char** result = (char**)malloc(sizeof(char*)*(N+2));
+ result[0] = strdup(prog.c_str());
+ for (int i=0; i<N; i++) {
+ result[i+1] = strdup(args[i].c_str());
+ }
+ result[N+1] = 0;
+ return result;
+}
+
+char *const *
+Command::GetEnv() const
+{
+ map<string,string> copy;
+ for (const char** p=(const char**)environ; *p != NULL; p++) {
+ char* name = strdup(*p);
+ char* value = strchr(name, '=');
+ *value = '\0';
+ value++;
+ copy[name] = value;
+ free(name);
+ }
+ for (map<string,string>::const_iterator it=env.begin(); it!=env.end(); it++) {
+ copy[it->first] = it->second;
+ }
+ char** result = (char**)malloc(sizeof(char*)*(copy.size()+1));
+ char** row = result;
+ for (map<string,string>::const_iterator it=copy.begin(); it!=copy.end(); it++) {
+ *row = (char*)malloc(it->first.size() + it->second.size() + 2);
+ strcpy(*row, it->first.c_str());
+ strcat(*row, "=");
+ strcat(*row, it->second.c_str());
+ row++;
+ }
+ *row = NULL;
+ return result;
+}
+
+string
+get_command_output(const Command& command, int* err, bool quiet)
+{
+ if (!quiet) {
+ print_command(command);
+ }
+
+ int fds[2];
+ pipe(fds);
+
+ pid_t pid = fork();
+
+ if (pid == -1) {
+ // fork error
+ *err = errno;
+ return string();
+ } else if (pid == 0) {
+ // child
+ while ((dup2(fds[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
+ close(fds[1]);
+ close(fds[0]);
+ const char* prog = command.GetProg();
+ char* const* argv = command.GetArgv();
+ char* const* env = command.GetEnv();
+ execvpe(prog, argv, env);
+ if (!quiet) {
+ print_error("Unable to run command: %s", prog);
+ }
+ exit(1);
+ } else {
+ // parent
+ close(fds[1]);
+ string result;
+ const int size = 16*1024;
+ char* buf = (char*)malloc(size);
+ while (true) {
+ ssize_t amt = read(fds[0], buf, size);
+ if (amt <= 0) {
+ break;
+ } else if (amt > 0) {
+ result.append(buf, amt);
+ }
+ }
+ free(buf);
+ int status;
+ waitpid(pid, &status, 0);
+ if (WIFEXITED(status)) {
+ *err = WEXITSTATUS(status);
+ return result;
+ } else {
+ *err = -1;
+ return string();
+ }
+ }
+}
+
+
+int
+run_command(const Command& command)
+{
+ print_command(command);
+
+ pid_t pid = fork();
+
+ if (pid == -1) {
+ // fork error
+ return errno;
+ } else if (pid == 0) {
+ // child
+ const char* prog = command.GetProg();
+ char* const* argv = command.GetArgv();
+ char* const* env = command.GetEnv();
+ execvpe(prog, argv, env);
+ print_error("Unable to run command: %s", prog);
+ exit(1);
+ } else {
+ // parent
+ int status;
+ waitpid(pid, &status, 0);
+ if (WIFEXITED(status)) {
+ return WEXITSTATUS(status);
+ } else {
+ return -1;
+ }
+ }
+}
+
diff --git a/tools/bit/command.h b/tools/bit/command.h
new file mode 100644
index 000000000000..eb0b88f8d1ac
--- /dev/null
+++ b/tools/bit/command.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef COMMAND_H
+#define COMMAND_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+using namespace std;
+
+struct Command
+{
+ Command(const string& prog);
+ ~Command();
+
+ void AddArg(const string& arg);
+ void AddEnv(const string& name, const string& value);
+
+ const char* GetProg() const;
+ char* const* GetArgv() const;
+ char* const* GetEnv() const;
+
+ string GetCommandline() const;
+
+ string prog;
+ vector<string> args;
+ map<string,string> env;
+};
+
+/**
+ * Run the command and collect stdout.
+ * Returns the exit code.
+ */
+string get_command_output(const Command& command, int* err, bool quiet=false);
+
+/**
+ * Run the command.
+ * Returns the exit code.
+ */
+int run_command(const Command& command);
+
+#endif // COMMAND_H
+
diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp
new file mode 100644
index 000000000000..04836adf2288
--- /dev/null
+++ b/tools/bit/main.cpp
@@ -0,0 +1,981 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "aapt.h"
+#include "adb.h"
+#include "make.h"
+#include "print.h"
+#include "util.h"
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <google/protobuf/stubs/common.h>
+
+using namespace std;
+
+/**
+ * An entry from the command line for something that will be built, installed,
+ * and/or tested.
+ */
+struct Target {
+ bool build;
+ bool install;
+ bool test;
+ string pattern;
+ string name;
+ vector<string> actions;
+ Module module;
+
+ int testActionCount;
+
+ int testPassCount;
+ int testFailCount;
+ bool actionsWithNoTests;
+
+ Target(bool b, bool i, bool t, const string& p);
+};
+
+Target::Target(bool b, bool i, bool t, const string& p)
+ :build(b),
+ install(i),
+ test(t),
+ pattern(p),
+ testActionCount(0),
+ testPassCount(0),
+ testFailCount(0),
+ actionsWithNoTests(false)
+{
+}
+
+/**
+ * Command line options.
+ */
+struct Options {
+ // For help
+ bool runHelp;
+
+ // For tab completion
+ bool runTab;
+ string tabPattern;
+
+ // For build/install/test
+ bool reboot;
+ vector<Target*> targets;
+
+ Options();
+ ~Options();
+};
+
+Options::Options()
+ :runHelp(false),
+ runTab(false),
+ reboot(false),
+ targets()
+{
+}
+
+Options::~Options()
+{
+}
+
+struct InstallApk
+{
+ TrackedFile file;
+ bool alwaysInstall;
+ bool installed;
+
+ InstallApk();
+ InstallApk(const InstallApk& that);
+ InstallApk(const string& filename, bool always);
+ ~InstallApk() {};
+};
+
+InstallApk::InstallApk()
+{
+}
+
+InstallApk::InstallApk(const InstallApk& that)
+ :file(that.file),
+ alwaysInstall(that.alwaysInstall),
+ installed(that.installed)
+{
+}
+
+InstallApk::InstallApk(const string& filename, bool always)
+ :file(filename),
+ alwaysInstall(always),
+ installed(false)
+{
+}
+
+
+/**
+ * Record for an test that is going to be launched.
+ */
+struct TestAction {
+ TestAction();
+
+ // The package name from the apk
+ string packageName;
+
+ // The test runner class
+ string runner;
+
+ // The test class, or none if all tests should be run
+ string className;
+
+ // The original target that requested this action
+ Target* target;
+
+ // The number of tests that passed
+ int passCount;
+
+ // The number of tests that failed
+ int failCount;
+};
+
+TestAction::TestAction()
+ :passCount(0),
+ failCount(0)
+{
+}
+
+/**
+ * Record for an activity that is going to be launched.
+ */
+struct ActivityAction {
+ // The package name from the apk
+ string packageName;
+
+ // The test class, or none if all tests should be run
+ string className;
+};
+
+/**
+ * Callback class for the am instrument command.
+ */
+class TestResults: public InstrumentationCallbacks
+{
+public:
+ virtual void OnTestStatus(TestStatus& status);
+ virtual void OnSessionStatus(SessionStatus& status);
+
+ /**
+ * Set the TestAction that the tests are for.
+ * It will be updated with statistics as the tests run.
+ */
+ void SetCurrentAction(TestAction* action);
+
+private:
+ TestAction* m_currentAction;
+};
+
+void
+TestResults::OnTestStatus(TestStatus& status)
+{
+ bool found;
+// printf("OnTestStatus\n");
+// status.PrintDebugString();
+ int32_t resultCode = status.has_results() ? status.result_code() : 0;
+
+ if (!status.has_results()) {
+ return;
+ }
+ const ResultsBundle &results = status.results();
+
+ int32_t currentTestNum = get_bundle_int(results, &found, "current", NULL);
+ if (!found) {
+ currentTestNum = -1;
+ }
+
+ int32_t testCount = get_bundle_int(results, &found, "numtests", NULL);
+ if (!found) {
+ testCount = -1;
+ }
+
+ string className = get_bundle_string(results, &found, "class", NULL);
+ if (!found) {
+ return;
+ }
+
+ string testName = get_bundle_string(results, &found, "test", NULL);
+ if (!found) {
+ return;
+ }
+
+ if (resultCode == 0) {
+ // test passed
+ m_currentAction->passCount++;
+ m_currentAction->target->testPassCount++;
+ } else if (resultCode == 1) {
+ // test starting
+ ostringstream line;
+ line << "Running";
+ if (currentTestNum > 0) {
+ line << ": " << currentTestNum;
+ if (testCount > 0) {
+ line << " of " << testCount;
+ }
+ }
+ line << ": " << m_currentAction->target->name << ':' << className << "\\#" << testName;
+ print_one_line("%s", line.str().c_str());
+ } else if (resultCode == -2) {
+ // test failed
+ m_currentAction->failCount++;
+ m_currentAction->target->testFailCount++;
+ printf("%s\n%sFailed: %s:%s\\#%s%s\n", g_escapeClearLine, g_escapeRedBold,
+ m_currentAction->target->name.c_str(), className.c_str(),
+ testName.c_str(), g_escapeEndColor);
+
+ string stack = get_bundle_string(results, &found, "stack", NULL);
+ if (found) {
+ printf("%s\n", stack.c_str());
+ }
+ }
+}
+
+void
+TestResults::OnSessionStatus(SessionStatus& /*status*/)
+{
+ //status.PrintDebugString();
+}
+
+void
+TestResults::SetCurrentAction(TestAction* action)
+{
+ m_currentAction = action;
+}
+
+/**
+ * Prints the usage statement / help text.
+ */
+static void
+print_usage(FILE* out) {
+ fprintf(out, "usage: bit OPTIONS PATTERN\n");
+ fprintf(out, "\n");
+ fprintf(out, " Build, sync and test android code.\n");
+ fprintf(out, "\n");
+ fprintf(out, " The -b -i and -t options allow you to specify which phases\n");
+ fprintf(out, " you want to run. If none of those options are given, then\n");
+ fprintf(out, " all phases are run. If any of these options are provided\n");
+ fprintf(out, " then only the listed phases are run.\n");
+ fprintf(out, "\n");
+ fprintf(out, " OPTIONS\n");
+ fprintf(out, " -b Run a build\n");
+ fprintf(out, " -i Install the targets\n");
+ fprintf(out, " -t Run the tests\n");
+ fprintf(out, "\n");
+ fprintf(out, " -r If the runtime needs to be restarted, do a full reboot\n");
+ fprintf(out, " instead\n");
+ fprintf(out, "\n");
+ fprintf(out, " PATTERN\n");
+ fprintf(out, " One or more targets to build, install and test. The target\n");
+ fprintf(out, " names are the names that appear in the LOCAL_MODULE or\n");
+ fprintf(out, " LOCAL_PACKAGE_NAME variables in Android.mk or Android.bp files.\n");
+ fprintf(out, "\n");
+ fprintf(out, " Building and installing\n");
+ fprintf(out, " -----------------------\n");
+ fprintf(out, " The modules specified will be built and then installed. If the\n");
+ fprintf(out, " files are on the system partition, they will be synced and the\n");
+ fprintf(out, " attached device rebooted. If they are APKs that aren't on the\n");
+ fprintf(out, " system partition they are installed with adb install.\n");
+ fprintf(out, "\n");
+ fprintf(out, " For example:\n");
+ fprintf(out, " bit framework\n");
+ fprintf(out, " Builds framework.jar, syncs the system partition and reboots.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit SystemUI\n");
+ fprintf(out, " Builds SystemUI.apk, syncs the system partition and reboots.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit CtsProtoTestCases\n");
+ fprintf(out, " Builds this CTS apk, adb installs it, but does not run any\n");
+ fprintf(out, " tests.\n");
+ fprintf(out, "\n");
+ fprintf(out, " Running Unit Tests\n");
+ fprintf(out, " ------------------\n");
+ fprintf(out, " To run a unit test, list the test class names and optionally the\n");
+ fprintf(out, " test method after the module.\n");
+ fprintf(out, "\n");
+ fprintf(out, " For example:\n");
+ fprintf(out, " bit CtsProtoTestCases:*\n");
+ fprintf(out, " Builds this CTS apk, adb installs it, and runs all the tests\n");
+ fprintf(out, " contained in that apk.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit framework CtsProtoTestCases:*\n");
+ fprintf(out, " Builds the framework and the apk, syncs and reboots, then\n");
+ fprintf(out, " adb installs CtsProtoTestCases.apk, and runs all tests \n");
+ fprintf(out, " contained in that apk.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\n");
+ fprintf(out, " bit CtsProtoTestCases:android.util.proto.cts.ProtoOutputStreamBoolTest\n");
+ fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs all the\n");
+ fprintf(out, " tests in the ProtoOutputStreamBoolTest class.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\\#testWrite\n");
+ fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the testWrite\n");
+ fprintf(out, " test method on that class.\n");
+ fprintf(out, "\n");
+ fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\\#testWrite,.ProtoOutputStreamBoolTest\\#testRepeated\n");
+ fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the testWrite\n");
+ fprintf(out, " and testRepeated test methods on that class.\n");
+ fprintf(out, "\n");
+ fprintf(out, " Launching an Activity\n");
+ fprintf(out, " ---------------------\n");
+ fprintf(out, " To launch an activity, specify the activity class name after\n");
+ fprintf(out, " the module name.\n");
+ fprintf(out, "\n");
+ fprintf(out, " For example:\n");
+ fprintf(out, " bit StatusBarTest:NotificationBuilderTest\n");
+ fprintf(out, " bit StatusBarTest:.NotificationBuilderTest\n");
+ fprintf(out, " bit StatusBarTest:com.android.statusbartest.NotificationBuilderTest\n");
+ fprintf(out, " Builds and installs StatusBarTest.apk, launches the\n");
+ fprintf(out, " com.android.statusbartest/.NotificationBuilderTest activity.\n");
+ fprintf(out, "\n");
+ fprintf(out, "\n");
+ fprintf(out, "usage: bit --tab ...\n");
+ fprintf(out, "\n");
+ fprintf(out, " Lists the targets in a format for tab completion. To get tab\n");
+ fprintf(out, " completion, add this to your bash environment:\n");
+ fprintf(out, "\n");
+ fprintf(out, " complete -C \"bit --tab\" bit\n");
+ fprintf(out, "\n");
+ fprintf(out, " Sourcing android's build/envsetup.sh will do this for you\n");
+ fprintf(out, " automatically.\n");
+ fprintf(out, "\n");
+ fprintf(out, "\n");
+ fprintf(out, "usage: bit --help\n");
+ fprintf(out, "usage: bit -h\n");
+ fprintf(out, "\n");
+ fprintf(out, " Print this help message\n");
+ fprintf(out, "\n");
+}
+
+
+/**
+ * Sets the appropriate flag* variables. If there is a problem with the
+ * commandline arguments, prints the help message and exits with an error.
+ */
+static void
+parse_args(Options* options, int argc, const char** argv)
+{
+ // Help
+ if (argc == 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) {
+ options->runHelp = true;
+ return;
+ }
+
+ // Tab
+ if (argc >= 4 && strcmp(argv[1], "--tab") == 0) {
+ options->runTab = true;
+ options->tabPattern = argv[3];
+ return;
+ }
+
+ // Normal usage
+ bool anyPhases = false;
+ bool gotPattern = false;
+ bool flagBuild = false;
+ bool flagInstall = false;
+ bool flagTest = false;
+ for (int i=1; i < argc; i++) {
+ string arg(argv[i]);
+ if (arg[0] == '-') {
+ for (size_t j=1; j<arg.size(); j++) {
+ switch (arg[j]) {
+ case '-':
+ break;
+ case 'b':
+ if (gotPattern) {
+ gotPattern = false;
+ flagInstall = false;
+ flagTest = false;
+ }
+ flagBuild = true;
+ anyPhases = true;
+ break;
+ case 'i':
+ if (gotPattern) {
+ gotPattern = false;
+ flagBuild = false;
+ flagTest = false;
+ }
+ flagInstall = true;
+ anyPhases = true;
+ break;
+ case 't':
+ if (gotPattern) {
+ gotPattern = false;
+ flagBuild = false;
+ flagInstall = false;
+ }
+ flagTest = true;
+ anyPhases = true;
+ break;
+ case 'r':
+ options->reboot = true;
+ break;
+ default:
+ fprintf(stderr, "Unrecognized option '%c'\n", arg[j]);
+ print_usage(stderr);
+ exit(1);
+ break;
+ }
+ }
+ } else {
+ Target* target = new Target(flagBuild || !anyPhases, flagInstall || !anyPhases,
+ flagTest || !anyPhases, arg);
+ size_t colonPos = arg.find(':');
+ if (colonPos == 0) {
+ fprintf(stderr, "Test / activity supplied without a module to build: %s\n",
+ arg.c_str());
+ print_usage(stderr);
+ exit(1);
+ } else if (colonPos == string::npos) {
+ target->name = arg;
+ } else {
+ target->name.assign(arg, 0, colonPos);
+ size_t beginPos = colonPos+1;
+ size_t commaPos;
+ while (true) {
+ commaPos = arg.find(',', beginPos);
+ if (commaPos == string::npos) {
+ if (beginPos != arg.size()) {
+ target->actions.push_back(string(arg, beginPos, commaPos));
+ }
+ break;
+ } else {
+ if (commaPos != beginPos) {
+ target->actions.push_back(string(arg, beginPos, commaPos-beginPos));
+ }
+ beginPos = commaPos+1;
+ }
+ }
+ }
+ options->targets.push_back(target);
+ gotPattern = true;
+ }
+ }
+ // If no pattern was supplied, give an error
+ if (options->targets.size() == 0) {
+ fprintf(stderr, "No PATTERN supplied.\n\n");
+ print_usage(stderr);
+ exit(1);
+ }
+}
+
+/**
+ * Get an environment variable.
+ * Exits with an error if it is unset or the empty string.
+ */
+static string
+get_required_env(const char* name, bool quiet)
+{
+ const char* value = getenv(name);
+ if (value == NULL || value[0] == '\0') {
+ if (!quiet) {
+ fprintf(stderr, "%s not set. Did you source build/envsetup.sh,"
+ " run lunch and do a build?\n", name);
+ }
+ exit(1);
+ }
+ return string(value);
+}
+
+/**
+ * Get the out directory.
+ *
+ * This duplicates the logic in build/make/core/envsetup.mk (which hasn't changed since 2011)
+ * so that we don't have to wait for get_build_var make invocation.
+ */
+string
+get_out_dir()
+{
+ const char* out_dir = getenv("OUT_DIR");
+ if (out_dir == NULL || out_dir[0] == '\0') {
+ const char* common_base = getenv("OUT_DIR_COMMON_BASE");
+ if (common_base == NULL || common_base[0] == '\0') {
+ // We don't prefix with buildTop because we cd there and it
+ // makes all the filenames long when being pretty printed.
+ return "out";
+ } else {
+ char* pwd = get_current_dir_name();
+ const char* slash = strrchr(pwd, '/');
+ if (slash == NULL) {
+ slash = "";
+ }
+ string result(common_base);
+ result += slash;
+ free(pwd);
+ return result;
+ }
+ }
+ return string(out_dir);
+}
+
+/**
+ * Check that a system property on the device matches the expected value.
+ * Exits with an error if they don't.
+ */
+static void
+check_device_property(const string& property, const string& expected)
+{
+ int err;
+ string deviceValue = get_system_property(property, &err);
+ check_error(err);
+ if (deviceValue != expected) {
+ print_error("There is a mismatch between the build you just did and the device you");
+ print_error("are trying to sync it to in the %s system property", property.c_str());
+ print_error(" build: %s", expected.c_str());
+ print_error(" device: %s", deviceValue.c_str());
+ exit(1);
+ }
+}
+
+/**
+ * Run the build, install, and test actions.
+ */
+void
+run_phases(vector<Target*> targets, bool reboot)
+{
+ int err = 0;
+
+ //
+ // Initialization
+ //
+
+ print_status("Initializing");
+
+ const string buildTop = get_required_env("ANDROID_BUILD_TOP", false);
+ const string buildProduct = get_required_env("TARGET_PRODUCT", false);
+ const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false);
+ const string buildType = get_required_env("TARGET_BUILD_TYPE", false);
+ const string buildDevice = get_build_var(buildTop, "TARGET_DEVICE", false);
+ const string buildId = get_build_var(buildTop, "BUILD_ID", false);
+ const string buildOut = get_out_dir();
+
+ // TODO: print_command("cd", buildTop.c_str());
+ chdir(buildTop.c_str());
+
+ // Get the modules for the targets
+ map<string,Module> modules;
+ read_modules(buildOut, buildDevice, &modules, false);
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ map<string,Module>::iterator mod = modules.find(target->name);
+ if (mod != modules.end()) {
+ target->module = mod->second;
+ } else {
+ print_error("Error: Could not find module: %s", target->name.c_str());
+ err = 1;
+ }
+ }
+ if (err != 0) {
+ exit(1);
+ }
+
+ // Choose the goals
+ vector<string> goals;
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ if (target->build) {
+ goals.push_back(target->name);
+ }
+ }
+
+ // Figure out whether we need to sync the system and which apks to install
+ string systemPath = buildOut + "/target/product/" + buildDevice + "/system/";
+ string dataPath = buildOut + "/target/product/" + buildDevice + "/data/";
+ bool syncSystem = false;
+ bool alwaysSyncSystem = false;
+ vector<InstallApk> installApks;
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ if (target->install) {
+ for (size_t j=0; j<target->module.installed.size(); j++) {
+ const string& file = target->module.installed[j];
+ // System partition
+ if (starts_with(file, systemPath)) {
+ syncSystem = true;
+ if (!target->build) {
+ // If a system partition target didn't get built then
+ // it won't change we will always need to do adb sync
+ alwaysSyncSystem = true;
+ }
+ continue;
+ }
+ // Apk in the data partition
+ if (starts_with(file, dataPath) && ends_with(file, ".apk")) {
+ // Always install it if we didn't build it because otherwise
+ // it will never have changed.
+ installApks.push_back(InstallApk(file, !target->build));
+ continue;
+ }
+ }
+ }
+ }
+ map<string,FileInfo> systemFilesBefore;
+ if (syncSystem && !alwaysSyncSystem) {
+ get_directory_contents(systemPath, &systemFilesBefore);
+ }
+
+ //
+ // Build
+ //
+
+ // Run the build
+ if (goals.size() > 0) {
+ print_status("Building");
+ err = build_goals(goals);
+ check_error(err);
+ }
+
+ //
+ // Install
+ //
+
+ // Sync the system partition and reboot
+ bool skipSync = false;
+ if (syncSystem) {
+ print_status("Syncing /system");
+
+ if (!alwaysSyncSystem) {
+ // If nothing changed and we weren't forced to sync, skip the reboot for speed.
+ map<string,FileInfo> systemFilesAfter;
+ get_directory_contents(systemPath, &systemFilesAfter);
+ skipSync = !directory_contents_differ(systemFilesBefore, systemFilesAfter);
+ }
+ if (skipSync) {
+ printf("Skipping sync because no files changed.\n");
+ } else {
+ // Do some sanity checks
+ check_device_property("ro.build.product", buildProduct);
+ check_device_property("ro.build.type", buildVariant);
+ check_device_property("ro.build.id", buildId);
+
+ // Stop & Sync
+ err = run_adb("shell", "stop", NULL);
+ check_error(err);
+ err = run_adb("remount", NULL);
+ check_error(err);
+ err = run_adb("sync", "system", NULL);
+ check_error(err);
+
+ if (reboot) {
+ print_status("Rebooting");
+
+ err = run_adb("reboot", NULL);
+ check_error(err);
+ err = run_adb("wait-for-device", NULL);
+ check_error(err);
+ } else {
+ print_status("Restarting the runtime");
+
+ err = run_adb("shell", "setprop", "sys.boot_completed", "0", NULL);
+ check_error(err);
+ err = run_adb("shell", "start", NULL);
+ check_error(err);
+ }
+
+ while (true) {
+ string completed = get_system_property("sys.boot_completed", &err);
+ check_error(err);
+ if (completed == "1") {
+ break;
+ }
+ sleep(2);
+ }
+ sleep(1);
+ err = run_adb("shell", "wm", "dismiss-keyguard", NULL);
+ check_error(err);
+ }
+ }
+
+ // Install APKs
+ if (installApks.size() > 0) {
+ print_status("Installing APKs");
+ for (size_t i=0; i<installApks.size(); i++) {
+ InstallApk& apk = installApks[i];
+ if (!apk.file.fileInfo.exists || apk.file.HasChanged()) {
+ // It didn't exist before or it changed, so int needs install
+ err = run_adb("install", "-r", apk.file.filename.c_str(), NULL);
+ check_error(err);
+ apk.installed = true;
+ } else {
+ printf("APK didn't change. Skipping install of %s\n", apk.file.filename.c_str());
+ }
+ }
+ }
+
+ //
+ // Actions
+ //
+
+ // Inspect the apks, and figure out what is an activity and what needs a test runner
+ bool printedInspecting = false;
+ vector<TestAction> testActions;
+ vector<ActivityAction> activityActions;
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ if (target->test) {
+ for (size_t j=0; j<target->module.installed.size(); j++) {
+ string filename = target->module.installed[j];
+
+ if (!ends_with(filename, ".apk")) {
+ continue;
+ }
+
+ if (!printedInspecting) {
+ printedInspecting = true;
+ print_status("Inspecting APKs");
+ }
+
+ Apk apk;
+ err = inspect_apk(&apk, filename);
+ check_error(err);
+
+ for (size_t k=0; k<target->actions.size(); k++) {
+ string actionString = target->actions[k];
+ if (actionString == "*") {
+ if (apk.runner.length() == 0) {
+ print_error("Error: Test requested for apk that doesn't"
+ " have an <instrumentation> tag: %s\n",
+ target->module.name.c_str());
+ exit(1);
+ }
+ TestAction action;
+ action.packageName = apk.package;
+ action.runner = apk.runner;
+ action.target = target;
+ testActions.push_back(action);
+ target->testActionCount++;
+ } else if (apk.HasActivity(actionString)) {
+ ActivityAction action;
+ action.packageName = apk.package;
+ action.className = full_class_name(apk.package, actionString);
+ activityActions.push_back(action);
+ } else {
+ if (apk.runner.length() == 0) {
+ print_error("Error: Test requested for apk that doesn't"
+ " have an <instrumentation> tag: %s\n",
+ target->module.name.c_str());
+ exit(1);
+ }
+ TestAction action;
+ action.packageName = apk.package;
+ action.runner = apk.runner;
+ action.className = full_class_name(apk.package, actionString);
+ action.target = target;
+ testActions.push_back(action);
+ target->testActionCount++;
+ }
+ }
+ }
+ }
+ }
+
+ // Run the instrumentation tests
+ TestResults testResults;
+ if (testActions.size() > 0) {
+ print_status("Running tests");
+ for (size_t i=0; i<testActions.size(); i++) {
+ TestAction& action = testActions[i];
+ testResults.SetCurrentAction(&action);
+ err = run_instrumentation_test(action.packageName, action.runner, action.className,
+ &testResults);
+ check_error(err);
+ if (action.passCount == 0 && action.failCount == 0) {
+ action.target->actionsWithNoTests = true;
+ }
+ int total = action.passCount + action.failCount;
+ printf("%sRan %d test%s for %s. ", g_escapeClearLine,
+ total, total > 1 ? "s" : "", action.target->name.c_str());
+ if (action.passCount == 0 && action.failCount == 0) {
+ printf("%s%d passed, %d failed%s\n", g_escapeYellowBold, action.passCount,
+ action.failCount, g_escapeEndColor);
+ } else if (action.failCount > 0) {
+ printf("%d passed, %s%d failed%s\n", action.passCount, g_escapeRedBold,
+ action.failCount, g_escapeEndColor);
+ } else {
+ printf("%s%d passed%s, %d failed\n", g_escapeGreenBold, action.passCount,
+ g_escapeEndColor, action.failCount);
+ }
+ }
+ }
+
+ // Launch the activity
+ if (activityActions.size() > 0) {
+ print_status("Starting activity");
+
+ if (activityActions.size() > 1) {
+ print_warning("Multiple activities specified. Will only start the first one:");
+ for (size_t i=0; i<activityActions.size(); i++) {
+ ActivityAction& action = activityActions[i];
+ print_warning(" %s",
+ pretty_component_name(action.packageName, action.className).c_str());
+ }
+ }
+
+ const ActivityAction& action = activityActions[0];
+ string componentName = action.packageName + "/" + action.className;
+ err = run_adb("shell", "am", "start", componentName.c_str(), NULL);
+ check_error(err);
+ }
+
+ //
+ // Print summary
+ //
+
+ printf("\n%s--------------------------------------------%s\n", g_escapeBold, g_escapeEndColor);
+
+ // Build
+ if (goals.size() > 0) {
+ printf("%sBuilt:%s\n", g_escapeBold, g_escapeEndColor);
+ for (size_t i=0; i<goals.size(); i++) {
+ printf(" %s\n", goals[i].c_str());
+ }
+ }
+
+ // Install
+ if (syncSystem) {
+ if (skipSync) {
+ printf("%sSkipped syncing /system partition%s\n", g_escapeBold, g_escapeEndColor);
+ } else {
+ printf("%sSynced /system partition%s\n", g_escapeBold, g_escapeEndColor);
+ }
+ }
+ if (installApks.size() > 0) {
+ bool printedTitle = false;
+ for (size_t i=0; i<installApks.size(); i++) {
+ const InstallApk& apk = installApks[i];
+ if (apk.installed) {
+ if (!printedTitle) {
+ printf("%sInstalled:%s\n", g_escapeBold, g_escapeEndColor);
+ printedTitle = true;
+ }
+ printf(" %s\n", apk.file.filename.c_str());
+ }
+ }
+ printedTitle = false;
+ for (size_t i=0; i<installApks.size(); i++) {
+ const InstallApk& apk = installApks[i];
+ if (!apk.installed) {
+ if (!printedTitle) {
+ printf("%sSkipped install:%s\n", g_escapeBold, g_escapeEndColor);
+ printedTitle = true;
+ }
+ printf(" %s\n", apk.file.filename.c_str());
+ }
+ }
+ }
+
+ // Tests
+ if (testActions.size() > 0) {
+ printf("%sRan tests:%s\n", g_escapeBold, g_escapeEndColor);
+ size_t maxNameLength = 0;
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ if (target->test) {
+ size_t len = target->name.length();
+ if (len > maxNameLength) {
+ maxNameLength = len;
+ }
+ }
+ }
+ string padding(maxNameLength, ' ');
+ for (size_t i=0; i<targets.size(); i++) {
+ Target* target = targets[i];
+ if (target->testActionCount > 0) {
+ printf(" %s%s", target->name.c_str(), padding.c_str() + target->name.length());
+ if (target->actionsWithNoTests) {
+ printf(" %s%d passed, %d failed%s\n", g_escapeYellowBold,
+ target->testPassCount, target->testFailCount, g_escapeEndColor);
+ } else if (target->testFailCount > 0) {
+ printf(" %d passed, %s%d failed%s\n", target->testPassCount,
+ g_escapeRedBold, target->testFailCount, g_escapeEndColor);
+ } else {
+ printf(" %s%d passed%s, %d failed\n", g_escapeGreenBold,
+ target->testPassCount, g_escapeEndColor, target->testFailCount);
+ }
+ }
+ }
+ }
+ if (activityActions.size() > 1) {
+ printf("%sStarted Activity:%s\n", g_escapeBold, g_escapeEndColor);
+ const ActivityAction& action = activityActions[0];
+ printf(" %s\n", pretty_component_name(action.packageName, action.className).c_str());
+ }
+
+ printf("%s--------------------------------------------%s\n", g_escapeBold, g_escapeEndColor);
+}
+
+/**
+ * Implement tab completion of the target names from the all modules file.
+ */
+void
+run_tab_completion(const string& word)
+{
+ const string buildTop = get_required_env("ANDROID_BUILD_TOP", true);
+ const string buildProduct = get_required_env("TARGET_PRODUCT", false);
+ const string buildOut = get_out_dir();
+
+ chdir(buildTop.c_str());
+
+ string buildDevice = sniff_device_name(buildOut, buildProduct);
+
+ map<string,Module> modules;
+ read_modules(buildOut, buildDevice, &modules, true);
+
+ for (map<string,Module>::const_iterator it = modules.begin(); it != modules.end(); it++) {
+ if (starts_with(it->first, word)) {
+ printf("%s\n", it->first.c_str());
+ }
+ }
+}
+
+/**
+ * Main entry point.
+ */
+int
+main(int argc, const char** argv)
+{
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+ init_print();
+
+ Options options;
+ parse_args(&options, argc, argv);
+
+ if (options.runHelp) {
+ // Help
+ print_usage(stdout);
+ exit(0);
+ } else if (options.runTab) {
+ run_tab_completion(options.tabPattern);
+ exit(0);
+ } else {
+ // Normal run
+ run_phases(options.targets, options.reboot);
+ }
+
+ return 0;
+}
+
diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp
new file mode 100644
index 000000000000..60b5687bb313
--- /dev/null
+++ b/tools/bit/make.cpp
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "make.h"
+
+#include "command.h"
+#include "print.h"
+#include "util.h"
+
+#include <json/reader.h>
+#include <json/value.h>
+
+#include <fstream>
+#include <string>
+#include <map>
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+
+using namespace std;
+
+map<string,string> g_buildVars;
+
+string
+get_build_var(const string& buildTop, const string& name, bool quiet)
+{
+ int err;
+
+ map<string,string>::iterator it = g_buildVars.find(name);
+ if (it == g_buildVars.end()) {
+ Command cmd("make");
+ cmd.AddArg("--no-print-directory");
+ cmd.AddArg("-C");
+ cmd.AddArg(buildTop);
+ cmd.AddArg("-f");
+ cmd.AddArg("build/core/config.mk");
+ cmd.AddArg(string("dumpvar-") + name);
+ cmd.AddEnv("CALLED_FROM_SETUP", "true");
+ cmd.AddEnv("BUILD_SYSTEM", "build/core");
+
+ string output = trim(get_command_output(cmd, &err, quiet));
+ if (err == 0) {
+ g_buildVars[name] = output;
+ return output;
+ } else {
+ return string();
+ }
+ } else {
+ return it->second;
+ }
+}
+
+string
+sniff_device_name(const string& buildOut, const string& product)
+{
+ string match("ro.build.product=" + product);
+
+ string base(buildOut + "/target/product");
+ DIR* dir = opendir(base.c_str());
+ if (dir == NULL) {
+ return string();
+ }
+
+ dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+ if (entry->d_type == DT_DIR) {
+ string filename(base + "/" + entry->d_name + "/system/build.prop");
+ vector<string> lines;
+ split_lines(&lines, read_file(filename));
+ for (size_t i=0; i<lines.size(); i++) {
+ if (lines[i] == match) {
+ return entry->d_name;
+ }
+ }
+ }
+ }
+
+ closedir(dir);
+ return string();
+}
+
+void
+json_error(const string& filename, const char* error, bool quiet)
+{
+ if (!quiet) {
+ print_error("Unable to parse module info file (%s): %s", error, filename.c_str());
+ print_error("Have you done a full build?");
+ }
+ exit(1);
+}
+
+static void
+get_values(const Json::Value& json, const string& name, vector<string>* result)
+{
+ Json::Value nullValue;
+
+ const Json::Value& value = json.get(name, nullValue);
+ if (!value.isArray()) {
+ return;
+ }
+
+ const int N = value.size();
+ for (int i=0; i<N; i++) {
+ const Json::Value& child = value[i];
+ if (child.isString()) {
+ result->push_back(child.asString());
+ }
+ }
+}
+
+void
+read_modules(const string& buildOut, const string& device, map<string,Module>* result, bool quiet)
+{
+ string filename(string(buildOut + "/target/product/") + device + "/module-info.json");
+ std::ifstream stream(filename, std::ifstream::binary);
+
+ if (stream.fail()) {
+ if (!quiet) {
+ print_error("Unable to open module info file: %s", filename.c_str());
+ print_error("Have you done a full build?");
+ }
+ exit(1);
+ }
+
+ Json::Value json;
+ Json::Reader reader;
+ if (!reader.parse(stream, json)) {
+ json_error(filename, "can't parse json format", quiet);
+ return;
+ }
+
+ if (!json.isObject()) {
+ json_error(filename, "root element not an object", quiet);
+ return;
+ }
+
+ vector<string> names = json.getMemberNames();
+ const int N = names.size();
+ for (int i=0; i<N; i++) {
+ const string& name = names[i];
+
+ const Json::Value& value = json[name];
+ if (!value.isObject()) {
+ continue;
+ }
+
+ Module module;
+
+ module.name = name;
+ get_values(value, "class", &module.classes);
+ get_values(value, "path", &module.paths);
+ get_values(value, "installed", &module.installed);
+
+ // Only keep classes we can handle
+ for (ssize_t i = module.classes.size() - 1; i >= 0; i--) {
+ string cl = module.classes[i];
+ if (!(cl == "JAVA_LIBRARIES" || cl == "EXECUTABLES" || cl == "SHARED_LIBRARIES"
+ || cl == "APPS")) {
+ module.classes.erase(module.classes.begin() + i);
+ }
+ }
+ if (module.classes.size() == 0) {
+ continue;
+ }
+
+ // Only target modules (not host)
+ for (ssize_t i = module.installed.size() - 1; i >= 0; i--) {
+ string fn = module.installed[i];
+ if (!starts_with(fn, buildOut + "/target/")) {
+ module.installed.erase(module.installed.begin() + i);
+ }
+ }
+ if (module.installed.size() == 0) {
+ continue;
+ }
+
+ (*result)[name] = module;
+ }
+}
+
+int
+build_goals(const vector<string>& goals)
+{
+ Command cmd("make");
+ cmd.AddArg("-f");
+ cmd.AddArg("build/core/main.mk");
+ for (size_t i=0; i<goals.size(); i++) {
+ cmd.AddArg(goals[i]);
+ }
+
+ return run_command(cmd);
+}
+
diff --git a/tools/bit/make.h b/tools/bit/make.h
new file mode 100644
index 000000000000..bb83c6e14226
--- /dev/null
+++ b/tools/bit/make.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef MAKE_H
+#define MAKE_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+using namespace std;
+
+struct Module
+{
+ string name;
+ vector<string> classes;
+ vector<string> paths;
+ vector<string> installed;
+};
+
+string get_build_var(const string& buildTop, const string& name, bool quiet);
+
+/**
+ * Poke around in the out directory and try to find a device name that matches
+ * our product. This is faster than running get_build_var and good enough for
+ * tab completion.
+ *
+ * Returns the empty string if we can't find one.
+ */
+string sniff_device_name(const string& buildOut, const string& product);
+
+void read_modules(const string& buildOut, const string& buildDevice,
+ map<string,Module>* modules, bool quiet);
+
+int build_goals(const vector<string>& goals);
+
+#endif // MAKE_H
diff --git a/tools/bit/print.cpp b/tools/bit/print.cpp
new file mode 100644
index 000000000000..790e0b4b227e
--- /dev/null
+++ b/tools/bit/print.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "print.h"
+
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "util.h"
+
+bool g_stdoutIsTty;
+char const* g_escapeBold;
+char const* g_escapeRedBold;
+char const* g_escapeGreenBold;
+char const* g_escapeYellowBold;
+char const* g_escapeUnderline;
+char const* g_escapeEndColor;
+char const* g_escapeClearLine;
+
+void
+init_print()
+{
+ if (isatty(fileno(stdout))) {
+ g_stdoutIsTty = true;
+ g_escapeBold = "\033[1m";
+ g_escapeRedBold = "\033[91m\033[1m";
+ g_escapeGreenBold = "\033[92m\033[1m";
+ g_escapeYellowBold = "\033[93m\033[1m";
+ g_escapeUnderline = "\033[4m";
+ g_escapeEndColor = "\033[0m";
+ g_escapeClearLine = "\033[K";
+ } else {
+ g_stdoutIsTty = false;
+ g_escapeBold = "";
+ g_escapeRedBold = "";
+ g_escapeGreenBold = "";
+ g_escapeYellowBold = "";
+ g_escapeUnderline = "";
+ g_escapeEndColor = "";
+ g_escapeClearLine = "";
+ }
+}
+
+void
+print_status(const char* format, ...)
+{
+ printf("\n%s%s", g_escapeBold, g_escapeUnderline);
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stdout, format, args);
+ va_end(args);
+
+ printf("%s\n", g_escapeEndColor);
+}
+
+void
+print_command(const Command& command)
+{
+ fputs(g_escapeBold, stdout);
+ for (map<string,string>::const_iterator it=command.env.begin(); it!=command.env.end(); it++) {
+ fputs(it->first.c_str(), stdout);
+ fputc('=', stdout);
+ fputs(escape_for_commandline(it->second.c_str()).c_str(), stdout);
+ putc(' ', stdout);
+ }
+ fputs(command.prog.c_str(), stdout);
+ for (vector<string>::const_iterator it=command.args.begin(); it!=command.args.end(); it++) {
+ putc(' ', stdout);
+ fputs(escape_for_commandline(it->c_str()).c_str(), stdout);
+ }
+ fputs(g_escapeEndColor, stdout);
+ fputc('\n', stdout);
+}
+
+void
+print_error(const char* format, ...)
+{
+ fputs(g_escapeRedBold, stderr);
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+
+ fputs(g_escapeEndColor, stderr);
+ fputc('\n', stderr);
+}
+
+void
+print_warning(const char* format, ...)
+{
+ fputs(g_escapeYellowBold, stderr);
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+
+ fputs(g_escapeEndColor, stderr);
+ fputc('\n', stderr);
+}
+
+void
+print_one_line(const char* format, ...)
+{
+ if (g_stdoutIsTty) {
+ struct winsize ws;
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
+ int size = ws.ws_col + 1;
+ char* buf = (char*)malloc(size);
+
+ va_list args;
+ va_start(args, format);
+ vsnprintf(buf, size, format, args);
+ va_end(args);
+
+ printf("%s%s\r", buf, g_escapeClearLine);
+ free(buf);
+
+ fflush(stdout);
+ } else {
+ va_list args;
+ va_start(args, format);
+ vfprintf(stdout, format, args);
+ va_end(args);
+ printf("\n");
+ }
+}
+
+void
+check_error(int err)
+{
+ if (err != 0) {
+ fputc('\n', stderr);
+ print_error("Stopping due to errors.");
+ exit(1);
+ }
+}
+
+
diff --git a/tools/bit/print.h b/tools/bit/print.h
new file mode 100644
index 000000000000..b6c3e9aa27fa
--- /dev/null
+++ b/tools/bit/print.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef PRINT_H
+#define PRINT_H
+
+#include "command.h"
+
+extern bool g_stdoutIsTty;
+extern char const* g_escapeBold;
+extern char const* g_escapeRedBold;
+extern char const* g_escapeGreenBold;
+extern char const* g_escapeYellowBold;
+extern char const* g_escapeUnderline;
+extern char const* g_escapeEndColor;
+extern char const* g_escapeClearLine;
+
+void init_print();
+void print_status(const char* format, ...);
+void print_command(const Command& command);
+void print_error(const char* format, ...);
+void print_warning(const char* format, ...);
+void print_one_line(const char* format, ...);
+void check_error(int err);
+
+#endif // PRINT_H
diff --git a/tools/bit/util.cpp b/tools/bit/util.cpp
new file mode 100644
index 000000000000..fc93bcb8c935
--- /dev/null
+++ b/tools/bit/util.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "util.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <string.h>
+#include <unistd.h>
+
+
+FileInfo::FileInfo()
+{
+ memset(this, 0, sizeof(FileInfo));
+}
+
+FileInfo::FileInfo(const FileInfo& that)
+{
+ memcpy(this, &that, sizeof(FileInfo));
+}
+
+FileInfo::FileInfo(const string& filename)
+{
+ struct stat st;
+ int err = stat(filename.c_str(), &st);
+ if (err != 0) {
+ memset(this, 0, sizeof(FileInfo));
+ } else {
+ exists = true;
+ mtime = st.st_mtime;
+ ctime = st.st_ctime;
+ size = st.st_size;
+ }
+}
+
+bool
+FileInfo::operator==(const FileInfo& that) const
+{
+ return exists == that.exists
+ && mtime == that.mtime
+ && ctime == that.ctime
+ && size == that.size;
+}
+
+bool
+FileInfo::operator!=(const FileInfo& that) const
+{
+ return exists != that.exists
+ || mtime != that.mtime
+ || ctime != that.ctime
+ || size != that.size;
+}
+
+FileInfo::~FileInfo()
+{
+}
+
+TrackedFile::TrackedFile()
+ :filename(),
+ fileInfo()
+{
+}
+
+TrackedFile::TrackedFile(const TrackedFile& that)
+{
+ filename = that.filename;
+ fileInfo = that.fileInfo;
+}
+
+TrackedFile::TrackedFile(const string& file)
+ :filename(file),
+ fileInfo(file)
+{
+}
+
+TrackedFile::~TrackedFile()
+{
+}
+
+bool
+TrackedFile::HasChanged() const
+{
+ FileInfo updated(filename);
+ return !updated.exists || fileInfo != updated;
+}
+
+void
+get_directory_contents(const string& name, map<string,FileInfo>* results)
+{
+ int err;
+ DIR* dir = opendir(name.c_str());
+ if (dir == NULL) {
+ return;
+ }
+
+ dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
+ continue;
+ }
+ if (entry->d_type == DT_DIR) {
+ string subdir = name + "/" + entry->d_name;
+ get_directory_contents(subdir, results);
+ } else if (entry->d_type == DT_LNK || entry->d_type == DT_REG) {
+ string filename(name + "/" + entry->d_name);
+ (*results)[filename] = FileInfo(filename);
+ }
+ }
+
+ closedir(dir);
+}
+
+bool
+directory_contents_differ(const map<string,FileInfo>& before, const map<string,FileInfo>& after)
+{
+ if (before.size() != after.size()) {
+ return true;
+ }
+ map<string,FileInfo>::const_iterator b = before.begin();
+ map<string,FileInfo>::const_iterator a = after.begin();
+ while (b != before.end() && a != after.end()) {
+ if (b->first != a->first) {
+ return true;
+ }
+ if (a->second != b->second) {
+ return true;
+ }
+ a++;
+ b++;
+ }
+ return false;
+}
+
+string
+escape_quotes(const char* str)
+{
+ string result;
+ while (*str) {
+ if (*str == '"') {
+ result += '\\';
+ result += '"';
+ } else {
+ result += *str;
+ }
+ }
+ return result;
+}
+
+string
+escape_for_commandline(const char* str)
+{
+ if (strchr(str, '"') != NULL || strchr(str, ' ') != NULL
+ || strchr(str, '\t') != NULL) {
+ return escape_quotes(str);
+ } else {
+ return str;
+ }
+}
+
+static bool
+spacechr(char c)
+{
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+}
+
+string
+trim(const string& str)
+{
+ const ssize_t N = (ssize_t)str.size();
+ ssize_t begin = 0;
+ while (begin < N && spacechr(str[begin])) {
+ begin++;
+ }
+ ssize_t end = N - 1;
+ while (end >= begin && spacechr(str[end])) {
+ end--;
+ }
+ return string(str, begin, end-begin+1);
+}
+
+bool
+starts_with(const string& str, const string& prefix)
+{
+ return str.compare(0, prefix.length(), prefix) == 0;
+}
+
+bool
+ends_with(const string& str, const string& suffix)
+{
+ if (str.length() < suffix.length()) {
+ return false;
+ } else {
+ return str.compare(str.length()-suffix.length(), suffix.length(), suffix) == 0;
+ }
+}
+
+void
+split_lines(vector<string>* result, const string& str)
+{
+ const int N = str.length();
+ int begin = 0;
+ int end = 0;
+ for (; end < N; end++) {
+ const char c = str[end];
+ if (c == '\r' || c == '\n') {
+ if (begin != end) {
+ result->push_back(string(str, begin, end-begin));
+ }
+ begin = end+1;
+ }
+ }
+ if (begin != end) {
+ result->push_back(string(str, begin, end-begin));
+ }
+}
+
+string
+read_file(const string& filename)
+{
+ FILE* file = fopen(filename.c_str(), "r");
+ if (file == NULL) {
+ return string();
+ }
+
+ fseek(file, 0, SEEK_END);
+ int size = ftell(file);
+ fseek(file, 0, SEEK_SET);
+
+ char* buf = (char*)malloc(size);
+ fread(buf, 1, size, file);
+
+ string result(buf, size);
+
+ free(buf);
+ fclose(file);
+
+ return result;
+}
+
+
diff --git a/tools/bit/util.h b/tools/bit/util.h
new file mode 100644
index 000000000000..718f1474a969
--- /dev/null
+++ b/tools/bit/util.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+using namespace std;
+
+struct FileInfo
+{
+ bool exists;
+ time_t mtime;
+ time_t ctime;
+ off_t size;
+
+ FileInfo();
+ FileInfo(const FileInfo& that);
+ explicit FileInfo(const string& filename);
+ ~FileInfo();
+
+ bool operator==(const FileInfo& that) const;
+ bool operator!=(const FileInfo& that) const;
+};
+
+
+/**
+ * Record for a file that we are watching
+ */
+struct TrackedFile {
+ string filename;
+ FileInfo fileInfo;
+
+ TrackedFile();
+ TrackedFile(const TrackedFile& that);
+ explicit TrackedFile(const string& filename);
+ ~TrackedFile();
+
+ // Returns if the file has changed. If it doesn't currently exist, returns true.
+ bool HasChanged() const;
+};
+
+/**
+ * Get FileInfo structures recursively for all the files and symlinks in a directory.
+ * Does not traverse symlinks, but it does record them.
+ */
+void get_directory_contents(const string& dir, map<string,FileInfo>* results);
+
+bool directory_contents_differ(const map<string,FileInfo>& before,
+ const map<string,FileInfo>& after);
+
+string escape_quotes(const char* str);
+
+string escape_for_commandline(const char* str);
+
+string trim(const string& trim);
+
+bool starts_with(const string& str, const string& prefix);
+
+bool ends_with(const string& str, const string& suffix);
+
+void split_lines(vector<string>* result, const string& str);
+
+string read_file(const string& filename);
+
+#endif // UTIL_H
+
diff --git a/tools/streaming_proto/main.cpp b/tools/streaming_proto/main.cpp
index d2862137b216..5435728a3d4b 100644
--- a/tools/streaming_proto/main.cpp
+++ b/tools/streaming_proto/main.cpp
@@ -191,38 +191,55 @@ get_field_id(const FieldDescriptorProto& field)
switch (field.type()) {
case FieldDescriptorProto::TYPE_DOUBLE:
result |= FIELD_TYPE_DOUBLE;
+ break;
case FieldDescriptorProto::TYPE_FLOAT:
result |= FIELD_TYPE_FLOAT;
+ break;
case FieldDescriptorProto::TYPE_INT64:
result |= FIELD_TYPE_INT64;
+ break;
case FieldDescriptorProto::TYPE_UINT64:
result |= FIELD_TYPE_UINT64;
+ break;
case FieldDescriptorProto::TYPE_INT32:
result |= FIELD_TYPE_INT32;
+ break;
case FieldDescriptorProto::TYPE_FIXED64:
result |= FIELD_TYPE_FIXED64;
+ break;
case FieldDescriptorProto::TYPE_FIXED32:
result |= FIELD_TYPE_FIXED32;
+ break;
case FieldDescriptorProto::TYPE_BOOL:
result |= FIELD_TYPE_BOOL;
+ break;
case FieldDescriptorProto::TYPE_STRING:
result |= FIELD_TYPE_STRING;
+ break;
case FieldDescriptorProto::TYPE_MESSAGE:
result |= FIELD_TYPE_OBJECT;
+ break;
case FieldDescriptorProto::TYPE_BYTES:
result |= FIELD_TYPE_BYTES;
+ break;
case FieldDescriptorProto::TYPE_UINT32:
result |= FIELD_TYPE_UINT32;
+ break;
case FieldDescriptorProto::TYPE_ENUM:
result |= FIELD_TYPE_ENUM;
+ break;
case FieldDescriptorProto::TYPE_SFIXED32:
result |= FIELD_TYPE_SFIXED32;
+ break;
case FieldDescriptorProto::TYPE_SFIXED64:
result |= FIELD_TYPE_SFIXED64;
+ break;
case FieldDescriptorProto::TYPE_SINT32:
result |= FIELD_TYPE_SINT32;
+ break;
case FieldDescriptorProto::TYPE_SINT64:
result |= FIELD_TYPE_SINT64;
+ break;
default:
;
}