1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "gflags/gflags.h" 18 19 #include "android-base/stringprintf.h" 20 #include "apk_layout_compiler.h" 21 #include "dex_builder.h" 22 #include "dex_layout_compiler.h" 23 #include "java_lang_builder.h" 24 #include "layout_validation.h" 25 #include "tinyxml_layout_parser.h" 26 #include "util.h" 27 28 #include "tinyxml2.h" 29 30 #include <fstream> 31 #include <iostream> 32 #include <sstream> 33 #include <string> 34 #include <vector> 35 36 namespace { 37 38 using namespace tinyxml2; 39 using android::base::StringPrintf; 40 using startop::dex::ClassBuilder; 41 using startop::dex::DexBuilder; 42 using startop::dex::MethodBuilder; 43 using startop::dex::Prototype; 44 using startop::dex::TypeDescriptor; 45 using namespace startop::util; 46 using std::string; 47 48 constexpr char kStdoutFilename[]{"stdout"}; 49 50 DEFINE_bool(apk, false, "Compile layouts in an APK"); 51 DEFINE_bool(dex, false, "Generate a DEX file instead of Java"); 52 DEFINE_int32(infd, -1, "Read input from the given file descriptor"); 53 DEFINE_string(out, kStdoutFilename, "Where to write the generated class"); 54 DEFINE_string(package, "", "The package name for the generated class (required)"); 55 56 template <typename Visitor> 57 class XmlVisitorAdapter : public XMLVisitor { 58 public: 59 explicit XmlVisitorAdapter(Visitor* visitor) : visitor_{visitor} {} 60 61 bool VisitEnter(const XMLDocument& /*doc*/) override { 62 visitor_->VisitStartDocument(); 63 return true; 64 } 65 66 bool VisitExit(const XMLDocument& /*doc*/) override { 67 visitor_->VisitEndDocument(); 68 return true; 69 } 70 71 bool VisitEnter(const XMLElement& element, const XMLAttribute* /*firstAttribute*/) override { 72 visitor_->VisitStartTag( 73 std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes( 74 element.Name())); 75 return true; 76 } 77 78 bool VisitExit(const XMLElement& /*element*/) override { 79 visitor_->VisitEndTag(); 80 return true; 81 } 82 83 private: 84 Visitor* visitor_; 85 }; 86 87 template <typename Builder> 88 void CompileLayout(XMLDocument* xml, Builder* builder) { 89 startop::LayoutCompilerVisitor visitor{builder}; 90 XmlVisitorAdapter<decltype(visitor)> adapter{&visitor}; 91 xml->Accept(&adapter); 92 } 93 94 } // end namespace 95 96 int main(int argc, char** argv) { 97 constexpr size_t kProgramName = 0; 98 constexpr size_t kFileNameParam = 1; 99 constexpr size_t kNumRequiredArgs = 1; 100 101 gflags::SetUsageMessage( 102 "Compile XML layout files into equivalent Java language code\n" 103 "\n" 104 " example usage: viewcompiler layout.xml --package com.example.androidapp"); 105 gflags::ParseCommandLineFlags(&argc, &argv, /*remove_flags*/ true); 106 107 gflags::CommandLineFlagInfo cmd = gflags::GetCommandLineFlagInfoOrDie("package"); 108 if (argc < kNumRequiredArgs || cmd.is_default) { 109 gflags::ShowUsageWithFlags(argv[kProgramName]); 110 return 1; 111 } 112 113 const bool is_stdout = FLAGS_out == kStdoutFilename; 114 115 std::ofstream outfile; 116 if (!is_stdout) { 117 outfile.open(FLAGS_out); 118 } 119 120 if (FLAGS_apk) { 121 const startop::CompilationTarget target = 122 FLAGS_dex ? startop::CompilationTarget::kDex : startop::CompilationTarget::kJavaLanguage; 123 if (FLAGS_infd >= 0) { 124 startop::CompileApkLayoutsFd( 125 android::base::unique_fd{FLAGS_infd}, target, is_stdout ? std::cout : outfile); 126 } else { 127 if (argc < 2) { 128 gflags::ShowUsageWithFlags(argv[kProgramName]); 129 return 1; 130 } 131 const char* const filename = argv[kFileNameParam]; 132 startop::CompileApkLayouts(filename, target, is_stdout ? std::cout : outfile); 133 } 134 return 0; 135 } 136 137 const char* const filename = argv[kFileNameParam]; 138 const string layout_name = startop::util::FindLayoutNameFromFilename(filename); 139 140 XMLDocument xml; 141 xml.LoadFile(filename); 142 143 string message{}; 144 if (!startop::CanCompileLayout(xml, &message)) { 145 LOG(ERROR) << "Layout not supported: " << message; 146 return 1; 147 } 148 149 if (FLAGS_dex) { 150 DexBuilder dex_file; 151 string class_name = StringPrintf("%s.CompiledView", FLAGS_package.c_str()); 152 ClassBuilder compiled_view{dex_file.MakeClass(class_name)}; 153 MethodBuilder method{compiled_view.CreateMethod( 154 layout_name, 155 Prototype{TypeDescriptor::FromClassname("android.view.View"), 156 TypeDescriptor::FromClassname("android.content.Context"), 157 TypeDescriptor::Int()})}; 158 startop::DexViewBuilder builder{&method}; 159 CompileLayout(&xml, &builder); 160 method.Encode(); 161 162 slicer::MemView image{dex_file.CreateImage()}; 163 164 (is_stdout ? std::cout : outfile).write(image.ptr<const char>(), image.size()); 165 } else { 166 // Generate Java language output. 167 JavaLangViewBuilder builder{FLAGS_package, layout_name, is_stdout ? std::cout : outfile}; 168 169 CompileLayout(&xml, &builder); 170 } 171 return 0; 172 } 173