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:
XmlVisitorAdapter(Visitor * visitor)59 explicit XmlVisitorAdapter(Visitor* visitor) : visitor_{visitor} {}
60
VisitEnter(const XMLDocument &)61 bool VisitEnter(const XMLDocument& /*doc*/) override {
62 visitor_->VisitStartDocument();
63 return true;
64 }
65
VisitExit(const XMLDocument &)66 bool VisitExit(const XMLDocument& /*doc*/) override {
67 visitor_->VisitEndDocument();
68 return true;
69 }
70
VisitEnter(const XMLElement & element,const XMLAttribute *)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
VisitExit(const XMLElement &)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>
CompileLayout(XMLDocument * xml,Builder * 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
main(int argc,char ** argv)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