Loading startop/view_compiler/Android.bp +18 −0 Original line number Diff line number Diff line Loading @@ -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", Loading startop/view_compiler/apk_layout_compiler.cc 0 → 100644 +159 −0 Original line number Diff line number Diff line /* * 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 startop/view_compiler/apk_layout_compiler.h 0 → 100644 +31 −0 Original line number Diff line number Diff line /* * 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_ No newline at end of file startop/view_compiler/main.cc +17 −7 Original line number Diff line number Diff line Loading @@ -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" Loading Loading @@ -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)"); Loading Loading @@ -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; Loading @@ -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()); Loading Loading
startop/view_compiler/Android.bp +18 −0 Original line number Diff line number Diff line Loading @@ -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", Loading
startop/view_compiler/apk_layout_compiler.cc 0 → 100644 +159 −0 Original line number Diff line number Diff line /* * 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
startop/view_compiler/apk_layout_compiler.h 0 → 100644 +31 −0 Original line number Diff line number Diff line /* * 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_ No newline at end of file
startop/view_compiler/main.cc +17 −7 Original line number Diff line number Diff line Loading @@ -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" Loading Loading @@ -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)"); Loading Loading @@ -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; Loading @@ -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()); Loading