0578cbc6c5
This is a cleaned up version of a utility that I've had sitting around for a while. The bit tool can run an android platform build, then sync or install the outputs of that, and then run instrumentation tests. It's better than what we usually do: make && adb shell stop && adb sync system && adb shell start Here's the help text: usage: bit OPTIONS PATTERN Build, sync and test android code. The -b -i and -t options allow you to specify which phases you want to run. If none of those options are given, then all phases are run. If any of these options are provided then only the listed phases are run. OPTIONS -b Run a build -i Install the targets -t Run the tests PATTERN One or more targets to build, install and test. The target names are the names that appear in the LOCAL_MODULE or LOCAL_PACKAGE_NAME variables in Android.mk or Android.bp files. Building and installing ----------------------- The modules specified will be built and then installed. If the files are on the system partition, they will be synced and the attached device rebooted. If they are APKs that aren't on the system partition they are installed with adb install. For example: bit framework Builds framework.jar, syncs the system partition and reboots. bit SystemUI Builds SystemUI.apk, syncs the system partition and reboots. bit CtsProtoTestCases Builds this CTS apk, adb installs it, but does not run any tests. Running Unit Tests ------------------ To run a unit test, list the test class names and optionally the test method after the module. For example: bit CtsProtoTestCases:* Builds this CTS apk, adb installs it, and runs all the tests contained in that apk. bit framework CtsProtoTestCases:* Builds the framework and the apk, syncs and reboots, then adb installs CtsProtoTestCases.apk, and runs all tests contained in that apk. bit CtsProtoTestCases:.ProtoOutputStreamBoolTest bit CtsProtoTestCases:android.util.proto.cts.ProtoOutputStreamBoolTest Builds and installs CtsProtoTestCases.apk, and runs all the tests in the ProtoOutputStreamBoolTest class. bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\#testWrite Builds and installs CtsProtoTestCases.apk, and runs the testWrite test method on that class. bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\#testWrite,.ProtoOutputStreamBoolTest\#testRepeated Builds and installs CtsProtoTestCases.apk, and runs the testWrite and testRepeated test methods on that class. Launching an Activity --------------------- To launch an activity, specify the activity class name after the module name. For example: bit StatusBarTest:NotificationBuilderTest bit StatusBarTest:.NotificationBuilderTest bit StatusBarTest:com.android.statusbartest.NotificationBuilderTest Builds and installs StatusBarTest.apk, launches the com.android.statusbartest/.NotificationBuilderTest activity. Change-Id: I9cff7a23852fa1a67369e7807f7ae9f6e45d6131 Test: none
271 lines
7.3 KiB
C++
271 lines
7.3 KiB
C++
/*
|
|
* 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;
|
|
}
|
|
|