Merge "[view-compiler] Add layout validation"
This commit is contained in:
@@ -24,6 +24,9 @@ cc_defaults {
|
|||||||
"libdexfile",
|
"libdexfile",
|
||||||
"slicer",
|
"slicer",
|
||||||
],
|
],
|
||||||
|
static_libs: [
|
||||||
|
"libtinyxml2",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
cc_library_host_static {
|
cc_library_host_static {
|
||||||
@@ -32,7 +35,9 @@ cc_library_host_static {
|
|||||||
srcs: [
|
srcs: [
|
||||||
"dex_builder.cc",
|
"dex_builder.cc",
|
||||||
"java_lang_builder.cc",
|
"java_lang_builder.cc",
|
||||||
|
"tinyxml_layout_parser.cc",
|
||||||
"util.cc",
|
"util.cc",
|
||||||
|
"layout_validation.cc",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +48,6 @@ cc_binary_host {
|
|||||||
"main.cc",
|
"main.cc",
|
||||||
],
|
],
|
||||||
static_libs: [
|
static_libs: [
|
||||||
"libtinyxml2",
|
|
||||||
"libgflags",
|
"libgflags",
|
||||||
"libviewcompiler",
|
"libviewcompiler",
|
||||||
],
|
],
|
||||||
@@ -54,6 +58,7 @@ cc_test_host {
|
|||||||
defaults: ["viewcompiler_defaults"],
|
defaults: ["viewcompiler_defaults"],
|
||||||
srcs: [
|
srcs: [
|
||||||
"dex_builder_test.cc",
|
"dex_builder_test.cc",
|
||||||
|
"layout_validation_test.cc",
|
||||||
"util_test.cc",
|
"util_test.cc",
|
||||||
],
|
],
|
||||||
static_libs: [
|
static_libs: [
|
||||||
|
|||||||
42
startop/view_compiler/layout_validation.cc
Normal file
42
startop/view_compiler/layout_validation.cc
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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 "layout_validation.h"
|
||||||
|
|
||||||
|
#include "android-base/stringprintf.h"
|
||||||
|
|
||||||
|
namespace startop {
|
||||||
|
|
||||||
|
void LayoutValidationVisitor::VisitStartTag(const std::u16string& name) {
|
||||||
|
if (0 == name.compare(u"merge")) {
|
||||||
|
message_ = "Merge tags are not supported";
|
||||||
|
can_compile_ = false;
|
||||||
|
}
|
||||||
|
if (0 == name.compare(u"include")) {
|
||||||
|
message_ = "Include tags are not supported";
|
||||||
|
can_compile_ = false;
|
||||||
|
}
|
||||||
|
if (0 == name.compare(u"view")) {
|
||||||
|
message_ = "View tags are not supported";
|
||||||
|
can_compile_ = false;
|
||||||
|
}
|
||||||
|
if (0 == name.compare(u"fragment")) {
|
||||||
|
message_ = "Fragment tags are not supported";
|
||||||
|
can_compile_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace startop
|
||||||
46
startop/view_compiler/layout_validation.h
Normal file
46
startop/view_compiler/layout_validation.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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 LAYOUT_VALIDATION_H_
|
||||||
|
#define LAYOUT_VALIDATION_H_
|
||||||
|
|
||||||
|
#include "dex_builder.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace startop {
|
||||||
|
|
||||||
|
// This visitor determines whether a layout can be compiled. Since we do not currently support all
|
||||||
|
// features, such as includes and merges, we need to pre-validate the layout before we start
|
||||||
|
// compiling.
|
||||||
|
class LayoutValidationVisitor {
|
||||||
|
public:
|
||||||
|
void VisitStartDocument() const {}
|
||||||
|
void VisitEndDocument() const {}
|
||||||
|
void VisitStartTag(const std::u16string& name);
|
||||||
|
void VisitEndTag() const {}
|
||||||
|
|
||||||
|
const std::string& message() const { return message_; }
|
||||||
|
bool can_compile() const { return can_compile_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string message_{"Okay"};
|
||||||
|
bool can_compile_{true};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace startop
|
||||||
|
|
||||||
|
#endif // LAYOUT_VALIDATION_H_
|
||||||
163
startop/view_compiler/layout_validation_test.cc
Normal file
163
startop/view_compiler/layout_validation_test.cc
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
* 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 "tinyxml_layout_parser.h"
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
using startop::CanCompileLayout;
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void ValidateXmlText(const string& xml, bool expected) {
|
||||||
|
tinyxml2::XMLDocument doc;
|
||||||
|
doc.Parse(xml.c_str());
|
||||||
|
EXPECT_EQ(CanCompileLayout(doc), expected);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(LayoutValidationTest, SingleButtonLayout) {
|
||||||
|
const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Button xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:text="Hello, World!">
|
||||||
|
|
||||||
|
</Button>)";
|
||||||
|
ValidateXmlText(xml, /*expected=*/true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LayoutValidationTest, SmallConstraintLayout) {
|
||||||
|
const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button6"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:text="Button"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button7"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:text="Button2"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/button6" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button8"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:text="Button1"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/button7" />
|
||||||
|
</android.support.constraint.ConstraintLayout>)";
|
||||||
|
ValidateXmlText(xml, /*expected=*/true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LayoutValidationTest, MergeNode) {
|
||||||
|
const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView3"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="TextView" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button9"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Button" />
|
||||||
|
</merge>)";
|
||||||
|
ValidateXmlText(xml, /*expected=*/false);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LayoutValidationTest, IncludeLayout) {
|
||||||
|
const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<include
|
||||||
|
layout="@layout/single_button_layout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</android.support.constraint.ConstraintLayout>)";
|
||||||
|
ValidateXmlText(xml, /*expected=*/false);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LayoutValidationTest, ViewNode) {
|
||||||
|
const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<view
|
||||||
|
class="android.support.design.button.MaterialButton"
|
||||||
|
id="@+id/view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</android.support.constraint.ConstraintLayout>)";
|
||||||
|
ValidateXmlText(xml, /*expected=*/false);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LayoutValidationTest, FragmentNode) {
|
||||||
|
// This test case is from https://developer.android.com/guide/components/fragments
|
||||||
|
const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<fragment android:name="com.example.news.ArticleListFragment"
|
||||||
|
android:id="@+id/list"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
<fragment android:name="com.example.news.ArticleReaderFragment"
|
||||||
|
android:id="@+id/viewer"
|
||||||
|
android:layout_weight="2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</LinearLayout>)";
|
||||||
|
ValidateXmlText(xml, /*expected=*/false);
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include "dex_builder.h"
|
#include "dex_builder.h"
|
||||||
#include "java_lang_builder.h"
|
#include "java_lang_builder.h"
|
||||||
|
#include "tinyxml_layout_parser.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
#include "tinyxml2.h"
|
#include "tinyxml2.h"
|
||||||
@@ -100,6 +101,12 @@ int main(int argc, char** argv) {
|
|||||||
XMLDocument xml;
|
XMLDocument xml;
|
||||||
xml.LoadFile(filename);
|
xml.LoadFile(filename);
|
||||||
|
|
||||||
|
string message{};
|
||||||
|
if (!startop::CanCompileLayout(xml, &message)) {
|
||||||
|
LOG(ERROR) << "Layout not supported: " << message;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
std::ofstream outfile;
|
std::ofstream outfile;
|
||||||
if (FLAGS_out != kStdoutFilename) {
|
if (FLAGS_out != kStdoutFilename) {
|
||||||
outfile.open(FLAGS_out);
|
outfile.open(FLAGS_out);
|
||||||
|
|||||||
34
startop/view_compiler/tinyxml_layout_parser.cc
Normal file
34
startop/view_compiler/tinyxml_layout_parser.cc
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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 "tinyxml_layout_parser.h"
|
||||||
|
|
||||||
|
#include "layout_validation.h"
|
||||||
|
|
||||||
|
namespace startop {
|
||||||
|
|
||||||
|
bool CanCompileLayout(const tinyxml2::XMLDocument& xml, std::string* message) {
|
||||||
|
LayoutValidationVisitor validator;
|
||||||
|
TinyXmlVisitorAdapter adapter{&validator};
|
||||||
|
xml.Accept(&adapter);
|
||||||
|
|
||||||
|
if (message != nullptr) {
|
||||||
|
*message = validator.message();
|
||||||
|
}
|
||||||
|
|
||||||
|
return validator.can_compile();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace startop
|
||||||
65
startop/view_compiler/tinyxml_layout_parser.h
Normal file
65
startop/view_compiler/tinyxml_layout_parser.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 TINYXML_LAYOUT_PARSER_H_
|
||||||
|
#define TINYXML_LAYOUT_PARSER_H_
|
||||||
|
|
||||||
|
#include "tinyxml2.h"
|
||||||
|
|
||||||
|
#include <codecvt>
|
||||||
|
#include <locale>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace startop {
|
||||||
|
|
||||||
|
template <typename Visitor>
|
||||||
|
class TinyXmlVisitorAdapter : public tinyxml2::XMLVisitor {
|
||||||
|
public:
|
||||||
|
explicit TinyXmlVisitorAdapter(Visitor* visitor) : visitor_{visitor} {}
|
||||||
|
|
||||||
|
bool VisitEnter(const tinyxml2::XMLDocument& /*doc*/) override {
|
||||||
|
visitor_->VisitStartDocument();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VisitExit(const tinyxml2::XMLDocument& /*doc*/) override {
|
||||||
|
visitor_->VisitEndDocument();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VisitEnter(const tinyxml2::XMLElement& element,
|
||||||
|
const tinyxml2::XMLAttribute* /*firstAttribute*/) override {
|
||||||
|
visitor_->VisitStartTag(
|
||||||
|
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(
|
||||||
|
element.Name()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VisitExit(const tinyxml2::XMLElement& /*element*/) override {
|
||||||
|
visitor_->VisitEndTag();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Visitor* visitor_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns whether a layout resource represented by a TinyXML document is supported by the layout
|
||||||
|
// compiler.
|
||||||
|
bool CanCompileLayout(const tinyxml2::XMLDocument& xml, std::string* message = nullptr);
|
||||||
|
|
||||||
|
} // namespace startop
|
||||||
|
|
||||||
|
#endif // TINYXML_LAYOUT_PARSER_H_
|
||||||
Reference in New Issue
Block a user