Merge "[view-compiler] Add layout validation"

am: ceea877ddd

Change-Id: Ia95ec8ccc77c4be60f018d99391067f3b8b02fd2
This commit is contained in:
Eric Holk
2018-12-17 13:38:46 -08:00
committed by android-build-merger
7 changed files with 363 additions and 1 deletions

View File

@@ -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: [

View 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

View 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_

View 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);
}

View File

@@ -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);

View 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

View 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_