Merge "[view-compiler] Add layout validation"
am: ceea877ddd
Change-Id: Ia95ec8ccc77c4be60f018d99391067f3b8b02fd2
This commit is contained in:
@@ -24,6 +24,9 @@ cc_defaults {
|
||||
"libdexfile",
|
||||
"slicer",
|
||||
],
|
||||
static_libs: [
|
||||
"libtinyxml2",
|
||||
],
|
||||
}
|
||||
|
||||
cc_library_host_static {
|
||||
@@ -32,7 +35,9 @@ cc_library_host_static {
|
||||
srcs: [
|
||||
"dex_builder.cc",
|
||||
"java_lang_builder.cc",
|
||||
"tinyxml_layout_parser.cc",
|
||||
"util.cc",
|
||||
"layout_validation.cc",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -43,7 +48,6 @@ cc_binary_host {
|
||||
"main.cc",
|
||||
],
|
||||
static_libs: [
|
||||
"libtinyxml2",
|
||||
"libgflags",
|
||||
"libviewcompiler",
|
||||
],
|
||||
@@ -54,6 +58,7 @@ cc_test_host {
|
||||
defaults: ["viewcompiler_defaults"],
|
||||
srcs: [
|
||||
"dex_builder_test.cc",
|
||||
"layout_validation_test.cc",
|
||||
"util_test.cc",
|
||||
],
|
||||
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 "java_lang_builder.h"
|
||||
#include "tinyxml_layout_parser.h"
|
||||
#include "util.h"
|
||||
|
||||
#include "tinyxml2.h"
|
||||
@@ -100,6 +101,12 @@ int main(int argc, char** argv) {
|
||||
XMLDocument xml;
|
||||
xml.LoadFile(filename);
|
||||
|
||||
string message{};
|
||||
if (!startop::CanCompileLayout(xml, &message)) {
|
||||
LOG(ERROR) << "Layout not supported: " << message;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::ofstream outfile;
|
||||
if (FLAGS_out != kStdoutFilename) {
|
||||
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