Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b377e517 authored by Eric Holk's avatar Eric Holk
Browse files

[view compiler] Compile all layouts in an APK

Test: atest
Bug: 111895153
Change-Id: I5f4b9b4c1160acf1c04a4492f5e3a736fbaf2fdc
parent d426ee84
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -22,17 +22,35 @@ cc_defaults {
    shared_libs: [
        "libbase",
        "libdexfile",
        "libz",
        "slicer",
    ],
    static_libs: [
        "libtinyxml2",
        "liblog",
        "libutils",
        "libziparchive",
    ],
    cppflags: ["-std=c++17"],
    target: {
        android: {
            shared_libs: [
                "libandroidfw",
            ],
        },
        host: {
            static_libs: [
                "libandroidfw",
            ],
        },
    },
}

cc_library_host_static {
    name: "libviewcompiler",
    defaults: ["viewcompiler_defaults"],
    srcs: [
        "apk_layout_compiler.cc",
        "dex_builder.cc",
        "dex_layout_compiler.cc",
        "java_lang_builder.cc",
+159 −0
Original line number Diff line number Diff line
/*
 * 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 "apk_layout_compiler.h"
#include "dex_layout_compiler.h"
#include "java_lang_builder.h"
#include "layout_validation.h"
#include "util.h"

#include "androidfw/ApkAssets.h"
#include "androidfw/AssetManager2.h"
#include "androidfw/ResourceTypes.h"

#include <iostream>
#include <locale>

#include "android-base/stringprintf.h"

namespace startop {

using android::ResXMLParser;
using android::base::StringPrintf;

class ResXmlVisitorAdapter {
 public:
  ResXmlVisitorAdapter(ResXMLParser* parser) : parser_{parser} {}

  template <typename Visitor>
  void Accept(Visitor* visitor) {
    size_t depth{0};
    do {
      switch (parser_->next()) {
        case ResXMLParser::START_DOCUMENT:
          depth++;
          visitor->VisitStartDocument();
          break;
        case ResXMLParser::END_DOCUMENT:
          depth--;
          visitor->VisitEndDocument();
          break;
        case ResXMLParser::START_TAG: {
          depth++;
          size_t name_length = 0;
          const char16_t* name = parser_->getElementName(&name_length);
          visitor->VisitStartTag(std::u16string{name, name_length});
          break;
        }
        case ResXMLParser::END_TAG:
          depth--;
          visitor->VisitEndTag();
          break;
        default:;
      }
    } while (depth > 0 || parser_->getEventType() == ResXMLParser::FIRST_CHUNK_CODE);
  }

 private:
  ResXMLParser* parser_;
};

bool CanCompileLayout(ResXMLParser* parser) {
  ResXmlVisitorAdapter adapter{parser};
  LayoutValidationVisitor visitor;
  adapter.Accept(&visitor);

  return visitor.can_compile();
}

void CompileApkLayouts(const std::string& filename, CompilationTarget target,
                       std::ostream& target_out) {
  auto assets = android::ApkAssets::Load(filename);
  android::AssetManager2 resources;
  resources.SetApkAssets({assets.get()});

  std::string package_name;

  // TODO: handle multiple packages better
  bool first = true;
  for (const auto& package : assets->GetLoadedArsc()->GetPackages()) {
    CHECK(first);
    package_name = package->GetPackageName();
    first = false;
  }

  dex::DexBuilder dex_file;
  dex::ClassBuilder compiled_view{
      dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))};
  std::vector<dex::MethodBuilder> methods;

  assets->ForEachFile("res/", [&](const android::StringPiece& s, android::FileType) {
    if (s == "layout") {
      auto path = StringPrintf("res/%s/", s.to_string().c_str());
      assets->ForEachFile(path, [&](const android::StringPiece& layout_file, android::FileType) {
        auto layout_path = StringPrintf("%s%s", path.c_str(), layout_file.to_string().c_str());
        android::ApkAssetsCookie cookie = android::kInvalidCookie;
        auto asset = resources.OpenNonAsset(layout_path, android::Asset::ACCESS_RANDOM, &cookie);
        CHECK(asset);
        CHECK(android::kInvalidCookie != cookie);
        const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
        CHECK(nullptr != dynamic_ref_table);
        android::ResXMLTree xml_tree{dynamic_ref_table};
        xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true),
                       asset->getLength(),
                       /*copy_data=*/true);
        android::ResXMLParser parser{xml_tree};
        parser.restart();
        if (CanCompileLayout(&parser)) {
          parser.restart();
          const std::string layout_name = startop::util::FindLayoutNameFromFilename(layout_path);
          ResXmlVisitorAdapter adapter{&parser};
          switch (target) {
            case CompilationTarget::kDex: {
              methods.push_back(compiled_view.CreateMethod(
                  layout_name,
                  dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.View"),
                                 dex::TypeDescriptor::FromClassname("android.content.Context"),
                                 dex::TypeDescriptor::Int()}));
              DexViewBuilder builder(&methods.back());
              builder.Start();
              LayoutCompilerVisitor visitor{&builder};
              adapter.Accept(&visitor);
              builder.Finish();
              methods.back().Encode();
              break;
            }
            case CompilationTarget::kJavaLanguage: {
              JavaLangViewBuilder builder{package_name, layout_name, target_out};
              builder.Start();
              LayoutCompilerVisitor visitor{&builder};
              adapter.Accept(&visitor);
              builder.Finish();
              break;
            }
          }
        }
      });
    }
  });

  if (target == CompilationTarget::kDex) {
    slicer::MemView image{dex_file.CreateImage()};
    target_out.write(image.ptr<const char>(), image.size());
  }
}

}  // namespace startop
+31 −0
Original line number Diff line number Diff line
/*
 * 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 APK_LAYOUT_COMPILER_H_
#define APK_LAYOUT_COMPILER_H_

#include <string>

namespace startop {

enum class CompilationTarget { kJavaLanguage, kDex };

void CompileApkLayouts(const std::string& filename, CompilationTarget target,
                       std::ostream& target_out);

}  // namespace startop

#endif  // APK_LAYOUT_COMPILER_H_
 No newline at end of file
+17 −7
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
#include "gflags/gflags.h"

#include "android-base/stringprintf.h"
#include "apk_layout_compiler.h"
#include "dex_builder.h"
#include "dex_layout_compiler.h"
#include "java_lang_builder.h"
@@ -46,6 +47,7 @@ using std::string;

constexpr char kStdoutFilename[]{"stdout"};

DEFINE_bool(apk, false, "Compile layouts in an APK");
DEFINE_bool(dex, false, "Generate a DEX file instead of Java");
DEFINE_string(out, kStdoutFilename, "Where to write the generated class");
DEFINE_string(package, "", "The package name for the generated class (required)");
@@ -108,6 +110,21 @@ int main(int argc, char** argv) {
  }

  const char* const filename = argv[kFileNameParam];
  const bool is_stdout = FLAGS_out == kStdoutFilename;

  std::ofstream outfile;
  if (!is_stdout) {
    outfile.open(FLAGS_out);
  }

  if (FLAGS_apk) {
    startop::CompileApkLayouts(
        filename,
        FLAGS_dex ? startop::CompilationTarget::kDex : startop::CompilationTarget::kJavaLanguage,
        is_stdout ? std::cout : outfile);
    return 0;
  }

  const string layout_name = startop::util::FindLayoutNameFromFilename(filename);

  XMLDocument xml;
@@ -119,13 +136,6 @@ int main(int argc, char** argv) {
    return 1;
  }

  const bool is_stdout = FLAGS_out == kStdoutFilename;

  std::ofstream outfile;
  if (!is_stdout) {
    outfile.open(FLAGS_out);
  }

  if (FLAGS_dex) {
    DexBuilder dex_file;
    string class_name = StringPrintf("%s.CompiledView", FLAGS_package.c_str());