Merge "[view compiler] Compile all layouts in an APK"
This commit is contained in:
@@ -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",
|
||||
|
||||
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 "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());
|
||||
|
||||
Reference in New Issue
Block a user