Merge "[view compiler] Compile all layouts in an APK"
This commit is contained in:
@@ -22,17 +22,35 @@ cc_defaults {
|
|||||||
shared_libs: [
|
shared_libs: [
|
||||||
"libbase",
|
"libbase",
|
||||||
"libdexfile",
|
"libdexfile",
|
||||||
|
"libz",
|
||||||
"slicer",
|
"slicer",
|
||||||
],
|
],
|
||||||
static_libs: [
|
static_libs: [
|
||||||
"libtinyxml2",
|
"libtinyxml2",
|
||||||
|
"liblog",
|
||||||
|
"libutils",
|
||||||
|
"libziparchive",
|
||||||
],
|
],
|
||||||
|
cppflags: ["-std=c++17"],
|
||||||
|
target: {
|
||||||
|
android: {
|
||||||
|
shared_libs: [
|
||||||
|
"libandroidfw",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
host: {
|
||||||
|
static_libs: [
|
||||||
|
"libandroidfw",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cc_library_host_static {
|
cc_library_host_static {
|
||||||
name: "libviewcompiler",
|
name: "libviewcompiler",
|
||||||
defaults: ["viewcompiler_defaults"],
|
defaults: ["viewcompiler_defaults"],
|
||||||
srcs: [
|
srcs: [
|
||||||
|
"apk_layout_compiler.cc",
|
||||||
"dex_builder.cc",
|
"dex_builder.cc",
|
||||||
"dex_layout_compiler.cc",
|
"dex_layout_compiler.cc",
|
||||||
"java_lang_builder.cc",
|
"java_lang_builder.cc",
|
||||||
|
|||||||
159
startop/view_compiler/apk_layout_compiler.cc
Normal file
159
startop/view_compiler/apk_layout_compiler.cc
Normal 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
|
||||||
31
startop/view_compiler/apk_layout_compiler.h
Normal file
31
startop/view_compiler/apk_layout_compiler.h
Normal 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_
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "gflags/gflags.h"
|
#include "gflags/gflags.h"
|
||||||
|
|
||||||
#include "android-base/stringprintf.h"
|
#include "android-base/stringprintf.h"
|
||||||
|
#include "apk_layout_compiler.h"
|
||||||
#include "dex_builder.h"
|
#include "dex_builder.h"
|
||||||
#include "dex_layout_compiler.h"
|
#include "dex_layout_compiler.h"
|
||||||
#include "java_lang_builder.h"
|
#include "java_lang_builder.h"
|
||||||
@@ -46,6 +47,7 @@ using std::string;
|
|||||||
|
|
||||||
constexpr char kStdoutFilename[]{"stdout"};
|
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_bool(dex, false, "Generate a DEX file instead of Java");
|
||||||
DEFINE_string(out, kStdoutFilename, "Where to write the generated class");
|
DEFINE_string(out, kStdoutFilename, "Where to write the generated class");
|
||||||
DEFINE_string(package, "", "The package name for the generated class (required)");
|
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 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);
|
const string layout_name = startop::util::FindLayoutNameFromFilename(filename);
|
||||||
|
|
||||||
XMLDocument xml;
|
XMLDocument xml;
|
||||||
@@ -119,13 +136,6 @@ int main(int argc, char** argv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool is_stdout = FLAGS_out == kStdoutFilename;
|
|
||||||
|
|
||||||
std::ofstream outfile;
|
|
||||||
if (!is_stdout) {
|
|
||||||
outfile.open(FLAGS_out);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FLAGS_dex) {
|
if (FLAGS_dex) {
|
||||||
DexBuilder dex_file;
|
DexBuilder dex_file;
|
||||||
string class_name = StringPrintf("%s.CompiledView", FLAGS_package.c_str());
|
string class_name = StringPrintf("%s.CompiledView", FLAGS_package.c_str());
|
||||||
|
|||||||
Reference in New Issue
Block a user