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