Merge "Prototype XML view compiler" am: 956791cee8
am: dcdd224bc0
Change-Id: I1920a587ab1dfb66f78123f68932fb7c7969fa40
This commit is contained in:
49
startop/tools/view_compiler/Android.bp
Normal file
49
startop/tools/view_compiler/Android.bp
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
cc_library_host_static {
|
||||
name: "libviewcompiler",
|
||||
srcs: [
|
||||
"java_lang_builder.cc",
|
||||
"util.cc",
|
||||
],
|
||||
static_libs: [
|
||||
"libbase"
|
||||
]
|
||||
}
|
||||
|
||||
cc_binary_host {
|
||||
name: "viewcompiler",
|
||||
srcs: [
|
||||
"main.cc",
|
||||
],
|
||||
static_libs: [
|
||||
"libbase",
|
||||
"libtinyxml2",
|
||||
"libgflags",
|
||||
"libviewcompiler",
|
||||
],
|
||||
}
|
||||
|
||||
cc_test_host {
|
||||
name: "view-compiler-tests",
|
||||
srcs: [
|
||||
"util_test.cc",
|
||||
],
|
||||
static_libs: [
|
||||
"libviewcompiler",
|
||||
]
|
||||
}
|
||||
25
startop/tools/view_compiler/README.md
Normal file
25
startop/tools/view_compiler/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# View Compiler
|
||||
|
||||
This directory contains an experimental compiler for layout files.
|
||||
|
||||
It will take a layout XML file and produce a CompiledLayout.java file with a
|
||||
specialized layout inflation function.
|
||||
|
||||
To use it, let's assume you had a layout in `my_layout.xml` and your app was in
|
||||
the Java language package `com.example.myapp`. Run the following command:
|
||||
|
||||
viewcompiler my_layout.xml --package com.example.myapp --out CompiledView.java
|
||||
|
||||
This will produce a `CompiledView.java`, which can then be compiled into your
|
||||
Android app. Then to use it, in places where you would have inflated
|
||||
`R.layouts.my_layout`, instead call `CompiledView.inflate`.
|
||||
|
||||
Precompiling views like this generally improves the time needed to inflate them.
|
||||
|
||||
This tool is still in its early stages and has a number of limitations.
|
||||
* Currently only one layout can be compiled at a time.
|
||||
* `merge` and `include` nodes are not supported.
|
||||
* View compilation is a manual process that requires code changes in the
|
||||
application.
|
||||
* This only works for apps that do not use a custom layout inflater.
|
||||
* Other limitations yet to be discovered.
|
||||
7
startop/tools/view_compiler/TEST_MAPPING
Normal file
7
startop/tools/view_compiler/TEST_MAPPING
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "view-compiler-tests"
|
||||
}
|
||||
]
|
||||
}
|
||||
115
startop/tools/view_compiler/java_lang_builder.cc
Normal file
115
startop/tools/view_compiler/java_lang_builder.cc
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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 "java_lang_builder.h"
|
||||
|
||||
#include "android-base/stringprintf.h"
|
||||
|
||||
using android::base::StringPrintf;
|
||||
using std::string;
|
||||
|
||||
void JavaLangViewBuilder::Start() const {
|
||||
out_ << StringPrintf("package %s;\n", package_.c_str())
|
||||
<< "import android.content.Context;\n"
|
||||
"import android.content.res.Resources;\n"
|
||||
"import android.content.res.XmlResourceParser;\n"
|
||||
"import android.util.AttributeSet;\n"
|
||||
"import android.util.Xml;\n"
|
||||
"import android.view.*;\n"
|
||||
"import android.widget.*;\n"
|
||||
"\n"
|
||||
"public final class CompiledView {\n"
|
||||
"\n"
|
||||
"static <T extends View> T createView(Context context, AttributeSet attrs, View parent, "
|
||||
"String name, LayoutInflater.Factory factory, LayoutInflater.Factory2 factory2) {"
|
||||
"\n"
|
||||
" if (factory2 != null) {\n"
|
||||
" return (T)factory2.onCreateView(parent, name, context, attrs);\n"
|
||||
" } else if (factory != null) {\n"
|
||||
" return (T)factory.onCreateView(name, context, attrs);\n"
|
||||
" }\n"
|
||||
// TODO: find a way to call the private factory
|
||||
" return null;\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
" public static View inflate(Context context) {\n"
|
||||
" try {\n"
|
||||
" LayoutInflater inflater = LayoutInflater.from(context);\n"
|
||||
" LayoutInflater.Factory factory = inflater.getFactory();\n"
|
||||
" LayoutInflater.Factory2 factory2 = inflater.getFactory2();\n"
|
||||
" Resources res = context.getResources();\n"
|
||||
<< StringPrintf(" XmlResourceParser xml = res.getLayout(%s.R.layout.%s);\n",
|
||||
package_.c_str(),
|
||||
layout_name_.c_str())
|
||||
<< " AttributeSet attrs = Xml.asAttributeSet(xml);\n"
|
||||
// The Java-language XmlPullParser needs a call to next to find the start document tag.
|
||||
" xml.next(); // start document\n";
|
||||
}
|
||||
|
||||
void JavaLangViewBuilder::Finish() const {
|
||||
out_ << " } catch (Exception e) {\n"
|
||||
" return null;\n"
|
||||
" }\n" // end try
|
||||
" }\n" // end inflate
|
||||
"}\n"; // end CompiledView
|
||||
}
|
||||
|
||||
void JavaLangViewBuilder::StartView(const string& class_name) {
|
||||
const string view_var = MakeVar("view");
|
||||
const string layout_var = MakeVar("layout");
|
||||
std::string parent = "null";
|
||||
if (!view_stack_.empty()) {
|
||||
const StackEntry& parent_entry = view_stack_.back();
|
||||
parent = parent_entry.view_var;
|
||||
}
|
||||
out_ << " xml.next(); // <" << class_name << ">\n"
|
||||
<< StringPrintf(" %s %s = createView(context, attrs, %s, \"%s\", factory, factory2);\n",
|
||||
class_name.c_str(),
|
||||
view_var.c_str(),
|
||||
parent.c_str(),
|
||||
class_name.c_str())
|
||||
<< StringPrintf(" if (%s == null) %s = new %s(context, attrs);\n",
|
||||
view_var.c_str(),
|
||||
view_var.c_str(),
|
||||
class_name.c_str());
|
||||
if (!view_stack_.empty()) {
|
||||
out_ << StringPrintf(" ViewGroup.LayoutParams %s = %s.generateLayoutParams(attrs);\n",
|
||||
layout_var.c_str(),
|
||||
parent.c_str());
|
||||
}
|
||||
view_stack_.push_back({class_name, view_var, layout_var});
|
||||
}
|
||||
|
||||
void JavaLangViewBuilder::FinishView() {
|
||||
const StackEntry var = view_stack_.back();
|
||||
view_stack_.pop_back();
|
||||
if (!view_stack_.empty()) {
|
||||
const string& parent = view_stack_.back().view_var;
|
||||
out_ << StringPrintf(" xml.next(); // </%s>\n", var.class_name.c_str())
|
||||
<< StringPrintf(" %s.addView(%s, %s);\n",
|
||||
parent.c_str(),
|
||||
var.view_var.c_str(),
|
||||
var.layout_params_var.c_str());
|
||||
} else {
|
||||
out_ << StringPrintf(" return %s;\n", var.view_var.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
const std::string JavaLangViewBuilder::MakeVar(std::string prefix) {
|
||||
std::stringstream v;
|
||||
v << prefix << view_id_++;
|
||||
return v.str();
|
||||
}
|
||||
65
startop/tools/view_compiler/java_lang_builder.h
Normal file
65
startop/tools/view_compiler/java_lang_builder.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 JAVA_LANG_BUILDER_H_
|
||||
#define JAVA_LANG_BUILDER_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
// Build Java language code to instantiate views.
|
||||
//
|
||||
// This has a very small interface to make it easier to generate additional
|
||||
// backends, such as a direct-to-DEX version.
|
||||
class JavaLangViewBuilder {
|
||||
public:
|
||||
JavaLangViewBuilder(std::string package, std::string layout_name, std::ostream& out = std::cout)
|
||||
: package_(package), layout_name_(layout_name), out_(out) {}
|
||||
|
||||
// Begin generating a class. Adds the package boilerplate, etc.
|
||||
void Start() const;
|
||||
// Finish generating a class, closing off any open curly braces, etc.
|
||||
void Finish() const;
|
||||
|
||||
// Begin creating a view (i.e. process the opening tag)
|
||||
void StartView(const std::string& class_name);
|
||||
// Finish a view, after all of its child nodes have been processed.
|
||||
void FinishView();
|
||||
|
||||
private:
|
||||
const std::string MakeVar(std::string prefix);
|
||||
|
||||
std::string const package_;
|
||||
std::string const layout_name_;
|
||||
|
||||
std::ostream& out_;
|
||||
|
||||
size_t view_id_ = 0;
|
||||
|
||||
struct StackEntry {
|
||||
// The class name for this view object
|
||||
const std::string class_name;
|
||||
|
||||
// The variable name that is holding the view object
|
||||
const std::string view_var;
|
||||
|
||||
// The variable name that holds the object's layout parameters
|
||||
const std::string layout_params_var;
|
||||
};
|
||||
std::vector<StackEntry> view_stack_;
|
||||
};
|
||||
|
||||
#endif // JAVA_LANG_BUILDER_H_
|
||||
105
startop/tools/view_compiler/main.cc
Normal file
105
startop/tools/view_compiler/main.cc
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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 "gflags/gflags.h"
|
||||
|
||||
#include "java_lang_builder.h"
|
||||
#include "util.h"
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace tinyxml2;
|
||||
using std::string;
|
||||
|
||||
constexpr char kStdoutFilename[]{"stdout"};
|
||||
|
||||
DEFINE_string(package, "", "The package name for the generated class (required)");
|
||||
DEFINE_string(out, kStdoutFilename, "Where to write the generated class");
|
||||
|
||||
namespace {
|
||||
class ViewCompilerXmlVisitor : public XMLVisitor {
|
||||
public:
|
||||
ViewCompilerXmlVisitor(JavaLangViewBuilder* builder) : builder_(builder) {}
|
||||
|
||||
bool VisitEnter(const XMLDocument& /*doc*/) override {
|
||||
builder_->Start();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VisitExit(const XMLDocument& /*doc*/) override {
|
||||
builder_->Finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VisitEnter(const XMLElement& element, const XMLAttribute* /*firstAttribute*/) override {
|
||||
builder_->StartView(element.Name());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VisitExit(const XMLElement& /*element*/) override {
|
||||
builder_->FinishView();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
JavaLangViewBuilder* builder_;
|
||||
};
|
||||
} // end namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
constexpr size_t kProgramName = 0;
|
||||
constexpr size_t kFileNameParam = 1;
|
||||
constexpr size_t kNumRequiredArgs = 2;
|
||||
|
||||
gflags::SetUsageMessage(
|
||||
"Compile XML layout files into equivalent Java language code\n"
|
||||
"\n"
|
||||
" example usage: viewcompiler layout.xml --package com.example.androidapp");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, /*remove_flags*/ true);
|
||||
|
||||
gflags::CommandLineFlagInfo cmd = gflags::GetCommandLineFlagInfoOrDie("package");
|
||||
if (argc != kNumRequiredArgs || cmd.is_default) {
|
||||
gflags::ShowUsageWithFlags(argv[kProgramName]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* const filename = argv[kFileNameParam];
|
||||
const string layout_name = FindLayoutNameFromFilename(filename);
|
||||
|
||||
// We want to generate Java language code to inflate exactly this layout. This means
|
||||
// generating code to walk the resource XML too.
|
||||
|
||||
XMLDocument xml;
|
||||
xml.LoadFile(filename);
|
||||
|
||||
std::ofstream outfile;
|
||||
if (FLAGS_out != kStdoutFilename) {
|
||||
outfile.open(FLAGS_out);
|
||||
}
|
||||
JavaLangViewBuilder builder{
|
||||
FLAGS_package, layout_name, FLAGS_out == kStdoutFilename ? std::cout : outfile};
|
||||
|
||||
ViewCompilerXmlVisitor visitor{&builder};
|
||||
xml.Accept(&visitor);
|
||||
|
||||
return 0;
|
||||
}
|
||||
32
startop/tools/view_compiler/util.cc
Normal file
32
startop/tools/view_compiler/util.cc
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 "util.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
// TODO: see if we can borrow this from somewhere else, like aapt2.
|
||||
string FindLayoutNameFromFilename(const string& filename) {
|
||||
size_t start = filename.rfind("/");
|
||||
if (start == string::npos) {
|
||||
start = 0;
|
||||
} else {
|
||||
start++; // advance past '/' character
|
||||
}
|
||||
size_t end = filename.find(".", start);
|
||||
|
||||
return filename.substr(start, end - start);
|
||||
}
|
||||
23
startop/tools/view_compiler/util.h
Normal file
23
startop/tools/view_compiler/util.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 UTIL_H_
|
||||
#define UTIL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
std::string FindLayoutNameFromFilename(const std::string& filename);
|
||||
|
||||
#endif // UTIL_H_
|
||||
28
startop/tools/view_compiler/util_test.cc
Normal file
28
startop/tools/view_compiler/util_test.cc
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 "util.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
TEST(UtilTest, FindLayoutNameFromFilename) {
|
||||
EXPECT_EQ("bar", ::FindLayoutNameFromFilename("foo/bar.xml"));
|
||||
EXPECT_EQ("bar", ::FindLayoutNameFromFilename("bar.xml"));
|
||||
EXPECT_EQ("bar", ::FindLayoutNameFromFilename("./foo/bar.xml"));
|
||||
EXPECT_EQ("bar", ::FindLayoutNameFromFilename("/foo/bar.xml"));
|
||||
}
|
||||
Reference in New Issue
Block a user