Merge "[view compiler] Compile all layouts in an APK"

am: a95eac0c59

Change-Id: Ib22fce0fdc51560ff9a4cdf23a5daa545632dad5
This commit is contained in:
Eric Holk
2019-01-08 10:17:20 -08:00
committed by android-build-merger
4 changed files with 225 additions and 7 deletions

View File

@@ -22,17 +22,35 @@ cc_defaults {
shared_libs: [
"libbase",
"libdexfile",
"libz",
"slicer",
],
static_libs: [
"libtinyxml2",
"liblog",
"libutils",
"libziparchive",
],
cppflags: ["-std=c++17"],
target: {
android: {
shared_libs: [
"libandroidfw",
],
},
host: {
static_libs: [
"libandroidfw",
],
},
},
}
cc_library_host_static {
name: "libviewcompiler",
defaults: ["viewcompiler_defaults"],
srcs: [
"apk_layout_compiler.cc",
"dex_builder.cc",
"dex_layout_compiler.cc",
"java_lang_builder.cc",

View File

@@ -0,0 +1,159 @@
/*
* Copyright (C) 2018 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 "apk_layout_compiler.h"
#include "dex_layout_compiler.h"
#include "java_lang_builder.h"
#include "layout_validation.h"
#include "util.h"
#include "androidfw/ApkAssets.h"
#include "androidfw/AssetManager2.h"
#include "androidfw/ResourceTypes.h"
#include <iostream>
#include <locale>
#include "android-base/stringprintf.h"
namespace startop {
using android::ResXMLParser;
using android::base::StringPrintf;
class ResXmlVisitorAdapter {
public:
ResXmlVisitorAdapter(ResXMLParser* parser) : parser_{parser} {}
template <typename Visitor>
void Accept(Visitor* visitor) {
size_t depth{0};
do {
switch (parser_->next()) {
case ResXMLParser::START_DOCUMENT:
depth++;
visitor->VisitStartDocument();
break;
case ResXMLParser::END_DOCUMENT:
depth--;
visitor->VisitEndDocument();
break;
case ResXMLParser::START_TAG: {
depth++;
size_t name_length = 0;
const char16_t* name = parser_->getElementName(&name_length);
visitor->VisitStartTag(std::u16string{name, name_length});
break;
}
case ResXMLParser::END_TAG:
depth--;
visitor->VisitEndTag();
break;
default:;
}
} while (depth > 0 || parser_->getEventType() == ResXMLParser::FIRST_CHUNK_CODE);
}
private:
ResXMLParser* parser_;
};
bool CanCompileLayout(ResXMLParser* parser) {
ResXmlVisitorAdapter adapter{parser};
LayoutValidationVisitor visitor;
adapter.Accept(&visitor);
return visitor.can_compile();
}
void CompileApkLayouts(const std::string& filename, CompilationTarget target,
std::ostream& target_out) {
auto assets = android::ApkAssets::Load(filename);
android::AssetManager2 resources;
resources.SetApkAssets({assets.get()});
std::string package_name;
// TODO: handle multiple packages better
bool first = true;
for (const auto& package : assets->GetLoadedArsc()->GetPackages()) {
CHECK(first);
package_name = package->GetPackageName();
first = false;
}
dex::DexBuilder dex_file;
dex::ClassBuilder compiled_view{
dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))};
std::vector<dex::MethodBuilder> methods;
assets->ForEachFile("res/", [&](const android::StringPiece& s, android::FileType) {
if (s == "layout") {
auto path = StringPrintf("res/%s/", s.to_string().c_str());
assets->ForEachFile(path, [&](const android::StringPiece& layout_file, android::FileType) {
auto layout_path = StringPrintf("%s%s", path.c_str(), layout_file.to_string().c_str());
android::ApkAssetsCookie cookie = android::kInvalidCookie;
auto asset = resources.OpenNonAsset(layout_path, android::Asset::ACCESS_RANDOM, &cookie);
CHECK(asset);
CHECK(android::kInvalidCookie != cookie);
const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
CHECK(nullptr != dynamic_ref_table);
android::ResXMLTree xml_tree{dynamic_ref_table};
xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true),
asset->getLength(),
/*copy_data=*/true);
android::ResXMLParser parser{xml_tree};
parser.restart();
if (CanCompileLayout(&parser)) {
parser.restart();
const std::string layout_name = startop::util::FindLayoutNameFromFilename(layout_path);
ResXmlVisitorAdapter adapter{&parser};
switch (target) {
case CompilationTarget::kDex: {
methods.push_back(compiled_view.CreateMethod(
layout_name,
dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.View"),
dex::TypeDescriptor::FromClassname("android.content.Context"),
dex::TypeDescriptor::Int()}));
DexViewBuilder builder(&methods.back());
builder.Start();
LayoutCompilerVisitor visitor{&builder};
adapter.Accept(&visitor);
builder.Finish();
methods.back().Encode();
break;
}
case CompilationTarget::kJavaLanguage: {
JavaLangViewBuilder builder{package_name, layout_name, target_out};
builder.Start();
LayoutCompilerVisitor visitor{&builder};
adapter.Accept(&visitor);
builder.Finish();
break;
}
}
}
});
}
});
if (target == CompilationTarget::kDex) {
slicer::MemView image{dex_file.CreateImage()};
target_out.write(image.ptr<const char>(), image.size());
}
}
} // namespace startop

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2018 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 APK_LAYOUT_COMPILER_H_
#define APK_LAYOUT_COMPILER_H_
#include <string>
namespace startop {
enum class CompilationTarget { kJavaLanguage, kDex };
void CompileApkLayouts(const std::string& filename, CompilationTarget target,
std::ostream& target_out);
} // namespace startop
#endif // APK_LAYOUT_COMPILER_H_

View File

@@ -17,6 +17,7 @@
#include "gflags/gflags.h"
#include "android-base/stringprintf.h"
#include "apk_layout_compiler.h"
#include "dex_builder.h"
#include "dex_layout_compiler.h"
#include "java_lang_builder.h"
@@ -46,6 +47,7 @@ using std::string;
constexpr char kStdoutFilename[]{"stdout"};
DEFINE_bool(apk, false, "Compile layouts in an APK");
DEFINE_bool(dex, false, "Generate a DEX file instead of Java");
DEFINE_string(out, kStdoutFilename, "Where to write the generated class");
DEFINE_string(package, "", "The package name for the generated class (required)");
@@ -108,6 +110,21 @@ int main(int argc, char** argv) {
}
const char* const filename = argv[kFileNameParam];
const bool is_stdout = FLAGS_out == kStdoutFilename;
std::ofstream outfile;
if (!is_stdout) {
outfile.open(FLAGS_out);
}
if (FLAGS_apk) {
startop::CompileApkLayouts(
filename,
FLAGS_dex ? startop::CompilationTarget::kDex : startop::CompilationTarget::kJavaLanguage,
is_stdout ? std::cout : outfile);
return 0;
}
const string layout_name = startop::util::FindLayoutNameFromFilename(filename);
XMLDocument xml;
@@ -119,13 +136,6 @@ int main(int argc, char** argv) {
return 1;
}
const bool is_stdout = FLAGS_out == kStdoutFilename;
std::ofstream outfile;
if (!is_stdout) {
outfile.open(FLAGS_out);
}
if (FLAGS_dex) {
DexBuilder dex_file;
string class_name = StringPrintf("%s.CompiledView", FLAGS_package.c_str());