diff options
Diffstat (limited to 'tools')
236 files changed, 12829 insertions, 5659 deletions
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index a93ee2e2b71d..deb9cc083eb0 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -68,6 +68,7 @@ public: mSingleCrunchInputFile(NULL), mSingleCrunchOutputFile(NULL), mBuildSharedLibrary(false), mBuildAppAsSharedLibrary(false), + mCompileSdkVersion(0), mArgc(0), mArgv(NULL) {} ~Bundle(void) {} @@ -123,6 +124,10 @@ public: void setErrorOnFailedInsert(bool val) { mErrorOnFailedInsert = val; } bool getErrorOnMissingConfigEntry() { return mErrorOnMissingConfigEntry; } void setErrorOnMissingConfigEntry(bool val) { mErrorOnMissingConfigEntry = val; } + const android::String8& getCompileSdkVersionCodename() { return mCompileSdkVersionCodename; } + void setCompileSdkVersionCodename(const android::String8& codename) { mCompileSdkVersionCodename = codename; } + int getCompileSdkVersion() { return mCompileSdkVersion; } + void setCompileSdkVersion(int version) { mCompileSdkVersion = version; } const android::String8& getPlatformBuildVersionCode() { return mPlatformVersionCode; } void setPlatformBuildVersionCode(const android::String8& code) { mPlatformVersionCode = code; } const android::String8& getPlatformBuildVersionName() { return mPlatformVersionName; } @@ -344,6 +349,8 @@ private: const char* mSingleCrunchOutputFile; bool mBuildSharedLibrary; bool mBuildAppAsSharedLibrary; + int mCompileSdkVersion; + android::String8 mCompileSdkVersionCodename; android::String8 mPlatformVersionCode; android::String8 mPlatformVersionName; android::String8 mPrivateSymbolsPackage; diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index cb87737c6868..05375b0cb871 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -293,6 +293,8 @@ enum { ISGAME_ATTR = 0x10103f4, REQUIRED_FEATURE_ATTR = 0x1010557, REQUIRED_NOT_FEATURE_ATTR = 0x1010558, + COMPILE_SDK_VERSION_ATTR = 0x01010572, // NOT FINALIZED + COMPILE_SDK_VERSION_CODENAME_ATTR = 0x01010573, // NOT FINALIZED }; String8 getComponentName(String8 &pkgName, String8 &componentName) { @@ -1247,9 +1249,37 @@ int doDump(Bundle* bundle) splitName.string()).string()); } - String8 platformVersionName = AaptXml::getAttribute(tree, NULL, + String8 platformBuildVersionName = AaptXml::getAttribute(tree, NULL, "platformBuildVersionName"); - printf(" platformBuildVersionName='%s'", platformVersionName.string()); + if (platformBuildVersionName != "") { + printf(" platformBuildVersionName='%s'", platformBuildVersionName.string()); + } + + String8 platformBuildVersionCode = AaptXml::getAttribute(tree, NULL, + "platformBuildVersionCode"); + if (platformBuildVersionCode != "") { + printf(" platformBuildVersionCode='%s'", platformBuildVersionCode.string()); + } + + int32_t compileSdkVersion = AaptXml::getIntegerAttribute(tree, + COMPILE_SDK_VERSION_ATTR, &error); + if (error != "") { + SourcePos(manifestFile, tree.getLineNumber()).error( + "ERROR getting 'android:compileSdkVersion' attribute: %s", + error.string()); + goto bail; + } + if (compileSdkVersion > 0) { + printf(" compileSdkVersion='%d'", compileSdkVersion); + } + + String8 compileSdkVersionCodename = AaptXml::getResolvedAttribute(res, tree, + COMPILE_SDK_VERSION_CODENAME_ATTR, &error); + if (compileSdkVersionCodename != "") { + printf(" compileSdkVersionCodename='%s'", ResTable::normalizeForOutput( + compileSdkVersionCodename.string()).string()); + } + printf("\n"); int32_t installLocation = AaptXml::getResolvedIntegerAttribute(res, tree, diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index bd2b2a36788f..ab6dced5b67d 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -918,6 +918,22 @@ status_t massageManifest(Bundle* bundle, ResourceTable* table, sp<XMLNode> root) } } + + if (bundle->getCompileSdkVersion() != 0) { + if (!addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "compileSdkVersion", + String8::format("%d", bundle->getCompileSdkVersion()), + errorOnFailedInsert, true)) { + return UNKNOWN_ERROR; + } + } + + if (bundle->getCompileSdkVersionCodename() != "") { + if (!addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "compileSdkVersionCodename", + bundle->getCompileSdkVersionCodename(), errorOnFailedInsert, true)) { + return UNKNOWN_ERROR; + } + } + if (bundle->getPlatformBuildVersionCode() != "") { if (!addTagAttribute(root, "", "platformBuildVersionCode", bundle->getPlatformBuildVersionCode(), errorOnFailedInsert, true)) { @@ -1052,7 +1068,12 @@ enum { VERSION_NAME_ATTR = 0x0101021c, }; -static ssize_t extractPlatformBuildVersion(ResXMLTree& tree, Bundle* bundle) { +static ssize_t extractPlatformBuildVersion(const ResTable& table, ResXMLTree& tree, Bundle* bundle) { + // First check if we should be recording the compileSdkVersion* attributes. + static const String16 compileSdkVersionName("android:attr/compileSdkVersion"); + const bool useCompileSdkVersion = table.identifierForName(compileSdkVersionName.string(), + compileSdkVersionName.size()) != 0u; + size_t len; ResXMLTree::event_code_t code; while ((code = tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { @@ -1082,6 +1103,10 @@ static ssize_t extractPlatformBuildVersion(ResXMLTree& tree, Bundle* bundle) { bundle->setPlatformBuildVersionCode(String8::format("%d", versionCode)); } + if (useCompileSdkVersion && versionCode >= 0 && bundle->getCompileSdkVersion() == 0) { + bundle->setCompileSdkVersion(versionCode); + } + String8 versionName = AaptXml::getAttribute(tree, VERSION_NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR: failed to get platform version name\n"); @@ -1091,6 +1116,11 @@ static ssize_t extractPlatformBuildVersion(ResXMLTree& tree, Bundle* bundle) { if (versionName != "" && bundle->getPlatformBuildVersionName() == "") { bundle->setPlatformBuildVersionName(versionName); } + + if (useCompileSdkVersion && versionName != "" + && bundle->getCompileSdkVersionCodename() == "") { + bundle->setCompileSdkVersionCodename(versionName); + } return NO_ERROR; } @@ -1121,7 +1151,7 @@ static ssize_t extractPlatformBuildVersion(AssetManager& assets, Bundle* bundle) fprintf(stderr, "ERROR: Platform AndroidManifest.xml is corrupt\n"); result = UNKNOWN_ERROR; } else { - result = extractPlatformBuildVersion(tree, bundle); + result = extractPlatformBuildVersion(assets.getResources(true), tree, bundle); } } @@ -1707,7 +1737,9 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil // extract them from the platform APK. if (packageType != ResourceTable::System && (bundle->getPlatformBuildVersionCode() == "" || - bundle->getPlatformBuildVersionName() == "")) { + bundle->getPlatformBuildVersionName() == "" || + bundle->getCompileSdkVersion() == 0 || + bundle->getCompileSdkVersionCodename() == "")) { err = extractPlatformBuildVersion(assets->getAssetManager(), bundle); if (err != NO_ERROR) { return UNKNOWN_ERROR; diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h index bf56ec024699..b982d0d2ea63 100644 --- a/tools/aapt/SdkConstants.h +++ b/tools/aapt/SdkConstants.h @@ -43,6 +43,7 @@ enum { SDK_NOUGAT_MR1 = 25, SDK_O = 26, SDK_O_MR1 = 27, + SDK_P = 10000, // STOPSHIP Replace with the real version. }; #endif // H_AAPT_SDK_CONSTANTS diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index ade638d2c422..eb3a99ae6331 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -16,6 +16,7 @@ toolSources = [ "cmd/Compile.cpp", + "cmd/Convert.cpp", "cmd/Diff.cpp", "cmd/Dump.cpp", "cmd/Link.cpp", @@ -83,14 +84,19 @@ cc_library_host_static { "configuration/ConfigurationParser.cpp", "filter/AbiFilter.cpp", "filter/ConfigFilter.cpp", - "flatten/Archive.cpp", - "flatten/TableFlattener.cpp", - "flatten/XmlFlattener.cpp", - "io/BigBufferStreams.cpp", + "format/Archive.cpp", + "format/Container.cpp", + "format/binary/BinaryResourceParser.cpp", + "format/binary/ResChunkPullParser.cpp", + "format/binary/TableFlattener.cpp", + "format/binary/XmlFlattener.cpp", + "format/proto/ProtoDeserialize.cpp", + "format/proto/ProtoSerialize.cpp", + "io/BigBufferStream.cpp", "io/File.cpp", - "io/FileInputStream.cpp", + "io/FileStream.cpp", "io/FileSystem.cpp", - "io/StringInputStream.cpp", + "io/StringStream.cpp", "io/Util.cpp", "io/ZipArchive.cpp", "link/AutoVersioner.cpp", @@ -102,17 +108,14 @@ cc_library_host_static { "link/XmlCompatVersioner.cpp", "link/XmlNamespaceRemover.cpp", "link/XmlReferenceLinker.cpp", + "optimize/MultiApkGenerator.cpp", "optimize/ResourceDeduper.cpp", "optimize/VersionCollapser.cpp", "process/SymbolTable.cpp", - "proto/ProtoHelpers.cpp", - "proto/TableProtoDeserializer.cpp", - "proto/TableProtoSerializer.cpp", "split/TableSplitter.cpp", + "text/Printer.cpp", "text/Unicode.cpp", "text/Utf8Iterator.cpp", - "unflatten/BinaryResourceParser.cpp", - "unflatten/ResChunkPullParser.cpp", "util/BigBuffer.cpp", "util/Files.cpp", "util/Util.cpp", @@ -138,6 +141,7 @@ cc_library_host_static { "xml/XmlDom.cpp", "xml/XmlPullParser.cpp", "xml/XmlUtil.cpp", + "Configuration.proto", "Resources.proto", "ResourcesInternal.proto", ], diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk new file mode 100644 index 000000000000..c9987b86cc5c --- /dev/null +++ b/tools/aapt2/Android.mk @@ -0,0 +1,11 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +# Target for running host unit tests on post/pre-submit. +.PHONY: aapt2_run_host_unit_tests +aapt2_run_host_unit_tests: PRIVATE_GTEST_OPTIONS := --gtest_output=xml:$(DIST_DIR)/gtest/aapt2_host_unit_tests_result.xml +aapt2_run_host_unit_tests: $(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests + -$(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests $(PRIVATE_GTEST_OPTIONS) > /dev/null 2>&1 + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp index a9278c136cff..f621660cb8ad 100644 --- a/tools/aapt2/ConfigDescription.cpp +++ b/tools/aapt2/ConfigDescription.cpp @@ -876,6 +876,17 @@ ConfigDescription ConfigDescription::CopyWithoutSdkVersion() const { return copy; } +std::string ConfigDescription::GetBcp47LanguageTag(bool canonicalize) const { + char locale[RESTABLE_MAX_LOCALE_LEN]; + getBcp47Locale(locale, canonicalize); + return std::string(locale); +} + +std::string ConfigDescription::to_string() const { + const android::String8 str = toString(); + return std::string(str.string(), str.size()); +} + bool ConfigDescription::Dominates(const ConfigDescription& o) const { if (*this == o) { return true; diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h index 65c96175091c..f71955247d78 100644 --- a/tools/aapt2/ConfigDescription.h +++ b/tools/aapt2/ConfigDescription.h @@ -61,6 +61,11 @@ struct ConfigDescription : public android::ResTable_config { ConfigDescription CopyWithoutSdkVersion() const; + // Returns the BCP-47 language tag of this configuration's locale. + std::string GetBcp47LanguageTag(bool canonicalize = false) const; + + std::string to_string() const; + /** * A configuration X dominates another configuration Y, if X has at least the * precedence of Y and X is strictly more general than Y: for any type defined diff --git a/tools/aapt2/Configuration.proto b/tools/aapt2/Configuration.proto new file mode 100644 index 000000000000..fc636a43ec40 --- /dev/null +++ b/tools/aapt2/Configuration.proto @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2017 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. + */ + +syntax = "proto3"; + +package aapt.pb; + +option java_package = "com.android.aapt"; +option optimize_for = LITE_RUNTIME; + +// A description of the requirements a device must have in order for a +// resource to be matched and selected. +message Configuration { + enum LayoutDirection { + LAYOUT_DIRECTION_UNSET = 0; + LAYOUT_DIRECTION_LTR = 1; + LAYOUT_DIRECTION_RTL = 2; + } + + enum ScreenLayoutSize { + SCREEN_LAYOUT_SIZE_UNSET = 0; + SCREEN_LAYOUT_SIZE_SMALL = 1; + SCREEN_LAYOUT_SIZE_NORMAL = 2; + SCREEN_LAYOUT_SIZE_LARGE = 3; + SCREEN_LAYOUT_SIZE_XLARGE = 4; + } + + enum ScreenLayoutLong { + SCREEN_LAYOUT_LONG_UNSET = 0; + SCREEN_LAYOUT_LONG_LONG = 1; + SCREEN_LAYOUT_LONG_NOTLONG = 2; + } + + enum ScreenRound { + SCREEN_ROUND_UNSET = 0; + SCREEN_ROUND_ROUND = 1; + SCREEN_ROUND_NOTROUND = 2; + } + + enum WideColorGamut { + WIDE_COLOR_GAMUT_UNSET = 0; + WIDE_COLOR_GAMUT_WIDECG = 1; + WIDE_COLOR_GAMUT_NOWIDECG = 2; + } + + enum Hdr { + HDR_UNSET = 0; + HDR_HIGHDR = 1; + HDR_LOWDR = 2; + } + + enum Orientation { + ORIENTATION_UNSET = 0; + ORIENTATION_PORT = 1; + ORIENTATION_LAND = 2; + ORIENTATION_SQUARE = 3; + } + + enum UiModeType { + UI_MODE_TYPE_UNSET = 0; + UI_MODE_TYPE_NORMAL = 1; + UI_MODE_TYPE_DESK = 2; + UI_MODE_TYPE_CAR = 3; + UI_MODE_TYPE_TELEVISION = 4; + UI_MODE_TYPE_APPLIANCE = 5; + UI_MODE_TYPE_WATCH = 6; + UI_MODE_TYPE_VRHEADSET = 7; + } + + enum UiModeNight { + UI_MODE_NIGHT_UNSET = 0; + UI_MODE_NIGHT_NIGHT = 1; + UI_MODE_NIGHT_NOTNIGHT = 2; + } + + enum Touchscreen { + TOUCHSCREEN_UNSET = 0; + TOUCHSCREEN_NOTOUCH = 1; + TOUCHSCREEN_STYLUS = 2; + TOUCHSCREEN_FINGER = 3; + } + + enum KeysHidden { + KEYS_HIDDEN_UNSET = 0; + KEYS_HIDDEN_KEYSEXPOSED = 1; + KEYS_HIDDEN_KEYSHIDDEN = 2; + KEYS_HIDDEN_KEYSSOFT = 3; + } + + enum Keyboard { + KEYBOARD_UNSET = 0; + KEYBOARD_NOKEYS = 1; + KEYBOARD_QWERTY = 2; + KEYBOARD_TWELVEKEY = 3; + } + + enum NavHidden { + NAV_HIDDEN_UNSET = 0; + NAV_HIDDEN_NAVEXPOSED = 1; + NAV_HIDDEN_NAVHIDDEN = 2; + } + + enum Navigation { + NAVIGATION_UNSET = 0; + NAVIGATION_NONAV = 1; + NAVIGATION_DPAD = 2; + NAVIGATION_TRACKBALL = 3; + NAVIGATION_WHEEL = 4; + } + + // + // Axis/dimensions that are understood by the runtime. + // + + // Mobile country code. + uint32 mcc = 1; + + // Mobile network code. + uint32 mnc = 2; + + // BCP-47 locale tag. + string locale = 3; + + // Left-to-right, right-to-left... + LayoutDirection layout_direction = 4; + + // Screen width in pixels. Prefer screen_width_dp. + uint32 screen_width = 5; + + // Screen height in pixels. Prefer screen_height_dp. + uint32 screen_height = 6; + + // Screen width in density independent pixels (dp). + uint32 screen_width_dp = 7; + + // Screen height in density independent pixels (dp). + uint32 screen_height_dp = 8; + + // The smallest screen dimension, regardless of orientation, in dp. + uint32 smallest_screen_width_dp = 9; + + // Whether the device screen is classified as small, normal, large, xlarge. + ScreenLayoutSize screen_layout_size = 10; + + // Whether the device screen is long. + ScreenLayoutLong screen_layout_long = 11; + + // Whether the screen is round (Android Wear). + ScreenRound screen_round = 12; + + // Whether the screen supports wide color gamut. + WideColorGamut wide_color_gamut = 13; + + // Whether the screen has high dynamic range. + Hdr hdr = 14; + + // Which orientation the device is in (portrait, landscape). + Orientation orientation = 15; + + // Which type of UI mode the device is in (television, car, etc.). + UiModeType ui_mode_type = 16; + + // Whether the device is in night mode. + UiModeNight ui_mode_night = 17; + + // The device's screen density in dots-per-inch (dpi). + uint32 density = 18; + + // Whether a touchscreen exists, supports a stylus, or finger. + Touchscreen touchscreen = 19; + + // Whether the keyboard hardware keys are currently hidden, exposed, or + // if the keyboard is a software keyboard. + KeysHidden keys_hidden = 20; + + // The type of keyboard present (none, QWERTY, 12-key). + Keyboard keyboard = 21; + + // Whether the navigation is exposed or hidden. + NavHidden nav_hidden = 22; + + // The type of navigation present on the device + // (trackball, wheel, dpad, etc.). + Navigation navigation = 23; + + // The minimum SDK version of the device. + uint32 sdk_version = 24; + + // + // Build-time only dimensions. + // + + string product = 25; +} diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index 49ed7780f950..0f6fb50aba48 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -17,7 +17,6 @@ #include "Debug.h" #include <algorithm> -#include <iostream> #include <map> #include <memory> #include <queue> @@ -25,120 +24,245 @@ #include <vector> #include "android-base/logging.h" +#include "android-base/stringprintf.h" #include "ResourceTable.h" #include "ResourceValues.h" #include "ValueVisitor.h" +#include "text/Printer.h" #include "util/Util.h" +using ::aapt::text::Printer; +using ::android::StringPiece; +using ::android::base::StringPrintf; + namespace aapt { namespace { -class PrintVisitor : public ValueVisitor { +class ValueHeadlinePrinter : public ConstValueVisitor { public: - using ValueVisitor::Visit; + using ConstValueVisitor::Visit; - void Visit(Attribute* attr) override { - std::cout << "(attr) type="; - attr->PrintMask(&std::cout); - static constexpr uint32_t kMask = - android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS; - if (attr->type_mask & kMask) { - for (const auto& symbol : attr->symbols) { - std::cout << "\n " << symbol.symbol.name.value().entry; - if (symbol.symbol.id) { - std::cout << " (" << symbol.symbol.id.value() << ")"; - } - std::cout << " = " << symbol.value; - } + explicit ValueHeadlinePrinter(const std::string& package, Printer* printer) + : package_(package), printer_(printer) { + } + + void Visit(const Attribute* attr) override { + printer_->Print("(attr) type="); + printer_->Print(attr->MaskString()); + if (!attr->symbols.empty()) { + printer_->Print(StringPrintf(" size=%zd", attr->symbols.size())); } } - void Visit(Style* style) override { - std::cout << "(style)"; + void Visit(const Style* style) override { + printer_->Print(StringPrintf("(style) size=%zd", style->entries.size())); if (style->parent) { + printer_->Print(" parent="); + const Reference& parent_ref = style->parent.value(); - std::cout << " parent="; if (parent_ref.name) { if (parent_ref.private_reference) { - std::cout << "*"; + printer_->Print("*"); + } + + const ResourceName& parent_name = parent_ref.name.value(); + if (package_ != parent_name.package) { + printer_->Print(parent_name.package); + printer_->Print(":"); } - std::cout << parent_ref.name.value() << " "; + printer_->Print(to_string(parent_name.type)); + printer_->Print("/"); + printer_->Print(parent_name.entry); + if (parent_ref.id) { + printer_->Print(" ("); + printer_->Print(parent_ref.id.value().to_string()); + printer_->Print(")"); + } + } else if (parent_ref.id) { + printer_->Print(parent_ref.id.value().to_string()); + } else { + printer_->Print("???"); } + } + } + + void Visit(const Array* array) override { + printer_->Print(StringPrintf("(array) size=%zd", array->elements.size())); + } + + void Visit(const Plural* plural) override { + size_t count = std::count_if(plural->values.begin(), plural->values.end(), + [](const std::unique_ptr<Item>& v) { return v != nullptr; }); + printer_->Print(StringPrintf("(plurals) size=%zd", count)); + } + + void Visit(const Styleable* styleable) override { + printer_->Println(StringPrintf("(styleable) size=%zd", styleable->entries.size())); + } + + void VisitItem(const Item* item) override { + // Pretty much guaranteed to be one line. + if (const Reference* ref = ValueCast<Reference>(item)) { + // Special case Reference so that we can print local resources without a package name. + ref->PrettyPrint(package_, printer_); + } else { + item->PrettyPrint(printer_); + } + } + + private: + std::string package_; + Printer* printer_; +}; + +class ValueBodyPrinter : public ConstValueVisitor { + public: + using ConstValueVisitor::Visit; - if (parent_ref.id) { - std::cout << parent_ref.id.value(); + explicit ValueBodyPrinter(const std::string& package, Printer* printer) + : package_(package), printer_(printer) { + } + + void Visit(const Attribute* attr) override { + constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS; + if (attr->type_mask & kMask) { + for (const auto& symbol : attr->symbols) { + printer_->Print(symbol.symbol.name.value().entry); + if (symbol.symbol.id) { + printer_->Print("("); + printer_->Print(symbol.symbol.id.value().to_string()); + printer_->Print(")"); + } + printer_->Println(StringPrintf("=0x%08x", symbol.value)); } } + } + void Visit(const Style* style) override { for (const auto& entry : style->entries) { - std::cout << "\n "; if (entry.key.name) { const ResourceName& name = entry.key.name.value(); - if (!name.package.empty()) { - std::cout << name.package << ":"; + if (!name.package.empty() && name.package != package_) { + printer_->Print(name.package); + printer_->Print(":"); } - std::cout << name.entry; - } + printer_->Print(name.entry); - if (entry.key.id) { - std::cout << "(" << entry.key.id.value() << ")"; + if (entry.key.id) { + printer_->Print("("); + printer_->Print(entry.key.id.value().to_string()); + printer_->Print(")"); + } + } else if (entry.key.id) { + printer_->Print(entry.key.id.value().to_string()); + } else { + printer_->Print("???"); } - std::cout << "=" << *entry.value; + printer_->Print("="); + PrintItem(*entry.value); + printer_->Println(); } } - void Visit(Array* array) override { - array->Print(&std::cout); + void Visit(const Array* array) override { + const size_t count = array->elements.size(); + printer_->Print("["); + if (count > 0) { + for (size_t i = 0u; i < count; i++) { + if (i != 0u && i % 4u == 0u) { + printer_->Println(); + printer_->Print(" "); + } + PrintItem(*array->elements[i]); + if (i != count - 1) { + printer_->Print(", "); + } + } + printer_->Println("]"); + } } - void Visit(Plural* plural) override { - plural->Print(&std::cout); + void Visit(const Plural* plural) override { + constexpr std::array<const char*, Plural::Count> kPluralNames = { + {"zero", "one", "two", "few", "many", "other"}}; + + for (size_t i = 0; i < Plural::Count; i++) { + if (plural->values[i] != nullptr) { + printer_->Print(StringPrintf("%s=", kPluralNames[i])); + PrintItem(*plural->values[i]); + printer_->Println(); + } + } } - void Visit(Styleable* styleable) override { - std::cout << "(styleable)"; + void Visit(const Styleable* styleable) override { for (const auto& attr : styleable->entries) { - std::cout << "\n "; if (attr.name) { const ResourceName& name = attr.name.value(); - if (!name.package.empty()) { - std::cout << name.package << ":"; + if (!name.package.empty() && name.package != package_) { + printer_->Print(name.package); + printer_->Print(":"); + } + printer_->Print(name.entry); + + if (attr.id) { + printer_->Print("("); + printer_->Print(attr.id.value().to_string()); + printer_->Print(")"); } - std::cout << name.entry; } if (attr.id) { - std::cout << "(" << attr.id.value() << ")"; + printer_->Print(attr.id.value().to_string()); } + printer_->Println(); } } - void VisitItem(Item* item) override { - item->Print(&std::cout); + void VisitItem(const Item* item) override { + // Intentionally left empty, we already printed the Items. } + + private: + void PrintItem(const Item& item) { + if (const Reference* ref = ValueCast<Reference>(&item)) { + // Special case Reference so that we can print local resources without a package name. + ref->PrettyPrint(package_, printer_); + } else { + item.PrettyPrint(printer_); + } + } + + std::string package_; + Printer* printer_; }; } // namespace -void Debug::PrintTable(ResourceTable* table, const DebugPrintTableOptions& options) { - PrintVisitor visitor; +void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options, + Printer* printer) { + for (const auto& package : table.packages) { + ValueHeadlinePrinter headline_printer(package->name, printer); + ValueBodyPrinter body_printer(package->name, printer); - for (auto& package : table->packages) { - std::cout << "Package name=" << package->name; + printer->Print("Package name="); + printer->Print(package->name); if (package->id) { - std::cout << " id=" << std::hex << (int)package->id.value() << std::dec; + printer->Print(StringPrintf(" id=%02x", package->id.value())); } - std::cout << std::endl; + printer->Println(); + printer->Indent(); for (const auto& type : package->types) { - std::cout << "\n type " << type->type; + printer->Print("type "); + printer->Print(to_string(type->type)); if (type->id) { - std::cout << " id=" << std::hex << (int)type->id.value() << std::dec; + printer->Print(StringPrintf(" id=%02x", type->id.value())); } - std::cout << " entryCount=" << type->entries.size() << std::endl; + printer->Println(StringPrintf(" entryCount=%zd", type->entries.size())); std::vector<const ResourceEntry*> sorted_entries; for (const auto& entry : type->entries) { @@ -156,35 +280,54 @@ void Debug::PrintTable(ResourceTable* table, const DebugPrintTableOptions& optio sorted_entries.insert(iter, entry.get()); } + printer->Indent(); for (const ResourceEntry* entry : sorted_entries) { const ResourceId id(package->id.value_or_default(0), type->id.value_or_default(0), entry->id.value_or_default(0)); - const ResourceName name(package->name, type->type, entry->name); - std::cout << " spec resource " << id << " " << name; + printer->Print("resource "); + printer->Print(id.to_string()); + printer->Print(" "); + + // Write the name without the package (this is obvious and too verbose). + printer->Print(to_string(type->type)); + printer->Print("/"); + printer->Print(entry->name); + switch (entry->symbol_status.state) { case SymbolState::kPublic: - std::cout << " PUBLIC"; + printer->Print(" PUBLIC"); break; case SymbolState::kPrivate: - std::cout << " _PRIVATE_"; + printer->Print(" _PRIVATE_"); break; - default: + case SymbolState::kUndefined: + // Print nothing. break; } - std::cout << std::endl; + printer->Println(); + printer->Indent(); for (const auto& value : entry->values) { - std::cout << " (" << value->config << ") "; - value->value->Accept(&visitor); + printer->Print("("); + printer->Print(value->config.to_string()); + printer->Print(") "); + value->value->Accept(&headline_printer); if (options.show_sources && !value->value->GetSource().path.empty()) { - std::cout << " src=" << value->value->GetSource(); + printer->Print(" src="); + printer->Print(value->value->GetSource().to_string()); } - std::cout << std::endl; + printer->Println(); + printer->Indent(); + value->value->Accept(&body_printer); + printer->Undent(); } + printer->Undent(); } + printer->Undent(); } + printer->Undent(); } } @@ -261,11 +404,11 @@ void Debug::DumpHex(const void* data, size_t len) { namespace { -class XmlPrinter : public xml::Visitor { +class XmlPrinter : public xml::ConstVisitor { public: - using xml::Visitor::Visit; + using xml::ConstVisitor::Visit; - void Visit(xml::Element* el) override { + void Visit(const xml::Element* el) override { const size_t previous_size = prefix_.size(); for (const xml::NamespaceDecl& decl : el->namespace_decls) { @@ -291,15 +434,21 @@ class XmlPrinter : public xml::Visitor { std::cerr << "(" << attr.compiled_attribute.value().id.value_or_default(ResourceId(0x0)) << ")"; } - std::cerr << "=" << attr.value << "\n"; + std::cerr << "="; + if (attr.compiled_value != nullptr) { + std::cerr << *attr.compiled_value; + } else { + std::cerr << attr.value; + } + std::cerr << "\n"; } prefix_ += " "; - xml::Visitor::Visit(el); + xml::ConstVisitor::Visit(el); prefix_.resize(previous_size); } - void Visit(xml::Text* text) override { + void Visit(const xml::Text* text) override { std::cerr << prefix_ << "T: '" << text->text << "'\n"; } @@ -309,9 +458,9 @@ class XmlPrinter : public xml::Visitor { } // namespace -void Debug::DumpXml(xml::XmlResource* doc) { +void Debug::DumpXml(const xml::XmlResource& doc) { XmlPrinter printer; - doc->root->Accept(&printer); + doc.root->Accept(&printer); } } // namespace aapt diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h index e2456c7f98b2..3c1ee4c0cdba 100644 --- a/tools/aapt2/Debug.h +++ b/tools/aapt2/Debug.h @@ -22,6 +22,7 @@ #include "Resource.h" #include "ResourceTable.h" +#include "text/Printer.h" #include "xml/XmlDom.h" namespace aapt { @@ -31,13 +32,11 @@ struct DebugPrintTableOptions { }; struct Debug { - static void PrintTable(ResourceTable* table, - const DebugPrintTableOptions& options = {}); - static void PrintStyleGraph(ResourceTable* table, - const ResourceName& target_style); + static void PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options, + text::Printer* printer); + static void PrintStyleGraph(ResourceTable* table, const ResourceName& target_style); static void DumpHex(const void* data, size_t len); - static void DumpXml(xml::XmlResource* doc); - static std::string ToString(xml::XmlResource* doc); + static void DumpXml(const xml::XmlResource& doc); }; } // namespace aapt diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp index 7e5efa15f61b..20a9f417228c 100644 --- a/tools/aapt2/LoadedApk.cpp +++ b/tools/aapt2/LoadedApk.cpp @@ -18,54 +18,156 @@ #include "ResourceValues.h" #include "ValueVisitor.h" -#include "flatten/Archive.h" -#include "flatten/TableFlattener.h" -#include "io/BigBufferInputStream.h" +#include "format/Archive.h" +#include "format/binary/TableFlattener.h" +#include "format/binary/XmlFlattener.h" +#include "format/proto/ProtoDeserialize.h" +#include "io/BigBufferStream.h" #include "io/Util.h" +#include "xml/XmlDom.h" + +using ::aapt::io::IFile; +using ::aapt::io::IFileCollection; +using ::aapt::xml::XmlResource; +using ::android::StringPiece; +using ::std::unique_ptr; namespace aapt { -std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context, - const android::StringPiece& path) { +std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path, IDiagnostics* diag) { Source source(path); std::string error; std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error); - if (!apk) { - context->GetDiagnostics()->Error(DiagMessage(source) << error); + if (apk == nullptr) { + diag->Error(DiagMessage(path) << "failed opening zip: " << error); + return {}; + } + + ApkFormat apkFormat = DetermineApkFormat(apk.get()); + switch (apkFormat) { + case ApkFormat::kBinary: + return LoadBinaryApkFromFileCollection(source, std::move(apk), diag); + case ApkFormat::kProto: + return LoadProtoApkFromFileCollection(source, std::move(apk), diag); + default: + diag->Error(DiagMessage(path) << "could not identify format of APK"); + return {}; + } +} + +std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection( + const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) { + std::unique_ptr<ResourceTable> table; + + io::IFile* table_file = collection->FindFile(kProtoResourceTablePath); + if (table_file != nullptr) { + pb::ResourceTable pb_table; + std::unique_ptr<io::InputStream> in = table_file->OpenInputStream(); + if (in == nullptr) { + diag->Error(DiagMessage(source) << "failed to open " << kProtoResourceTablePath); + return {}; + } + + io::ZeroCopyInputAdaptor adaptor(in.get()); + if (!pb_table.ParseFromZeroCopyStream(&adaptor)) { + diag->Error(DiagMessage(source) << "failed to read " << kProtoResourceTablePath); + return {}; + } + + std::string error; + table = util::make_unique<ResourceTable>(); + if (!DeserializeTableFromPb(pb_table, collection.get(), table.get(), &error)) { + diag->Error(DiagMessage(source) + << "failed to deserialize " << kProtoResourceTablePath << ": " << error); + return {}; + } + } + + io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath); + if (manifest_file == nullptr) { + diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath); return {}; } - io::IFile* file = apk->FindFile("resources.arsc"); - if (!file) { - context->GetDiagnostics()->Error(DiagMessage(source) << "no resources.arsc found"); + std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream(); + if (manifest_in == nullptr) { + diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath); + return {}; + } + + pb::XmlNode pb_node; + io::ZeroCopyInputAdaptor manifest_adaptor(manifest_in.get()); + if (!pb_node.ParseFromZeroCopyStream(&manifest_adaptor)) { + diag->Error(DiagMessage(source) << "failed to read proto " << kAndroidManifestPath); + return {}; + } + + std::string error; + std::unique_ptr<xml::XmlResource> manifest = DeserializeXmlResourceFromPb(pb_node, &error); + if (manifest == nullptr) { + diag->Error(DiagMessage(source) + << "failed to deserialize proto " << kAndroidManifestPath << ": " << error); + return {}; + } + return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table), + std::move(manifest)); +} + +std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection( + const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) { + std::unique_ptr<ResourceTable> table; + + io::IFile* table_file = collection->FindFile(kApkResourceTablePath); + if (table_file != nullptr) { + table = util::make_unique<ResourceTable>(); + std::unique_ptr<io::IData> data = table_file->OpenAsData(); + if (data == nullptr) { + diag->Error(DiagMessage(source) << "failed to open " << kApkResourceTablePath); + return {}; + } + BinaryResourceParser parser(diag, table.get(), source, data->data(), data->size(), + collection.get()); + if (!parser.Parse()) { + return {}; + } + } + + io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath); + if (manifest_file == nullptr) { + diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath); return {}; } - std::unique_ptr<io::IData> data = file->OpenAsData(); - if (!data) { - context->GetDiagnostics()->Error(DiagMessage(source) << "could not open resources.arsc"); + std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData(); + if (manifest_data == nullptr) { + diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath); return {}; } - std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); - BinaryResourceParser parser(context, table.get(), source, data->data(), data->size(), apk.get()); - if (!parser.Parse()) { + std::string error; + std::unique_ptr<xml::XmlResource> manifest = + xml::Inflate(manifest_data->data(), manifest_data->size(), &error); + if (manifest == nullptr) { + diag->Error(DiagMessage(source) + << "failed to parse binary " << kAndroidManifestPath << ": " << error); return {}; } - return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table)); + return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table), + std::move(manifest)); } bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, IArchiveWriter* writer) { FilterChain empty; - return WriteToArchive(context, options, &empty, writer); + return WriteToArchive(context, table_.get(), options, &empty, writer); } -bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, - FilterChain* filters, IArchiveWriter* writer) { +bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table, + const TableFlattenerOptions& options, FilterChain* filters, + IArchiveWriter* writer, XmlResource* manifest) { std::set<std::string> referenced_resources; // List the files being referenced in the resource table. - for (auto& pkg : table_->packages) { + for (auto& pkg : split_table->packages) { for (auto& type : pkg->types) { for (auto& entry : type->entries) { for (auto& config_value : entry->values) { @@ -84,7 +186,7 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption std::string path = file->GetSource().path; // The name of the path has the format "<zip-file-name>@<path-to-file>". - path = path.substr(path.find("@") + 1); + path = path.substr(path.find('@') + 1); // Skip resources that are not referenced if requested. if (path.find("res/") == 0 && referenced_resources.find(path) == referenced_resources.end()) { @@ -108,7 +210,7 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption // TODO(adamlesinski): How to determine if there were sparse entries (and if to encode // with sparse entries) b/35389232. TableFlattener flattener(options, &buffer); - if (!flattener.Consume(context, table_.get())) { + if (!flattener.Consume(context, split_table)) { return false; } @@ -118,9 +220,22 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption return false; } - } else { + } else if (manifest != nullptr && path == "AndroidManifest.xml") { + BigBuffer buffer(8192); + XmlFlattener xml_flattener(&buffer, {}); + if (!xml_flattener.Consume(context, manifest)) { + context->GetDiagnostics()->Error(DiagMessage(path) << "flattening failed"); + return false; + } + uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u; - if (!io::CopyFileToArchive(context, file, path, compression_flags, writer)) { + io::BigBufferInputStream manifest_buffer_in(&buffer); + if (!io::CopyInputStreamToArchive(context, &manifest_buffer_in, path, compression_flags, + writer)) { + return false; + } + } else { + if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) { return false; } } @@ -128,4 +243,41 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption return true; } +ApkFormat LoadedApk::DetermineApkFormat(io::IFileCollection* apk) { + if (apk->FindFile("resources.arsc") != nullptr) { + return ApkFormat::kBinary; + } else if (apk->FindFile("resources.pb") != nullptr) { + return ApkFormat::kProto; + } else { + // If the resource table is not present, attempt to read the manifest. + io::IFile* manifest_file = apk->FindFile(kAndroidManifestPath); + if (manifest_file == nullptr) { + return ApkFormat::kUnknown; + } + + // First try in proto format. + std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream(); + if (manifest_in != nullptr) { + pb::XmlNode pb_node; + io::ZeroCopyInputAdaptor manifest_adaptor(manifest_in.get()); + if (pb_node.ParseFromZeroCopyStream(&manifest_adaptor)) { + return ApkFormat::kProto; + } + } + + // If it didn't work, try in binary format. + std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData(); + if (manifest_data != nullptr) { + std::string error; + std::unique_ptr<xml::XmlResource> manifest = + xml::Inflate(manifest_data->data(), manifest_data->size(), &error); + if (manifest != nullptr) { + return ApkFormat::kBinary; + } + } + + return ApkFormat::kUnknown; + } +} + } // namespace aapt diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h index 8aa9674aa2ed..6d2257f2e17a 100644 --- a/tools/aapt2/LoadedApk.h +++ b/tools/aapt2/LoadedApk.h @@ -21,52 +21,97 @@ #include "ResourceTable.h" #include "filter/Filter.h" -#include "flatten/Archive.h" -#include "flatten/TableFlattener.h" +#include "format/Archive.h" +#include "format/binary/BinaryResourceParser.h" +#include "format/binary/TableFlattener.h" #include "io/ZipArchive.h" -#include "unflatten/BinaryResourceParser.h" +#include "xml/XmlDom.h" namespace aapt { -/** Info about an APK loaded in memory. */ +constexpr static const char kApkResourceTablePath[] = "resources.arsc"; +constexpr static const char kProtoResourceTablePath[] = "resources.pb"; +constexpr static const char kAndroidManifestPath[] = "AndroidManifest.xml"; + +enum ApkFormat { + kUnknown, + kBinary, + kProto, +}; + +// Info about an APK loaded in memory. class LoadedApk { public: - LoadedApk( - const Source& source, - std::unique_ptr<io::IFileCollection> apk, - std::unique_ptr<ResourceTable> table) - : source_(source), apk_(std::move(apk)), table_(std::move(table)) {} + // Loads both binary and proto APKs from disk. + static std::unique_ptr<LoadedApk> LoadApkFromPath(const ::android::StringPiece& path, + IDiagnostics* diag); + + // Loads a proto APK from the given file collection. + static std::unique_ptr<LoadedApk> LoadProtoApkFromFileCollection( + const Source& source, std::unique_ptr<io::IFileCollection> collection, IDiagnostics* diag); + + // Loads a binary APK from the given file collection. + static std::unique_ptr<LoadedApk> LoadBinaryApkFromFileCollection( + const Source& source, std::unique_ptr<io::IFileCollection> collection, IDiagnostics* diag); + + LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk, + std::unique_ptr<ResourceTable> table, std::unique_ptr<xml::XmlResource> manifest) + : source_(source), + apk_(std::move(apk)), + table_(std::move(table)), + manifest_(std::move(manifest)) { + } - io::IFileCollection* GetFileCollection() { return apk_.get(); } + io::IFileCollection* GetFileCollection() { + return apk_.get(); + } - ResourceTable* GetResourceTable() { return table_.get(); } + const ResourceTable* GetResourceTable() const { + return table_.get(); + } - const Source& GetSource() { return source_; } + ResourceTable* GetResourceTable() { + return table_.get(); + } + + const Source& GetSource() { + return source_; + } + + const xml::XmlResource* GetManifest() const { + return manifest_.get(); + } /** * Writes the APK on disk at the given path, while also removing the resource * files that are not referenced in the resource table. */ - bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, - IArchiveWriter* writer); + virtual bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, + IArchiveWriter* writer); /** - * Writes the APK on disk at the given path, while also removing the resource - * files that are not referenced in the resource table. The provided filter - * chain is applied to each entry in the APK file. + * Writes the APK on disk at the given path, while also removing the resource files that are not + * referenced in the resource table. The provided filter chain is applied to each entry in the APK + * file. + * + * If the manifest is also provided, it will be written to the new APK file, otherwise the + * original manifest will be written. The manifest is only required if the contents of the new APK + * have been modified in a way that require the AndroidManifest.xml to also be modified. */ - bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, - FilterChain* filters, IArchiveWriter* writer); + virtual bool WriteToArchive(IAaptContext* context, ResourceTable* split_table, + const TableFlattenerOptions& options, FilterChain* filters, + IArchiveWriter* writer, xml::XmlResource* manifest = nullptr); - static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context, - const android::StringPiece& path); private: + DISALLOW_COPY_AND_ASSIGN(LoadedApk); + Source source_; std::unique_ptr<io::IFileCollection> apk_; std::unique_ptr<ResourceTable> table_; + std::unique_ptr<xml::XmlResource> manifest_; - DISALLOW_COPY_AND_ASSIGN(LoadedApk); + static ApkFormat DetermineApkFormat(io::IFileCollection* apk); }; } // namespace aapt diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp index 7664fac44be1..d81921f23904 100644 --- a/tools/aapt2/Locale.cpp +++ b/tools/aapt2/Locale.cpp @@ -24,9 +24,10 @@ #include "util/Util.h" -namespace aapt { +using ::android::ResTable_config; +using ::android::StringPiece; -using android::ResTable_config; +namespace aapt { void LocaleValue::set_language(const char* language_chars) { size_t i = 0; @@ -72,7 +73,7 @@ static inline bool is_number(const std::string& str) { return std::all_of(std::begin(str), std::end(str), ::isdigit); } -bool LocaleValue::InitFromFilterString(const android::StringPiece& str) { +bool LocaleValue::InitFromFilterString(const StringPiece& str) { // A locale (as specified in the filter) is an underscore separated name such // as "en_US", "en_Latn_US", or "en_US_POSIX". std::vector<std::string> parts = util::SplitAndLowercase(str, '_'); @@ -138,6 +139,71 @@ bool LocaleValue::InitFromFilterString(const android::StringPiece& str) { return true; } +bool LocaleValue::InitFromBcp47Tag(const StringPiece& bcp47tag) { + return InitFromBcp47TagImpl(bcp47tag, '-'); +} + +bool LocaleValue::InitFromBcp47TagImpl(const StringPiece& bcp47tag, const char separator) { + std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator); + if (subtags.size() == 1) { + set_language(subtags[0].c_str()); + } else if (subtags.size() == 2) { + set_language(subtags[0].c_str()); + + // The second tag can either be a region, a variant or a script. + switch (subtags[1].size()) { + case 2: + case 3: + set_region(subtags[1].c_str()); + break; + case 4: + if ('0' <= subtags[1][0] && subtags[1][0] <= '9') { + // This is a variant: fall through + } else { + set_script(subtags[1].c_str()); + break; + } + case 5: + case 6: + case 7: + case 8: + set_variant(subtags[1].c_str()); + break; + default: + return false; + } + } else if (subtags.size() == 3) { + // The language is always the first subtag. + set_language(subtags[0].c_str()); + + // The second subtag can either be a script or a region code. + // If its size is 4, it's a script code, else it's a region code. + if (subtags[1].size() == 4) { + set_script(subtags[1].c_str()); + } else if (subtags[1].size() == 2 || subtags[1].size() == 3) { + set_region(subtags[1].c_str()); + } else { + return false; + } + + // The third tag can either be a region code (if the second tag was + // a script), else a variant code. + if (subtags[2].size() >= 4) { + set_variant(subtags[2].c_str()); + } else { + set_region(subtags[2].c_str()); + } + } else if (subtags.size() == 4) { + set_language(subtags[0].c_str()); + set_script(subtags[1].c_str()); + set_region(subtags[2].c_str()); + set_variant(subtags[3].c_str()); + } else { + return false; + } + return true; +} + ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter, std::vector<std::string>::iterator end) { const std::vector<std::string>::iterator start_iter = iter; @@ -145,71 +211,13 @@ ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter, std::string& part = *iter; if (part[0] == 'b' && part[1] == '+') { // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags, - // except that the separator is "+" and not "-". - std::vector<std::string> subtags = util::SplitAndLowercase(part, '+'); - subtags.erase(subtags.begin()); - if (subtags.size() == 1) { - set_language(subtags[0].c_str()); - } else if (subtags.size() == 2) { - set_language(subtags[0].c_str()); - - // The second tag can either be a region, a variant or a script. - switch (subtags[1].size()) { - case 2: - case 3: - set_region(subtags[1].c_str()); - break; - case 4: - if ('0' <= subtags[1][0] && subtags[1][0] <= '9') { - // This is a variant: fall through - } else { - set_script(subtags[1].c_str()); - break; - } - case 5: - case 6: - case 7: - case 8: - set_variant(subtags[1].c_str()); - break; - default: - return -1; - } - } else if (subtags.size() == 3) { - // The language is always the first subtag. - set_language(subtags[0].c_str()); - - // The second subtag can either be a script or a region code. - // If its size is 4, it's a script code, else it's a region code. - if (subtags[1].size() == 4) { - set_script(subtags[1].c_str()); - } else if (subtags[1].size() == 2 || subtags[1].size() == 3) { - set_region(subtags[1].c_str()); - } else { - return -1; - } - - // The third tag can either be a region code (if the second tag was - // a script), else a variant code. - if (subtags[2].size() >= 4) { - set_variant(subtags[2].c_str()); - } else { - set_region(subtags[2].c_str()); - } - } else if (subtags.size() == 4) { - set_language(subtags[0].c_str()); - set_script(subtags[1].c_str()); - set_region(subtags[2].c_str()); - set_variant(subtags[3].c_str()); - } else { + // except that the separator is "+" and not "-". Skip the prefix 'b+'. + if (!InitFromBcp47TagImpl(StringPiece(part).substr(2), '+')) { return -1; } - ++iter; - } else { - if ((part.length() == 2 || part.length() == 3) && is_alpha(part) && - part != "car") { + if ((part.length() == 2 || part.length() == 3) && is_alpha(part) && part != "car") { set_language(part.c_str()); ++iter; @@ -222,7 +230,6 @@ ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter, } } } - return static_cast<ssize_t>(iter - start_iter); } diff --git a/tools/aapt2/Locale.h b/tools/aapt2/Locale.h index 3d73b2eb17bf..6d8b598415cc 100644 --- a/tools/aapt2/Locale.h +++ b/tools/aapt2/Locale.h @@ -41,6 +41,9 @@ struct LocaleValue { */ bool InitFromFilterString(const android::StringPiece& config); + // Initializes this LocaleValue from a BCP-47 locale tag. + bool InitFromBcp47Tag(const android::StringPiece& bcp47tag); + /** * Initialize this LocaleValue from parts of a vector. */ @@ -67,6 +70,8 @@ struct LocaleValue { inline bool operator>(const LocaleValue& o) const; private: + bool InitFromBcp47TagImpl(const android::StringPiece& bcp47tag, const char separator); + void set_language(const char* language); void set_region(const char* language); void set_script(const char* script); diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index 36ab30c7fb6e..808b29cfd844 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -50,7 +50,7 @@ static void PrintVersion() { } static void PrintUsage() { - std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|version] ..." << std::endl; + std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|convert|version] ..." << std::endl; } extern int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics); @@ -58,6 +58,7 @@ extern int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) extern int Dump(const std::vector<StringPiece>& args); extern int Diff(const std::vector<StringPiece>& args); extern int Optimize(const std::vector<StringPiece>& args); +extern int Convert(const std::vector<StringPiece>& args); static int ExecuteCommand(const StringPiece& command, const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { @@ -71,6 +72,8 @@ static int ExecuteCommand(const StringPiece& command, const std::vector<StringPi return Diff(args); } else if (command == "optimize") { return Optimize(args); + } else if (command == "convert") { + return Convert(args); } else if (command == "version") { PrintVersion(); return 0; diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h index 1305a4cf0710..f1aad29a5c58 100644 --- a/tools/aapt2/NameMangler.h +++ b/tools/aapt2/NameMangler.h @@ -69,8 +69,7 @@ class NameMangler { * The mangled name should contain symbols that are illegal to define in XML, * so that there will never be name mangling collisions. */ - static std::string MangleEntry(const std::string& package, - const std::string& name) { + static std::string MangleEntry(const std::string& package, const std::string& name) { return package + "$" + name; } @@ -86,8 +85,8 @@ class NameMangler { } out_package->assign(out_name->data(), pivot); - out_name->assign(out_name->data() + pivot + 1, - out_name->size() - (pivot + 1)); + std::string new_name = out_name->substr(pivot + 1); + *out_name = std::move(new_name); return true; } diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp index a9f5f298e019..b78f48ce7f17 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -17,13 +17,34 @@ #include "Resource.h" #include <map> +#include <sstream> #include <string> -using android::StringPiece; +#include "android-base/stringprintf.h" + +using ::android::StringPiece; +using ::android::base::StringPrintf; namespace aapt { -StringPiece ToString(ResourceType type) { +std::string ResourceId::to_string() const { + return StringPrintf("0x%08x", id); +} + +std::string ResourceName::to_string() const { + return ResourceNameRef(*this).to_string(); +} + +std::string ResourceNameRef::to_string() const { + std::ostringstream str_stream; + if (!package.empty()) { + str_stream << package << ":"; + } + str_stream << type << "/" << entry; + return str_stream.str(); +} + +StringPiece to_string(ResourceType type) { switch (type) { case ResourceType::kAnim: return "anim"; diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index cbcc8fb805aa..6fcf0f6d19cd 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -69,11 +69,10 @@ enum class ResourceType { kXml, }; -android::StringPiece ToString(ResourceType type); +android::StringPiece to_string(ResourceType type); /** - * Returns a pointer to a valid ResourceType, or nullptr if - * the string was invalid. + * Returns a pointer to a valid ResourceType, or nullptr if the string was invalid. */ const ResourceType* ParseResourceType(const android::StringPiece& str); @@ -92,7 +91,7 @@ struct ResourceName { int compare(const ResourceName& other) const; bool is_valid() const; - std::string ToString() const; + std::string to_string() const; }; /** @@ -115,8 +114,10 @@ struct ResourceNameRef { ResourceNameRef& operator=(ResourceNameRef&& rhs) = default; ResourceNameRef& operator=(const ResourceName& rhs); - ResourceName ToResourceName() const; bool is_valid() const; + + ResourceName ToResourceName() const; + std::string to_string() const; }; constexpr const uint8_t kAppPackageId = 0x7fu; @@ -149,6 +150,8 @@ struct ResourceId { uint8_t package_id() const; uint8_t type_id() const; uint16_t entry_id() const; + + std::string to_string() const; }; struct SourcedResourceName { @@ -157,12 +160,22 @@ struct SourcedResourceName { }; struct ResourceFile { + enum class Type { + kUnknown, + kPng, + kBinaryXml, + kProtoXml, + }; + // Name ResourceName name; // Configuration ConfigDescription config; + // Type + Type type; + // Source Source source; @@ -219,7 +232,9 @@ inline bool ResourceId::is_valid() const { return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0; } -inline bool ResourceId::is_valid_dynamic() const { return (id & 0x00ff0000u) != 0; } +inline bool ResourceId::is_valid_dynamic() const { + return (id & 0x00ff0000u) != 0; +} inline uint8_t ResourceId::package_id() const { return static_cast<uint8_t>(id >> 24); @@ -249,24 +264,21 @@ inline bool operator!=(const ResourceId& lhs, const ResourceId& rhs) { return lhs.id != rhs.id; } -inline ::std::ostream& operator<<(::std::ostream& out, - const ResourceId& res_id) { - std::ios_base::fmtflags old_flags = out.flags(); - char old_fill = out.fill(); - out << "0x" << std::internal << std::setfill('0') << std::setw(8) << std::hex - << res_id.id; - out.flags(old_flags); - out.fill(old_fill); - return out; +inline ::std::ostream& operator<<(::std::ostream& out, const ResourceId& res_id) { + return out << res_id.to_string(); +} + +// For generic code to call 'using std::to_string; to_string(T);'. +inline std::string to_string(const ResourceId& id) { + return id.to_string(); } // // ResourceType implementation. // -inline ::std::ostream& operator<<(::std::ostream& out, - const ResourceType& val) { - return out << ToString(val); +inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) { + return out << to_string(val); } // @@ -305,18 +317,8 @@ inline bool operator!=(const ResourceName& lhs, const ResourceName& rhs) { std::tie(rhs.package, rhs.type, rhs.entry); } -inline ::std::ostream& operator<<(::std::ostream& out, - const ResourceName& name) { - if (!name.package.empty()) { - out << name.package << ":"; - } - return out << name.type << "/" << name.entry; -} - -inline std::string ResourceName::ToString() const { - std::stringstream stream; - stream << *this; - return stream.str(); +inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) { + return out << name.to_string(); } // @@ -360,12 +362,8 @@ inline bool operator!=(const ResourceNameRef& lhs, const ResourceNameRef& rhs) { std::tie(rhs.package, rhs.type, rhs.entry); } -inline ::std::ostream& operator<<(::std::ostream& out, - const ResourceNameRef& name) { - if (!name.package.empty()) { - out << name.package << ":"; - } - return out << name.type << "/" << name.entry; +inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNameRef& name) { + return out << name.to_string(); } inline bool operator<(const ResourceName& lhs, const ResourceNameRef& b) { @@ -376,8 +374,7 @@ inline bool operator!=(const ResourceName& lhs, const ResourceNameRef& rhs) { return ResourceNameRef(lhs) != rhs; } -inline bool operator==(const SourcedResourceName& lhs, - const SourcedResourceName& rhs) { +inline bool operator==(const SourcedResourceName& lhs, const SourcedResourceName& rhs) { return lhs.name == rhs.name && lhs.line == rhs.line; } diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 1c3ac2ad4f17..4cc60a8dbb07 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -392,6 +392,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, {"declare-styleable", std::mem_fn(&ResourceParser::ParseDeclareStyleable)}, {"integer-array", std::mem_fn(&ResourceParser::ParseIntegerArray)}, {"java-symbol", std::mem_fn(&ResourceParser::ParseSymbol)}, + {"overlayable", std::mem_fn(&ResourceParser::ParseOverlayable)}, {"plurals", std::mem_fn(&ResourceParser::ParsePlural)}, {"public", std::mem_fn(&ResourceParser::ParsePublic)}, {"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)}, @@ -498,7 +499,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, const auto bag_iter = elToBagMap.find(resource_type); if (bag_iter != elToBagMap.end()) { // Ensure we have a name (unless this is a <public-group>). - if (resource_type != "public-group") { + if (resource_type != "public-group" && resource_type != "overlayable") { if (!maybe_name) { diag_->Error(DiagMessage(out_resource->source) << "<" << parser->element_name() << "> missing 'name' attribute"); @@ -605,7 +606,7 @@ std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser, if (processed_item) { // Fix up the reference. if (Reference* ref = ValueCast<Reference>(processed_item.get())) { - TransformReferenceFromNamespace(parser, "", ref); + ResolvePackage(parser, ref); } return processed_item; } @@ -690,6 +691,11 @@ bool ResourceParser::ParseString(xml::XmlPullParser* parser, bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource) { + if (out_resource->config != ConfigDescription::DefaultConfig()) { + diag_->Warn(DiagMessage(out_resource->source) + << "ignoring configuration '" << out_resource->config << "' for <public> tag"); + } + Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); if (!maybe_type) { diag_->Error(DiagMessage(out_resource->source) @@ -726,8 +732,13 @@ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, return true; } -bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, - ParsedResource* out_resource) { +bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) { + if (out_resource->config != ConfigDescription::DefaultConfig()) { + diag_->Warn(DiagMessage(out_resource->source) + << "ignoring configuration '" << out_resource->config + << "' for <public-group> tag"); + } + Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); if (!maybe_type) { diag_->Error(DiagMessage(out_resource->source) @@ -842,13 +853,83 @@ bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser, return true; } -bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, - ParsedResource* out_resource) { - if (ParseSymbolImpl(parser, out_resource)) { - out_resource->symbol_state = SymbolState::kPrivate; - return true; +bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource) { + if (out_resource->config != ConfigDescription::DefaultConfig()) { + diag_->Warn(DiagMessage(out_resource->source) + << "ignoring configuration '" << out_resource->config << "' for <" + << parser->element_name() << "> tag"); } - return false; + + if (!ParseSymbolImpl(parser, out_resource)) { + return false; + } + + out_resource->symbol_state = SymbolState::kPrivate; + return true; +} + +bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource) { + if (out_resource->config != ConfigDescription::DefaultConfig()) { + diag_->Warn(DiagMessage(out_resource->source) + << "ignoring configuration '" << out_resource->config << "' for <overlayable> tag"); + } + + if (Maybe<StringPiece> maybe_policy = xml::FindNonEmptyAttribute(parser, "policy")) { + const StringPiece& policy = maybe_policy.value(); + if (policy != "system") { + diag_->Error(DiagMessage(out_resource->source) + << "<overlayable> has invalid policy '" << policy << "'"); + return false; + } + } + + bool error = false; + const size_t depth = parser->depth(); + while (xml::XmlPullParser::NextChildNode(parser, depth)) { + if (parser->event() != xml::XmlPullParser::Event::kStartElement) { + // Skip text/comments. + continue; + } + + const Source item_source = source_.WithLine(parser->line_number()); + const std::string& element_namespace = parser->element_namespace(); + const std::string& element_name = parser->element_name(); + if (element_namespace.empty() && element_name == "item") { + Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); + if (!maybe_name) { + diag_->Error(DiagMessage(item_source) + << "<item> within an <overlayable> tag must have a 'name' attribute"); + error = true; + continue; + } + + Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); + if (!maybe_type) { + diag_->Error(DiagMessage(item_source) + << "<item> within an <overlayable> tag must have a 'type' attribute"); + error = true; + continue; + } + + const ResourceType* type = ParseResourceType(maybe_type.value()); + if (type == nullptr) { + diag_->Error(DiagMessage(out_resource->source) + << "invalid resource type '" << maybe_type.value() + << "' in <item> within an <overlayable>"); + error = true; + continue; + } + + // TODO(b/64980941): Mark the symbol as overlayable and allow marking which entity can overlay + // the resource (system/app). + + xml::XmlPullParser::SkipCurrentElement(parser); + } else if (!ShouldIgnoreElement(element_namespace, element_name)) { + diag_->Error(DiagMessage(item_source) << ":" << element_name << ">"); + error = true; + } + } + return !error; } bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser, @@ -1074,15 +1155,13 @@ bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) { return false; } - Maybe<Reference> maybe_key = - ResourceUtils::ParseXmlAttributeName(maybe_name.value()); + Maybe<Reference> maybe_key = ResourceUtils::ParseXmlAttributeName(maybe_name.value()); if (!maybe_key) { - diag_->Error(DiagMessage(source) << "invalid attribute name '" - << maybe_name.value() << "'"); + diag_->Error(DiagMessage(source) << "invalid attribute name '" << maybe_name.value() << "'"); return false; } - TransformReferenceFromNamespace(parser, "", &maybe_key.value()); + ResolvePackage(parser, &maybe_key.value()); maybe_key.value().SetSource(source); std::unique_ptr<Item> value = ParseXml(parser, 0, kAllowRawString); @@ -1091,8 +1170,7 @@ bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) { return false; } - style->entries.push_back( - Style::Entry{std::move(maybe_key.value()), std::move(value)}); + style->entries.push_back(Style::Entry{std::move(maybe_key.value()), std::move(value)}); return true; } @@ -1104,21 +1182,18 @@ bool ResourceParser::ParseStyle(const ResourceType type, xml::XmlPullParser* par Maybe<StringPiece> maybe_parent = xml::FindAttribute(parser, "parent"); if (maybe_parent) { - // If the parent is empty, we don't have a parent, but we also don't infer - // either. + // If the parent is empty, we don't have a parent, but we also don't infer either. if (!maybe_parent.value().empty()) { std::string err_str; - style->parent = ResourceUtils::ParseStyleParentReference( - maybe_parent.value(), &err_str); + style->parent = ResourceUtils::ParseStyleParentReference(maybe_parent.value(), &err_str); if (!style->parent) { diag_->Error(DiagMessage(out_resource->source) << err_str); return false; } - // Transform the namespace prefix to the actual package name, and mark the - // reference as + // Transform the namespace prefix to the actual package name, and mark the reference as // private if appropriate. - TransformReferenceFromNamespace(parser, "", &style->parent.value()); + ResolvePackage(parser, &style->parent.value()); } } else { @@ -1127,8 +1202,7 @@ bool ResourceParser::ParseStyle(const ResourceType type, xml::XmlPullParser* par size_t pos = style_name.find_last_of(u'.'); if (pos != std::string::npos) { style->parent_inferred = true; - style->parent = Reference( - ResourceName({}, ResourceType::kStyle, style_name.substr(0, pos))); + style->parent = Reference(ResourceName({}, ResourceType::kStyle, style_name.substr(0, pos))); } } @@ -1373,7 +1447,7 @@ bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser, } Reference& child_ref = maybe_ref.value(); - xml::TransformReferenceFromNamespace(parser, "", &child_ref); + xml::ResolvePackage(parser, &child_ref); // Create the ParsedResource that will add the attribute to the table. ParsedResource child_resource; diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 5631dc2ad29c..fb9dbd0cd0fd 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -91,6 +91,7 @@ class ResourceParser { bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource); + bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak); diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 144ebd22e105..9a5cd3edb47f 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -22,7 +22,7 @@ #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" -#include "io/StringInputStream.h" +#include "io/StringStream.h" #include "test/Test.h" #include "xml/XmlPullParser.h" @@ -790,4 +790,49 @@ TEST_F(ResourceParserTest, ParsePlatformIndependentNewline) { ASSERT_TRUE(TestParse(R"(<string name="foo">%1$s %n %2$s</string>)")); } +TEST_F(ResourceParserTest, ParseOverlayableTagWithSystemPolicy) { + std::string input = R"( + <overlayable policy="illegal_policy"> + <item type="string" name="foo" /> + </overlayable>)"; + EXPECT_FALSE(TestParse(input)); + + input = R"( + <overlayable policy="system"> + <item name="foo" /> + </overlayable>)"; + EXPECT_FALSE(TestParse(input)); + + input = R"( + <overlayable policy="system"> + <item type="attr" /> + </overlayable>)"; + EXPECT_FALSE(TestParse(input)); + + input = R"( + <overlayable policy="system"> + <item type="bad_type" name="foo" /> + </overlayable>)"; + EXPECT_FALSE(TestParse(input)); + + input = R"(<overlayable policy="system" />)"; + EXPECT_TRUE(TestParse(input)); + + input = R"(<overlayable />)"; + EXPECT_TRUE(TestParse(input)); + + input = R"( + <overlayable policy="system"> + <item type="string" name="foo" /> + <item type="dimen" name="foo" /> + </overlayable>)"; + ASSERT_TRUE(TestParse(input)); + + input = R"( + <overlayable> + <item type="string" name="bar" /> + </overlayable>)"; + ASSERT_TRUE(TestParse(input)); +} + } // namespace aapt diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index ab59560d33a3..0304e21698df 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -546,4 +546,34 @@ Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNam return SearchResult{package, type, entry}; } +std::unique_ptr<ResourceTable> ResourceTable::Clone() const { + std::unique_ptr<ResourceTable> new_table = util::make_unique<ResourceTable>(); + for (const auto& pkg : packages) { + ResourceTablePackage* new_pkg = new_table->CreatePackage(pkg->name, pkg->id); + for (const auto& type : pkg->types) { + ResourceTableType* new_type = new_pkg->FindOrCreateType(type->type); + if (!new_type->id) { + new_type->id = type->id; + new_type->symbol_status = type->symbol_status; + } + + for (const auto& entry : type->entries) { + ResourceEntry* new_entry = new_type->FindOrCreateEntry(entry->name); + if (!new_entry->id) { + new_entry->id = entry->id; + new_entry->symbol_status = entry->symbol_status; + } + + for (const auto& config_value : entry->values) { + ResourceConfigValue* new_value = + new_entry->FindOrCreateValue(config_value->config, config_value->product); + Value* value = config_value->value->Clone(&new_table->string_pool); + new_value->value = std::unique_ptr<Value>(value); + } + } + } + } + return new_table; +} + } // namespace aapt diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 4295d0674774..d5db67e77f51 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -251,6 +251,8 @@ class ResourceTable { ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {}); + std::unique_ptr<ResourceTable> Clone() const; + /** * The string pool used by this resource table. Values that reference strings * must use diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index f193fe0c6593..02ac86c94b46 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -23,12 +23,12 @@ #include "NameMangler.h" #include "SdkConstants.h" -#include "flatten/ResourceTypeExtensions.h" +#include "format/binary/ResourceTypeExtensions.h" #include "util/Files.h" #include "util/Util.h" -using android::StringPiece; -using android::StringPiece16; +using ::android::StringPiece; +using ::android::StringPiece16; namespace aapt { namespace ResourceUtils { @@ -704,8 +704,17 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config } else { if (type != ResourceType::kString && util::StartsWith(str, "res/")) { // This must be a FileReference. - return util::make_unique<FileReference>(dst_pool->MakeRef( - str, StringPool::Context(StringPool::Context::kHighPriority, config))); + std::unique_ptr<FileReference> file_ref = + util::make_unique<FileReference>(dst_pool->MakeRef( + str, StringPool::Context(StringPool::Context::kHighPriority, config))); + if (type == ResourceType::kRaw) { + file_ref->type = ResourceFile::Type::kUnknown; + } else if (util::EndsWith(*file_ref->path, ".xml")) { + file_ref->type = ResourceFile::Type::kBinaryXml; + } else if (util::EndsWith(*file_ref->path, ".png")) { + file_ref->type = ResourceFile::Type::kPng; + } + return std::move(file_ref); } // There are no styles associated with this string, so treat it as a simple string. diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 0fe1a1f9ddcc..a782cd3672d1 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -17,9 +17,12 @@ #include "ResourceValues.h" #include <algorithm> +#include <cinttypes> #include <limits> #include <set> +#include <sstream> +#include "android-base/stringprintf.h" #include "androidfw/ResourceTypes.h" #include "Resource.h" @@ -27,23 +30,43 @@ #include "ValueVisitor.h" #include "util/Util.h" +using ::aapt::text::Printer; +using ::android::StringPiece; +using ::android::base::StringPrintf; + namespace aapt { +void Value::PrettyPrint(Printer* printer) const { + std::ostringstream str_stream; + Print(&str_stream); + printer->Print(str_stream.str()); +} + std::ostream& operator<<(std::ostream& out, const Value& value) { value.Print(&out); return out; } template <typename Derived> -void BaseValue<Derived>::Accept(RawValueVisitor* visitor) { +void BaseValue<Derived>::Accept(ValueVisitor* visitor) { visitor->Visit(static_cast<Derived*>(this)); } template <typename Derived> -void BaseItem<Derived>::Accept(RawValueVisitor* visitor) { +void BaseValue<Derived>::Accept(ConstValueVisitor* visitor) const { + visitor->Visit(static_cast<const Derived*>(this)); +} + +template <typename Derived> +void BaseItem<Derived>::Accept(ValueVisitor* visitor) { visitor->Visit(static_cast<Derived*>(this)); } +template <typename Derived> +void BaseItem<Derived>::Accept(ConstValueVisitor* visitor) const { + visitor->Visit(static_cast<const Derived*>(this)); +} + RawString::RawString(const StringPool::Ref& ref) : value(ref) {} bool RawString::Equals(const Value* value) const { @@ -145,6 +168,49 @@ void Reference::Print(std::ostream* out) const { } } +static void PrettyPrintReferenceImpl(const Reference& ref, bool print_package, Printer* printer) { + switch (ref.reference_type) { + case Reference::Type::kResource: + printer->Print("@"); + break; + + case Reference::Type::kAttribute: + printer->Print("?"); + break; + } + + if (!ref.name && !ref.id) { + printer->Print("null"); + return; + } + + if (ref.private_reference) { + printer->Print("*"); + } + + if (ref.name) { + const ResourceName& name = ref.name.value(); + if (print_package) { + printer->Print(name.to_string()); + } else { + printer->Print(to_string(name.type)); + printer->Print("/"); + printer->Print(name.entry); + } + } else if (ref.id && ref.id.value().is_valid_dynamic()) { + printer->Print(ref.id.value().to_string()); + } +} + +void Reference::PrettyPrint(Printer* printer) const { + PrettyPrintReferenceImpl(*this, true /*print_package*/, printer); +} + +void Reference::PrettyPrint(const StringPiece& package, Printer* printer) const { + const bool print_package = name ? package != name.value().package : true; + PrettyPrintReferenceImpl(*this, print_package, printer); +} + bool Id::Equals(const Value* value) const { return ValueCast<Id>(value) != nullptr; } @@ -155,11 +221,16 @@ bool Id::Flatten(android::Res_value* out) const { return true; } -Id* Id::Clone(StringPool* /*new_pool*/) const { return new Id(*this); } +Id* Id::Clone(StringPool* /*new_pool*/) const { + return new Id(*this); +} -void Id::Print(std::ostream* out) const { *out << "(id)"; } +void Id::Print(std::ostream* out) const { + *out << "(id)"; +} -String::String(const StringPool::Ref& ref) : value(ref) {} +String::String(const StringPool::Ref& ref) : value(ref) { +} bool String::Equals(const Value* value) const { const String* other = ValueCast<String>(value); @@ -208,7 +279,14 @@ void String::Print(std::ostream* out) const { *out << "(string) \"" << *value << "\""; } -StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {} +void String::PrettyPrint(Printer* printer) const { + printer->Print("\""); + printer->Print(*value); + printer->Print("\""); +} + +StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) { +} bool StyledString::Equals(const Value* value) const { const StyledString* other = ValueCast<StyledString>(value); @@ -259,7 +337,8 @@ void StyledString::Print(std::ostream* out) const { } } -FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {} +FileReference::FileReference(const StringPool::Ref& _path) : path(_path) { +} bool FileReference::Equals(const Value* value) const { const FileReference* other = ValueCast<FileReference>(value); @@ -282,6 +361,7 @@ bool FileReference::Flatten(android::Res_value* out_value) const { FileReference* FileReference::Clone(StringPool* new_pool) const { FileReference* fr = new FileReference(new_pool->MakeRef(path)); fr->file = file; + fr->type = type; fr->comment_ = comment_; fr->source_ = source_; return fr; @@ -291,7 +371,8 @@ void FileReference::Print(std::ostream* out) const { *out << "(file) " << *path; } -BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {} +BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) { +} BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) { value.dataType = dataType; @@ -307,7 +388,7 @@ bool BinaryPrimitive::Equals(const Value* value) const { this->value.data == other->value.data; } -bool BinaryPrimitive::Flatten(android::Res_value* out_value) const { +bool BinaryPrimitive::Flatten(::android::Res_value* out_value) const { out_value->dataType = value.dataType; out_value->data = util::HostToDevice32(value.data); return true; @@ -318,32 +399,110 @@ BinaryPrimitive* BinaryPrimitive::Clone(StringPool* /*new_pool*/) const { } void BinaryPrimitive::Print(std::ostream* out) const { + *out << StringPrintf("(primitive) type=0x%02x data=0x%08x", value.dataType, value.data); +} + +static std::string ComplexToString(uint32_t complex_value, bool fraction) { + using ::android::Res_value; + + constexpr std::array<int, 4> kRadixShifts = {{23, 16, 8, 0}}; + + // Determine the radix that was used. + const uint32_t radix = + (complex_value >> Res_value::COMPLEX_RADIX_SHIFT) & Res_value::COMPLEX_RADIX_MASK; + const uint64_t mantissa = uint64_t{(complex_value >> Res_value::COMPLEX_MANTISSA_SHIFT) & + Res_value::COMPLEX_MANTISSA_MASK} + << kRadixShifts[radix]; + const float value = mantissa * (1.0f / (1 << 23)); + + std::string str = StringPrintf("%f", value); + + const int unit_type = + (complex_value >> Res_value::COMPLEX_UNIT_SHIFT) & Res_value::COMPLEX_UNIT_MASK; + if (fraction) { + switch (unit_type) { + case Res_value::COMPLEX_UNIT_FRACTION: + str += "%"; + break; + case Res_value::COMPLEX_UNIT_FRACTION_PARENT: + str += "%p"; + break; + default: + str += "???"; + break; + } + } else { + switch (unit_type) { + case Res_value::COMPLEX_UNIT_PX: + str += "px"; + break; + case Res_value::COMPLEX_UNIT_DIP: + str += "dp"; + break; + case Res_value::COMPLEX_UNIT_SP: + str += "sp"; + break; + case Res_value::COMPLEX_UNIT_PT: + str += "pt"; + break; + case Res_value::COMPLEX_UNIT_IN: + str += "in"; + break; + case Res_value::COMPLEX_UNIT_MM: + str += "mm"; + break; + default: + str += "???"; + break; + } + } + return str; +} + +void BinaryPrimitive::PrettyPrint(Printer* printer) const { + using ::android::Res_value; switch (value.dataType) { - case android::Res_value::TYPE_NULL: - if (value.data == android::Res_value::DATA_NULL_EMPTY) { - *out << "(empty)"; + case Res_value::TYPE_NULL: + if (value.data == Res_value::DATA_NULL_EMPTY) { + printer->Print("@empty"); } else { - *out << "(null)"; + printer->Print("@null"); } break; - case android::Res_value::TYPE_INT_DEC: - *out << "(integer) " << static_cast<int32_t>(value.data); + + case Res_value::TYPE_INT_DEC: + printer->Print(StringPrintf("%" PRIi32, static_cast<int32_t>(value.data))); + break; + + case Res_value::TYPE_INT_HEX: + printer->Print(StringPrintf("0x%08x", value.data)); + break; + + case Res_value::TYPE_INT_BOOLEAN: + printer->Print(value.data != 0 ? "true" : "false"); + break; + + case Res_value::TYPE_INT_COLOR_ARGB8: + case Res_value::TYPE_INT_COLOR_RGB8: + case Res_value::TYPE_INT_COLOR_ARGB4: + case Res_value::TYPE_INT_COLOR_RGB4: + printer->Print(StringPrintf("#%08x", value.data)); break; - case android::Res_value::TYPE_INT_HEX: - *out << "(integer) 0x" << std::hex << value.data << std::dec; + + case Res_value::TYPE_FLOAT: + printer->Print(StringPrintf("%g", *reinterpret_cast<const float*>(&value.data))); break; - case android::Res_value::TYPE_INT_BOOLEAN: - *out << "(boolean) " << (value.data != 0 ? "true" : "false"); + + case Res_value::TYPE_DIMENSION: + printer->Print(ComplexToString(value.data, false /*fraction*/)); break; - case android::Res_value::TYPE_INT_COLOR_ARGB8: - case android::Res_value::TYPE_INT_COLOR_RGB8: - case android::Res_value::TYPE_INT_COLOR_ARGB4: - case android::Res_value::TYPE_INT_COLOR_RGB4: - *out << "(color) #" << std::hex << value.data << std::dec; + + case Res_value::TYPE_FRACTION: + printer->Print(ComplexToString(value.data, true /*fraction*/)); break; + default: - *out << "(unknown 0x" << std::hex << (int)value.dataType << ") 0x" - << std::hex << value.data << std::dec; + printer->Print(StringPrintf("(unknown 0x%02x) 0x%08x", value.dataType, value.data)); break; } } @@ -413,107 +572,107 @@ Attribute* Attribute::Clone(StringPool* /*new_pool*/) const { return new Attribute(*this); } -void Attribute::PrintMask(std::ostream* out) const { +std::string Attribute::MaskString() const { if (type_mask == android::ResTable_map::TYPE_ANY) { - *out << "any"; - return; + return "any"; } + std::ostringstream out; bool set = false; if ((type_mask & android::ResTable_map::TYPE_REFERENCE) != 0) { if (!set) { set = true; } else { - *out << "|"; + out << "|"; } - *out << "reference"; + out << "reference"; } if ((type_mask & android::ResTable_map::TYPE_STRING) != 0) { if (!set) { set = true; } else { - *out << "|"; + out << "|"; } - *out << "string"; + out << "string"; } if ((type_mask & android::ResTable_map::TYPE_INTEGER) != 0) { if (!set) { set = true; } else { - *out << "|"; + out << "|"; } - *out << "integer"; + out << "integer"; } if ((type_mask & android::ResTable_map::TYPE_BOOLEAN) != 0) { if (!set) { set = true; } else { - *out << "|"; + out << "|"; } - *out << "boolean"; + out << "boolean"; } if ((type_mask & android::ResTable_map::TYPE_COLOR) != 0) { if (!set) { set = true; } else { - *out << "|"; + out << "|"; } - *out << "color"; + out << "color"; } if ((type_mask & android::ResTable_map::TYPE_FLOAT) != 0) { if (!set) { set = true; } else { - *out << "|"; + out << "|"; } - *out << "float"; + out << "float"; } if ((type_mask & android::ResTable_map::TYPE_DIMENSION) != 0) { if (!set) { set = true; } else { - *out << "|"; + out << "|"; } - *out << "dimension"; + out << "dimension"; } if ((type_mask & android::ResTable_map::TYPE_FRACTION) != 0) { if (!set) { set = true; } else { - *out << "|"; + out << "|"; } - *out << "fraction"; + out << "fraction"; } if ((type_mask & android::ResTable_map::TYPE_ENUM) != 0) { if (!set) { set = true; } else { - *out << "|"; + out << "|"; } - *out << "enum"; + out << "enum"; } if ((type_mask & android::ResTable_map::TYPE_FLAGS) != 0) { if (!set) { set = true; } else { - *out << "|"; + out << "|"; } - *out << "flags"; + out << "flags"; } + return out.str(); } void Attribute::Print(std::ostream* out) const { - *out << "(attr) "; - PrintMask(out); + *out << "(attr) " << MaskString(); if (!symbols.empty()) { *out << " [" << util::Joiner(symbols, ", ") << "]"; diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index 275864bbcd3e..b2ec8bdd7c77 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -29,11 +29,13 @@ #include "Resource.h" #include "StringPool.h" #include "io/File.h" +#include "text/Printer.h" #include "util/Maybe.h" namespace aapt { -struct RawValueVisitor; +class ValueVisitor; +class ConstValueVisitor; // A resource value. This is an all-encompassing representation // of Item and Map and their subclasses. The way to do @@ -45,36 +47,58 @@ class Value { virtual ~Value() = default; // Whether this value is weak and can be overridden without warning or error. Default is false. - bool IsWeak() const { return weak_; } + bool IsWeak() const { + return weak_; + } - void SetWeak(bool val) { weak_ = val; } + void SetWeak(bool val) { + weak_ = val; + } - // Whether the value is marked as translatable. - // This does not persist when flattened. + // Whether the value is marked as translatable. This does not persist when flattened to binary. // It is only used during compilation phase. - void SetTranslatable(bool val) { translatable_ = val; } + void SetTranslatable(bool val) { + translatable_ = val; + } // Default true. - bool IsTranslatable() const { return translatable_; } + bool IsTranslatable() const { + return translatable_; + } // Returns the source where this value was defined. - const Source& GetSource() const { return source_; } + const Source& GetSource() const { + return source_; + } - void SetSource(const Source& source) { source_ = source; } + void SetSource(const Source& source) { + source_ = source; + } - void SetSource(Source&& source) { source_ = std::move(source); } + void SetSource(Source&& source) { + source_ = std::move(source); + } // Returns the comment that was associated with this resource. - const std::string& GetComment() const { return comment_; } + const std::string& GetComment() const { + return comment_; + } - void SetComment(const android::StringPiece& str) { comment_ = str.to_string(); } + void SetComment(const android::StringPiece& str) { + comment_ = str.to_string(); + } - void SetComment(std::string&& str) { comment_ = std::move(str); } + void SetComment(std::string&& str) { + comment_ = std::move(str); + } virtual bool Equals(const Value* value) const = 0; // Calls the appropriate overload of ValueVisitor. - virtual void Accept(RawValueVisitor* visitor) = 0; + virtual void Accept(ValueVisitor* visitor) = 0; + + // Calls the appropriate overload of ConstValueVisitor. + virtual void Accept(ConstValueVisitor* visitor) const = 0; // Clone the value. `new_pool` is the new StringPool that // any resources with strings should use when copying their string. @@ -83,6 +107,10 @@ class Value { // Human readable printout of this value. virtual void Print(std::ostream* out) const = 0; + // Human readable printout of this value that may omit some information for the sake + // of brevity and readability. Default implementation just calls Print(). + virtual void PrettyPrint(text::Printer* printer) const; + friend std::ostream& operator<<(std::ostream& out, const Value& value); protected: @@ -95,7 +123,8 @@ class Value { // Inherit from this to get visitor accepting implementations for free. template <typename Derived> struct BaseValue : public Value { - void Accept(RawValueVisitor* visitor) override; + void Accept(ValueVisitor* visitor) override; + void Accept(ConstValueVisitor* visitor) const override; }; // A resource item with a single value. This maps to android::ResTable_entry. @@ -111,7 +140,8 @@ struct Item : public Value { // Inherit from this to get visitor accepting implementations for free. template <typename Derived> struct BaseItem : public Item { - void Accept(RawValueVisitor* visitor) override; + void Accept(ValueVisitor* visitor) override; + void Accept(ConstValueVisitor* visitor) const override; }; // A reference to another resource. This maps to android::Res_value::TYPE_REFERENCE. @@ -137,6 +167,10 @@ struct Reference : public BaseItem<Reference> { bool Flatten(android::Res_value* out_value) const override; Reference* Clone(StringPool* new_pool) const override; void Print(std::ostream* out) const override; + void PrettyPrint(text::Printer* printer) const override; + + // Prints the reference without a package name if the package name matches the one given. + void PrettyPrint(const android::StringPiece& package, text::Printer* printer) const; }; bool operator<(const Reference&, const Reference&); @@ -144,7 +178,10 @@ bool operator==(const Reference&, const Reference&); // An ID resource. Has no real value, just a place holder. struct Id : public BaseItem<Id> { - Id() { weak_ = true; } + Id() { + weak_ = true; + } + bool Equals(const Value* value) const override; bool Flatten(android::Res_value* out) const override; Id* Clone(StringPool* new_pool) const override; @@ -196,6 +233,7 @@ struct String : public BaseItem<String> { bool Flatten(android::Res_value* out_value) const override; String* Clone(StringPool* new_pool) const override; void Print(std::ostream* out) const override; + void PrettyPrint(text::Printer* printer) const override; }; struct StyledString : public BaseItem<StyledString> { @@ -221,6 +259,10 @@ struct FileReference : public BaseItem<FileReference> { // This field is NOT persisted in any format. It is transient. io::IFile* file = nullptr; + // FileType of the file pointed to by `file`. This is used to know how to inflate the file, + // or if to inflate at all (just copy). + ResourceFile::Type type = ResourceFile::Type::kUnknown; + FileReference() = default; explicit FileReference(const StringPool::Ref& path); @@ -242,6 +284,7 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> { bool Flatten(android::Res_value* out_value) const override; BinaryPrimitive* Clone(StringPool* new_pool) const override; void Print(std::ostream* out) const override; + void PrettyPrint(text::Printer* printer) const override; }; struct Attribute : public BaseValue<Attribute> { @@ -262,7 +305,7 @@ struct Attribute : public BaseValue<Attribute> { bool Equals(const Value* value) const override; Attribute* Clone(StringPool* new_pool) const override; - void PrintMask(std::ostream* out) const; + std::string MaskString() const; void Print(std::ostream* out) const override; bool Matches(const Item& item, DiagMessage* out_msg = nullptr) const; }; diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index 71f33b0853ad..7e7c86d53d24 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -14,77 +14,80 @@ * limitations under the License. */ -// Keep proto2 syntax because we require the distinction between fields that -// are set and unset. -syntax = "proto2"; +syntax = "proto3"; -option java_package = "com.android.aapt"; -option optimize_for = LITE_RUNTIME; +import "frameworks/base/tools/aapt2/Configuration.proto"; package aapt.pb; -// A configuration description that wraps the binary form of the C++ class -// aapt::ConfigDescription, with an added product definition. -// TODO(adamlesinski): Flesh this out to be represented in proto. -message ConfigDescription { - optional bytes data = 1; - optional string product = 2; -} +option java_package = "com.android.aapt"; +option optimize_for = LITE_RUNTIME; // A string pool that wraps the binary form of the C++ class android::ResStringPool. message StringPool { - optional bytes data = 1; + bytes data = 1; } // The position of a declared entity within a file. message SourcePosition { - optional uint32 line_number = 1; - optional uint32 column_number = 2; + uint32 line_number = 1; + uint32 column_number = 2; } // Developer friendly source file information for an entity in the resource table. message Source { // The index of the string path within the source string pool of a ResourceTable. - optional uint32 path_idx = 1; - optional SourcePosition position = 2; + uint32 path_idx = 1; + SourcePosition position = 2; } // Top level message representing a resource table. message ResourceTable { // The string pool containing source paths referenced throughout the resource table. This does // not end up in the final binary ARSC file. - optional StringPool source_pool = 1; + StringPool source_pool = 1; // Resource definitions corresponding to an Android package. repeated Package package = 2; } +// A package ID in the range [0x00, 0xff]. +message PackageId { + uint32 id = 1; +} + // Defines resources for an Android package. message Package { // The package ID of this package, in the range [0x00, 0xff]. - // The ID 0x00 is reserved for shared libraries, or when the ID is assigned at run-time. - // The ID 0x01 is reserved for the 'android' package (framework). - // The ID range [0x02, 0x7f) is reserved for auto-assignment to shared libraries at run-time. - // The ID 0x7f is reserved for the application package. - // IDs > 0x7f are reserved for the application as well and are treated as feature splits. - optional uint32 package_id = 1; + // - ID 0x00 is reserved for shared libraries, or when the ID is assigned at run-time. + // - ID 0x01 is reserved for the 'android' package (framework). + // - ID range [0x02, 0x7f) is reserved for auto-assignment to shared libraries at run-time. + // - ID 0x7f is reserved for the application package. + // - IDs > 0x7f are reserved for the application as well and are treated as feature splits. + // This may not be set if no ID was assigned. + PackageId package_id = 1; // The Java compatible Android package name of the app. - optional string package_name = 2; + string package_name = 2; // The series of types defined by the package. repeated Type type = 3; } +// A type ID in the range [0x01, 0xff]. +message TypeId { + uint32 id = 1; +} + // A set of resources grouped under a common type. Such types include string, layout, xml, dimen, // attr, etc. This maps to the second part of a resource identifier in Java (R.type.entry). message Type { - // The ID of the type. This may be 0, which indicates no ID is set. - optional uint32 id = 1; + // The ID of the type. This may not be set if no ID was assigned. + TypeId type_id = 1; // The name of the type. This corresponds to the 'type' part of a full resource name of the form // package:type/entry. The set of legal type names is listed in Resource.cpp. - optional string name = 2; + string name = 2; // The entries defined for this type. repeated Entry entry = 3; @@ -112,17 +115,22 @@ message SymbolStatus { PUBLIC = 2; } - optional Visibility visibility = 1; + Visibility visibility = 1; // The path at which this entry's visibility was defined (eg. public.xml). - optional Source source = 2; + Source source = 2; // The comment associated with the <public> tag. - optional string comment = 3; + string comment = 3; // Whether the symbol can be merged into another resource table without there being an existing // definition to override. Used for overlays and set to true when <add-resource> is specified. - optional bool allow_new = 4; + bool allow_new = 4; +} + +// An entry ID in the range [0x0000, 0xffff]. +message EntryId { + uint32 id = 1; } // An entry declaration. An entry has a full resource ID that is the combination of package ID, @@ -132,14 +140,15 @@ message Entry { // The ID of this entry. Together with the package ID and type ID, this forms a full resource ID // of the form 0xPPTTEEEE, where PP is the package ID, TT is the type ID, and EEEE is the entry // ID. - optional uint32 id = 1; + // This may not be set if no ID was assigned. + EntryId entry_id = 1; // The name of this entry. This corresponds to the 'entry' part of a full resource name of the // form package:type/entry. - optional string name = 2; + string name = 2; // The symbol status of this entry, which includes visibility information. - optional SymbolStatus symbol_status = 3; + SymbolStatus symbol_status = 3; // The set of values defined for this entry, each corresponding to a different // configuration/variant. @@ -148,50 +157,54 @@ message Entry { // A Configuration/Value pair. message ConfigValue { - optional ConfigDescription config = 1; - optional Value value = 2; + Configuration config = 1; + Value value = 2; } // The generic meta-data for every value in a resource table. message Value { // Where the value was defined. - optional Source source = 1; + Source source = 1; // Any comment associated with the value. - optional string comment = 2; + string comment = 2; // Whether the value can be overridden. - optional bool weak = 3; + bool weak = 3; - // If the value is an Item, this is set. - optional Item item = 4; - - // If the value is a CompoundValue, this is set. - optional CompoundValue compound_value = 5; + // The value is either an Item or a CompoundValue. + oneof value { + Item item = 4; + CompoundValue compound_value = 5; + } } // An Item is an abstract type. It represents a value that can appear inline in many places, such // as XML attribute values or on the right hand side of style attribute definitions. The concrete // type is one of the types below. Only one can be set. message Item { - optional Reference ref = 1; - optional String str = 2; - optional RawString raw_str = 3; - optional StyledString styled_str = 4; - optional FileReference file = 5; - optional Id id = 6; - optional Primitive prim = 7; + oneof value { + Reference ref = 1; + String str = 2; + RawString raw_str = 3; + StyledString styled_str = 4; + FileReference file = 5; + Id id = 6; + Primitive prim = 7; + } } // A CompoundValue is an abstract type. It represents a value that is a made of other values. // These can only usually appear as top-level resources. The concrete type is one of the types // below. Only one can be set. message CompoundValue { - optional Attribute attr = 1; - optional Style style = 2; - optional Styleable styleable = 3; - optional Array array = 4; - optional Plural plural = 5; + oneof value { + Attribute attr = 1; + Style style = 2; + Styleable styleable = 3; + Array array = 4; + Plural plural = 5; + } } // A value that is a reference to another resource. This reference can be by name or resource ID. @@ -204,16 +217,16 @@ message Reference { ATTRIBUTE = 1; } - optional Type type = 1; + Type type = 1; - // The resource ID (0xPPTTEEEE) of the resource being referred. - optional uint32 id = 2; + // The resource ID (0xPPTTEEEE) of the resource being referred. This is optional. + uint32 id = 2; - // The optional resource name. - optional string name = 3; + // The name of the resource being referred. This is optional if the resource ID is set. + string name = 3; // Whether this reference is referencing a private resource (@*package:type/entry). - optional bool private = 4; + bool private = 4; } // A value that represents an ID. This is just a placeholder, as ID values are used to occupy a @@ -223,32 +236,32 @@ message Id { // A value that is a string. message String { - optional string value = 1; + string value = 1; } // A value that is a raw string, which is unescaped/uninterpreted. This is typically used to // represent the value of a style attribute before the attribute is compiled and the set of // allowed values is known. message RawString { - optional string value = 1; + string value = 1; } // A string with styling information, like html tags that specify boldness, italics, etc. message StyledString { // The raw text of the string. - optional string value = 1; + string value = 1; // A Span marks a region of the string text that is styled. message Span { // The name of the tag, and its attributes, encoded as follows: // tag_name;attr1=value1;attr2=value2;[...] - optional string tag = 1; + string tag = 1; // The first character position this span applies to, in UTF-16 offset. - optional uint32 first_char = 2; + uint32 first_char = 2; // The last character position this span applies to, in UTF-16 offset. - optional uint32 last_char = 3; + uint32 last_char = 3; } repeated Span span = 2; @@ -256,15 +269,26 @@ message StyledString { // A value that is a reference to an external entity, like an XML file or a PNG. message FileReference { + enum Type { + UNKNOWN = 0; + PNG = 1; + BINARY_XML = 2; + PROTO_XML = 3; + } + // Path to a file within the APK (typically res/type-config/entry.ext). - optional string path = 1; + string path = 1; + + // The type of file this path points to. For UAM bundle, this cannot be + // BINARY_XML. + Type type = 2; } // A value that represents a primitive data type (float, int, boolean, etc.). // Corresponds to the fields (type/data) of the C struct android::Res_value. message Primitive { - optional uint32 type = 1; - optional uint32 data = 2; + uint32 type = 1; + uint32 data = 2; } // A value that represents an XML attribute and what values it accepts. @@ -272,21 +296,22 @@ message Attribute { // A Symbol used to represent an enum or a flag. message Symbol { // Where the enum/flag item was defined. - optional Source source = 1; + Source source = 1; // Any comments associated with the enum or flag. - optional string comment = 2; + string comment = 2; // The name of the enum/flag as a reference. Enums/flag items are generated as ID resource // values. - optional Reference name = 3; + Reference name = 3; // The value of the enum/flag. - optional uint32 value = 4; + uint32 value = 4; } // Bitmask of formats allowed for an attribute. enum FormatFlags { + NONE = 0x0; // Proto3 requires a default of 0. ANY = 0x0000ffff; // Allows any type except ENUM and FLAGS. REFERENCE = 0x01; // Allows Reference values. STRING = 0x02; // Allows String/StyledString values. @@ -304,15 +329,15 @@ message Attribute { // A bitmask of types that this XML attribute accepts. Corresponds to the flags in the // enum FormatFlags. - optional uint32 format_flags = 1; + uint32 format_flags = 1; // The smallest integer allowed for this XML attribute. Only makes sense if the format includes // FormatFlags::INTEGER. - optional int32 min_int = 2; + int32 min_int = 2; // The largest integer allowed for this XML attribute. Only makes sense if the format includes // FormatFlags::INTEGER. - optional int32 max_int = 3; + int32 max_int = 3; // The set of enums/flags defined in this attribute. Only makes sense if the format includes // either FormatFlags::ENUM or FormatFlags::FLAGS. Having both is an error. @@ -324,23 +349,23 @@ message Style { // An XML attribute/value pair defined in the style. message Entry { // Where the entry was defined. - optional Source source = 1; + Source source = 1; // Any comments associated with the entry. - optional string comment = 2; + string comment = 2; // A reference to the XML attribute. - optional Reference key = 3; + Reference key = 3; // The Item defined for this XML attribute. - optional Item item = 4; + Item item = 4; } // The optinal style from which this style inherits attributes. - optional Reference parent = 1; + Reference parent = 1; // The source file information of the parent inheritance declaration. - optional Source parent_source = 2; + Source parent_source = 2; // The set of XML attribute/value pairs for this style. repeated Entry entry = 3; @@ -352,13 +377,13 @@ message Styleable { // An attribute defined for this styleable. message Entry { // Where the attribute was defined within the <declare-styleable> block. - optional Source source = 1; + Source source = 1; // Any comments associated with the declaration. - optional string comment = 2; + string comment = 2; // The reference to the attribute. - optional Reference attr = 3; + Reference attr = 3; } // The set of attribute declarations. @@ -370,13 +395,13 @@ message Array { // A single element of the array. message Element { // Where the element was defined. - optional Source source = 1; + Source source = 1; // Any comments associated with the element. - optional string comment = 2; + string comment = 2; // The value assigned to this element. - optional Item item = 3; + Item item = 3; } // The list of array elements. @@ -398,16 +423,16 @@ message Plural { // The plural value for a given arity. message Entry { // Where the plural was defined. - optional Source source = 1; + Source source = 1; // Any comments associated with the plural. - optional string comment = 2; + string comment = 2; // The arity of the plural. - optional Arity arity = 3; + Arity arity = 3; // The value assigned to this plural. - optional Item item = 4; + Item item = 4; } // The set of arity/plural mappings. @@ -417,14 +442,13 @@ message Plural { // Defines an abstract XmlNode that must be either an XmlElement, or // a text node represented by a string. message XmlNode { - // If set, this node is an element/tag. - optional XmlElement element = 1; - - // If set, this node is a chunk of text. - optional string text = 2; + oneof node { + XmlElement element = 1; + string text = 2; + } // Source line and column info. - optional SourcePosition source = 3; + SourcePosition source = 3; } // An <element> in an XML document. @@ -433,10 +457,10 @@ message XmlElement { repeated XmlNamespace namespace_declaration = 1; // The namespace URI of this element. - optional string namespace_uri = 2; + string namespace_uri = 2; // The name of this element. - optional string name = 3; + string name = 3; // The attributes of this element. repeated XmlAttribute attribute = 4; @@ -447,25 +471,25 @@ message XmlElement { // A namespace declaration on an XmlElement (xmlns:android="http://..."). message XmlNamespace { - optional string prefix = 1; - optional string uri = 2; + string prefix = 1; + string uri = 2; // Source line and column info. - optional SourcePosition source = 3; + SourcePosition source = 3; } // An attribute defined on an XmlElement (android:text="..."). message XmlAttribute { - optional string namespace_uri = 1; - optional string name = 2; - optional string value = 3; + string namespace_uri = 1; + string name = 2; + string value = 3; // Source line and column info. - optional SourcePosition source = 4; + SourcePosition source = 4; - // The resource ID (0xPPTTEEEE) of the attribute. - optional uint32 resource_id = 5; + // The optional resource ID (0xPPTTEEEE) of the attribute. + uint32 resource_id = 5; - // The interpreted/compiled version of the `value` string. - optional Item compiled_item = 6; + // The optional interpreted/compiled version of the `value` string. + Item compiled_item = 6; } diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto index 31179174b843..520b242ee509 100644 --- a/tools/aapt2/ResourcesInternal.proto +++ b/tools/aapt2/ResourcesInternal.proto @@ -14,39 +14,40 @@ * limitations under the License. */ -syntax = "proto2"; - -option java_package = "android.aapt.pb.internal"; -option optimize_for = LITE_RUNTIME; +syntax = "proto3"; +import "frameworks/base/tools/aapt2/Configuration.proto"; import "frameworks/base/tools/aapt2/Resources.proto"; package aapt.pb.internal; +option java_package = "android.aapt.pb.internal"; +option optimize_for = LITE_RUNTIME; + // The top level message representing an external resource file (layout XML, PNG, etc). // This is used to represent a compiled file before it is linked. Only useful to aapt2. message CompiledFile { message Symbol { // The name of the symbol (in the form package:type/name). - optional string resource_name = 1; + string resource_name = 1; // The position in the file at which this symbol is defined. For debug use. - optional aapt.pb.SourcePosition source = 2; + aapt.pb.SourcePosition source = 2; } // The name of the resource (in the form package:type/name). - optional string resource_name = 1; + string resource_name = 1; // The configuration for which the resource is defined. - optional aapt.pb.ConfigDescription config = 2; + aapt.pb.Configuration config = 2; + + // The type of the file. + aapt.pb.FileReference.Type type = 3; // The filesystem path to where the source file originated. // Mainly used to display helpful error messages. - optional string source_path = 3; + string source_path = 4; // Any symbols this file auto-generates/exports (eg. @+id/foo in an XML file). - repeated Symbol exported_symbol = 4; - - // If this is a compiled XML file, this is the root node. - optional aapt.pb.XmlNode xml_root = 5; + repeated Symbol exported_symbol = 5; } diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp index 041cb4fa96cd..8ebde752bc4b 100644 --- a/tools/aapt2/SdkConstants.cpp +++ b/tools/aapt2/SdkConstants.cpp @@ -25,8 +25,8 @@ using android::StringPiece; namespace aapt { -static const char* sDevelopmentSdkCodeName = "O"; -static ApiVersion sDevelopmentSdkLevel = 26; +static const char* sDevelopmentSdkCodeName = "P"; +static ApiVersion sDevelopmentSdkLevel = 28; static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = { {0x021c, 1}, @@ -53,6 +53,7 @@ static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = { {0x0527, SDK_NOUGAT}, {0x0530, SDK_NOUGAT_MR1}, {0x0568, SDK_O}, + {0x056d, SDK_O_MR1}, }; static bool less_entry_id(const std::pair<uint16_t, ApiVersion>& p, uint16_t entryId) { @@ -70,680 +71,6 @@ ApiVersion FindAttributeSdkLevel(const ResourceId& id) { return iter->second; } -static const std::unordered_map<std::string, ApiVersion> sAttrMap = { - {"marqueeRepeatLimit", 2}, - {"windowNoDisplay", 3}, - {"backgroundDimEnabled", 3}, - {"inputType", 3}, - {"isDefault", 3}, - {"windowDisablePreview", 3}, - {"privateImeOptions", 3}, - {"editorExtras", 3}, - {"settingsActivity", 3}, - {"fastScrollEnabled", 3}, - {"reqTouchScreen", 3}, - {"reqKeyboardType", 3}, - {"reqHardKeyboard", 3}, - {"reqNavigation", 3}, - {"windowSoftInputMode", 3}, - {"imeFullscreenBackground", 3}, - {"noHistory", 3}, - {"headerDividersEnabled", 3}, - {"footerDividersEnabled", 3}, - {"candidatesTextStyleSpans", 3}, - {"smoothScrollbar", 3}, - {"reqFiveWayNav", 3}, - {"keyBackground", 3}, - {"keyTextSize", 3}, - {"labelTextSize", 3}, - {"keyTextColor", 3}, - {"keyPreviewLayout", 3}, - {"keyPreviewOffset", 3}, - {"keyPreviewHeight", 3}, - {"verticalCorrection", 3}, - {"popupLayout", 3}, - {"state_long_pressable", 3}, - {"keyWidth", 3}, - {"keyHeight", 3}, - {"horizontalGap", 3}, - {"verticalGap", 3}, - {"rowEdgeFlags", 3}, - {"codes", 3}, - {"popupKeyboard", 3}, - {"popupCharacters", 3}, - {"keyEdgeFlags", 3}, - {"isModifier", 3}, - {"isSticky", 3}, - {"isRepeatable", 3}, - {"iconPreview", 3}, - {"keyOutputText", 3}, - {"keyLabel", 3}, - {"keyIcon", 3}, - {"keyboardMode", 3}, - {"isScrollContainer", 3}, - {"fillEnabled", 3}, - {"updatePeriodMillis", 3}, - {"initialLayout", 3}, - {"voiceSearchMode", 3}, - {"voiceLanguageModel", 3}, - {"voicePromptText", 3}, - {"voiceLanguage", 3}, - {"voiceMaxResults", 3}, - {"bottomOffset", 3}, - {"topOffset", 3}, - {"allowSingleTap", 3}, - {"handle", 3}, - {"content", 3}, - {"animateOnClick", 3}, - {"configure", 3}, - {"hapticFeedbackEnabled", 3}, - {"innerRadius", 3}, - {"thickness", 3}, - {"sharedUserLabel", 3}, - {"dropDownWidth", 3}, - {"dropDownAnchor", 3}, - {"imeOptions", 3}, - {"imeActionLabel", 3}, - {"imeActionId", 3}, - {"imeExtractEnterAnimation", 3}, - {"imeExtractExitAnimation", 3}, - {"tension", 4}, - {"extraTension", 4}, - {"anyDensity", 4}, - {"searchSuggestThreshold", 4}, - {"includeInGlobalSearch", 4}, - {"onClick", 4}, - {"targetSdkVersion", 4}, - {"maxSdkVersion", 4}, - {"testOnly", 4}, - {"contentDescription", 4}, - {"gestureStrokeWidth", 4}, - {"gestureColor", 4}, - {"uncertainGestureColor", 4}, - {"fadeOffset", 4}, - {"fadeDuration", 4}, - {"gestureStrokeType", 4}, - {"gestureStrokeLengthThreshold", 4}, - {"gestureStrokeSquarenessThreshold", 4}, - {"gestureStrokeAngleThreshold", 4}, - {"eventsInterceptionEnabled", 4}, - {"fadeEnabled", 4}, - {"backupAgent", 4}, - {"allowBackup", 4}, - {"glEsVersion", 4}, - {"queryAfterZeroResults", 4}, - {"dropDownHeight", 4}, - {"smallScreens", 4}, - {"normalScreens", 4}, - {"largeScreens", 4}, - {"progressBarStyleInverse", 4}, - {"progressBarStyleSmallInverse", 4}, - {"progressBarStyleLargeInverse", 4}, - {"searchSettingsDescription", 4}, - {"textColorPrimaryInverseDisableOnly", 4}, - {"autoUrlDetect", 4}, - {"resizeable", 4}, - {"required", 5}, - {"accountType", 5}, - {"contentAuthority", 5}, - {"userVisible", 5}, - {"windowShowWallpaper", 5}, - {"wallpaperOpenEnterAnimation", 5}, - {"wallpaperOpenExitAnimation", 5}, - {"wallpaperCloseEnterAnimation", 5}, - {"wallpaperCloseExitAnimation", 5}, - {"wallpaperIntraOpenEnterAnimation", 5}, - {"wallpaperIntraOpenExitAnimation", 5}, - {"wallpaperIntraCloseEnterAnimation", 5}, - {"wallpaperIntraCloseExitAnimation", 5}, - {"supportsUploading", 5}, - {"killAfterRestore", 5}, - {"restoreNeedsApplication", 5}, - {"smallIcon", 5}, - {"accountPreferences", 5}, - {"textAppearanceSearchResultSubtitle", 5}, - {"textAppearanceSearchResultTitle", 5}, - {"summaryColumn", 5}, - {"detailColumn", 5}, - {"detailSocialSummary", 5}, - {"thumbnail", 5}, - {"detachWallpaper", 5}, - {"finishOnCloseSystemDialogs", 5}, - {"scrollbarFadeDuration", 5}, - {"scrollbarDefaultDelayBeforeFade", 5}, - {"fadeScrollbars", 5}, - {"colorBackgroundCacheHint", 5}, - {"dropDownHorizontalOffset", 5}, - {"dropDownVerticalOffset", 5}, - {"quickContactBadgeStyleWindowSmall", 6}, - {"quickContactBadgeStyleWindowMedium", 6}, - {"quickContactBadgeStyleWindowLarge", 6}, - {"quickContactBadgeStyleSmallWindowSmall", 6}, - {"quickContactBadgeStyleSmallWindowMedium", 6}, - {"quickContactBadgeStyleSmallWindowLarge", 6}, - {"author", 7}, - {"autoStart", 7}, - {"expandableListViewWhiteStyle", 8}, - {"installLocation", 8}, - {"vmSafeMode", 8}, - {"webTextViewStyle", 8}, - {"restoreAnyVersion", 8}, - {"tabStripLeft", 8}, - {"tabStripRight", 8}, - {"tabStripEnabled", 8}, - {"logo", 9}, - {"xlargeScreens", 9}, - {"immersive", 9}, - {"overScrollMode", 9}, - {"overScrollHeader", 9}, - {"overScrollFooter", 9}, - {"filterTouchesWhenObscured", 9}, - {"textSelectHandleLeft", 9}, - {"textSelectHandleRight", 9}, - {"textSelectHandle", 9}, - {"textSelectHandleWindowStyle", 9}, - {"popupAnimationStyle", 9}, - {"screenSize", 9}, - {"screenDensity", 9}, - {"allContactsName", 11}, - {"windowActionBar", 11}, - {"actionBarStyle", 11}, - {"navigationMode", 11}, - {"displayOptions", 11}, - {"subtitle", 11}, - {"customNavigationLayout", 11}, - {"hardwareAccelerated", 11}, - {"measureWithLargestChild", 11}, - {"animateFirstView", 11}, - {"dropDownSpinnerStyle", 11}, - {"actionDropDownStyle", 11}, - {"actionButtonStyle", 11}, - {"showAsAction", 11}, - {"previewImage", 11}, - {"actionModeBackground", 11}, - {"actionModeCloseDrawable", 11}, - {"windowActionModeOverlay", 11}, - {"valueFrom", 11}, - {"valueTo", 11}, - {"valueType", 11}, - {"propertyName", 11}, - {"ordering", 11}, - {"fragment", 11}, - {"windowActionBarOverlay", 11}, - {"fragmentOpenEnterAnimation", 11}, - {"fragmentOpenExitAnimation", 11}, - {"fragmentCloseEnterAnimation", 11}, - {"fragmentCloseExitAnimation", 11}, - {"fragmentFadeEnterAnimation", 11}, - {"fragmentFadeExitAnimation", 11}, - {"actionBarSize", 11}, - {"imeSubtypeLocale", 11}, - {"imeSubtypeMode", 11}, - {"imeSubtypeExtraValue", 11}, - {"splitMotionEvents", 11}, - {"listChoiceBackgroundIndicator", 11}, - {"spinnerMode", 11}, - {"animateLayoutChanges", 11}, - {"actionBarTabStyle", 11}, - {"actionBarTabBarStyle", 11}, - {"actionBarTabTextStyle", 11}, - {"actionOverflowButtonStyle", 11}, - {"actionModeCloseButtonStyle", 11}, - {"titleTextStyle", 11}, - {"subtitleTextStyle", 11}, - {"iconifiedByDefault", 11}, - {"actionLayout", 11}, - {"actionViewClass", 11}, - {"activatedBackgroundIndicator", 11}, - {"state_activated", 11}, - {"listPopupWindowStyle", 11}, - {"popupMenuStyle", 11}, - {"textAppearanceLargePopupMen", 11}, - {"textAppearanceSmallPopupMen", 11}, - {"breadCrumbTitle", 11}, - {"breadCrumbShortTitle", 11}, - {"listDividerAlertDialog", 11}, - {"textColorAlertDialogListItem", 11}, - {"loopViews", 11}, - {"dialogTheme", 11}, - {"alertDialogTheme", 11}, - {"dividerVertical", 11}, - {"homeAsUpIndicator", 11}, - {"enterFadeDuration", 11}, - {"exitFadeDuration", 11}, - {"selectableItemBackground", 11}, - {"autoAdvanceViewId", 11}, - {"useIntrinsicSizeAsMinimum", 11}, - {"actionModeCutDrawable", 11}, - {"actionModeCopyDrawable", 11}, - {"actionModePasteDrawable", 11}, - {"textEditPasteWindowLayout", 11}, - {"textEditNoPasteWindowLayout", 11}, - {"textIsSelectable", 11}, - {"windowEnableSplitTouch", 11}, - {"indeterminateProgressStyle", 11}, - {"progressBarPadding", 11}, - {"animationResolution", 11}, - {"state_accelerated", 11}, - {"baseline", 11}, - {"homeLayout", 11}, - {"opacity", 11}, - {"alpha", 11}, - {"transformPivotX", 11}, - {"transformPivotY", 11}, - {"translationX", 11}, - {"translationY", 11}, - {"scaleX", 11}, - {"scaleY", 11}, - {"rotation", 11}, - {"rotationX", 11}, - {"rotationY", 11}, - {"showDividers", 11}, - {"dividerPadding", 11}, - {"borderlessButtonStyle", 11}, - {"dividerHorizontal", 11}, - {"itemPadding", 11}, - {"buttonBarStyle", 11}, - {"buttonBarButtonStyle", 11}, - {"segmentedButtonStyle", 11}, - {"staticWallpaperPreview", 11}, - {"allowParallelSyncs", 11}, - {"isAlwaysSyncable", 11}, - {"verticalScrollbarPosition", 11}, - {"fastScrollAlwaysVisible", 11}, - {"fastScrollThumbDrawable", 11}, - {"fastScrollPreviewBackgroundLeft", 11}, - {"fastScrollPreviewBackgroundRight", 11}, - {"fastScrollTrackDrawable", 11}, - {"fastScrollOverlayPosition", 11}, - {"customTokens", 11}, - {"nextFocusForward", 11}, - {"firstDayOfWeek", 11}, - {"showWeekNumber", 11}, - {"minDate", 11}, - {"maxDate", 11}, - {"shownWeekCount", 11}, - {"selectedWeekBackgroundColor", 11}, - {"focusedMonthDateColor", 11}, - {"unfocusedMonthDateColor", 11}, - {"weekNumberColor", 11}, - {"weekSeparatorLineColor", 11}, - {"selectedDateVerticalBar", 11}, - {"weekDayTextAppearance", 11}, - {"dateTextAppearance", 11}, - {"solidColor", 11}, - {"spinnersShown", 11}, - {"calendarViewShown", 11}, - {"state_multiline", 11}, - {"detailsElementBackground", 11}, - {"textColorHighlightInverse", 11}, - {"textColorLinkInverse", 11}, - {"editTextColor", 11}, - {"editTextBackground", 11}, - {"horizontalScrollViewStyle", 11}, - {"layerType", 11}, - {"alertDialogIcon", 11}, - {"windowMinWidthMajor", 11}, - {"windowMinWidthMinor", 11}, - {"queryHint", 11}, - {"fastScrollTextColor", 11}, - {"largeHeap", 11}, - {"windowCloseOnTouchOutside", 11}, - {"datePickerStyle", 11}, - {"calendarViewStyle", 11}, - {"textEditSidePasteWindowLayout", 11}, - {"textEditSideNoPasteWindowLayout", 11}, - {"actionMenuTextAppearance", 11}, - {"actionMenuTextColor", 11}, - {"textCursorDrawable", 12}, - {"resizeMode", 12}, - {"requiresSmallestWidthDp", 12}, - {"compatibleWidthLimitDp", 12}, - {"largestWidthLimitDp", 12}, - {"state_hovered", 13}, - {"state_drag_can_accept", 13}, - {"state_drag_hovered", 13}, - {"stopWithTask", 13}, - {"switchTextOn", 13}, - {"switchTextOff", 13}, - {"switchPreferenceStyle", 13}, - {"switchTextAppearance", 13}, - {"track", 13}, - {"switchMinWidth", 13}, - {"switchPadding", 13}, - {"thumbTextPadding", 13}, - {"textSuggestionsWindowStyle", 13}, - {"textEditSuggestionItemLayout", 13}, - {"rowCount", 13}, - {"rowOrderPreserved", 13}, - {"columnCount", 13}, - {"columnOrderPreserved", 13}, - {"useDefaultMargins", 13}, - {"alignmentMode", 13}, - {"layout_row", 13}, - {"layout_rowSpan", 13}, - {"layout_columnSpan", 13}, - {"actionModeSelectAllDrawable", 13}, - {"isAuxiliary", 13}, - {"accessibilityEventTypes", 13}, - {"packageNames", 13}, - {"accessibilityFeedbackType", 13}, - {"notificationTimeout", 13}, - {"accessibilityFlags", 13}, - {"canRetrieveWindowContent", 13}, - {"listPreferredItemHeightLarge", 13}, - {"listPreferredItemHeightSmall", 13}, - {"actionBarSplitStyle", 13}, - {"actionProviderClass", 13}, - {"backgroundStacked", 13}, - {"backgroundSplit", 13}, - {"textAllCaps", 13}, - {"colorPressedHighlight", 13}, - {"colorLongPressedHighlight", 13}, - {"colorFocusedHighlight", 13}, - {"colorActivatedHighlight", 13}, - {"colorMultiSelectHighlight", 13}, - {"drawableStart", 13}, - {"drawableEnd", 13}, - {"actionModeStyle", 13}, - {"minResizeWidth", 13}, - {"minResizeHeight", 13}, - {"actionBarWidgetTheme", 13}, - {"uiOptions", 13}, - {"subtypeLocale", 13}, - {"subtypeExtraValue", 13}, - {"actionBarDivider", 13}, - {"actionBarItemBackground", 13}, - {"actionModeSplitBackground", 13}, - {"textAppearanceListItem", 13}, - {"textAppearanceListItemSmall", 13}, - {"targetDescriptions", 13}, - {"directionDescriptions", 13}, - {"overridesImplicitlyEnabledSubtype", 13}, - {"listPreferredItemPaddingLeft", 13}, - {"listPreferredItemPaddingRight", 13}, - {"requiresFadingEdge", 13}, - {"publicKey", 13}, - {"parentActivityName", 16}, - {"isolatedProcess", 16}, - {"importantForAccessibility", 16}, - {"keyboardLayout", 16}, - {"fontFamily", 16}, - {"mediaRouteButtonStyle", 16}, - {"mediaRouteTypes", 16}, - {"supportsRtl", 17}, - {"textDirection", 17}, - {"textAlignment", 17}, - {"layoutDirection", 17}, - {"paddingStart", 17}, - {"paddingEnd", 17}, - {"layout_marginStart", 17}, - {"layout_marginEnd", 17}, - {"layout_toStartOf", 17}, - {"layout_toEndOf", 17}, - {"layout_alignStart", 17}, - {"layout_alignEnd", 17}, - {"layout_alignParentStart", 17}, - {"layout_alignParentEnd", 17}, - {"listPreferredItemPaddingStart", 17}, - {"listPreferredItemPaddingEnd", 17}, - {"singleUser", 17}, - {"presentationTheme", 17}, - {"subtypeId", 17}, - {"initialKeyguardLayout", 17}, - {"widgetCategory", 17}, - {"permissionGroupFlags", 17}, - {"labelFor", 17}, - {"permissionFlags", 17}, - {"checkedTextViewStyle", 17}, - {"showOnLockScreen", 17}, - {"format12Hour", 17}, - {"format24Hour", 17}, - {"timeZone", 17}, - {"mipMap", 18}, - {"mirrorForRtl", 18}, - {"windowOverscan", 18}, - {"requiredForAllUsers", 18}, - {"indicatorStart", 18}, - {"indicatorEnd", 18}, - {"childIndicatorStart", 18}, - {"childIndicatorEnd", 18}, - {"restrictedAccountType", 18}, - {"requiredAccountType", 18}, - {"canRequestTouchExplorationMode", 18}, - {"canRequestEnhancedWebAccessibility", 18}, - {"canRequestFilterKeyEvents", 18}, - {"layoutMode", 18}, - {"keySet", 19}, - {"targetId", 19}, - {"fromScene", 19}, - {"toScene", 19}, - {"transition", 19}, - {"transitionOrdering", 19}, - {"fadingMode", 19}, - {"startDelay", 19}, - {"ssp", 19}, - {"sspPrefix", 19}, - {"sspPattern", 19}, - {"addPrintersActivity", 19}, - {"vendor", 19}, - {"category", 19}, - {"isAsciiCapable", 19}, - {"autoMirrored", 19}, - {"supportsSwitchingToNextInputMethod", 19}, - {"requireDeviceUnlock", 19}, - {"apduServiceBanner", 19}, - {"accessibilityLiveRegion", 19}, - {"windowTranslucentStatus", 19}, - {"windowTranslucentNavigation", 19}, - {"advancedPrintOptionsActivity", 19}, - {"banner", 20}, - {"windowSwipeToDismiss", 20}, - {"isGame", 20}, - {"allowEmbedded", 20}, - {"setupActivity", 20}, - {"fastScrollStyle", 21}, - {"windowContentTransitions", 21}, - {"windowContentTransitionManager", 21}, - {"translationZ", 21}, - {"tintMode", 21}, - {"controlX1", 21}, - {"controlY1", 21}, - {"controlX2", 21}, - {"controlY2", 21}, - {"transitionName", 21}, - {"transitionGroup", 21}, - {"viewportWidth", 21}, - {"viewportHeight", 21}, - {"fillColor", 21}, - {"pathData", 21}, - {"strokeColor", 21}, - {"strokeWidth", 21}, - {"trimPathStart", 21}, - {"trimPathEnd", 21}, - {"trimPathOffset", 21}, - {"strokeLineCap", 21}, - {"strokeLineJoin", 21}, - {"strokeMiterLimit", 21}, - {"colorControlNormal", 21}, - {"colorControlActivated", 21}, - {"colorButtonNormal", 21}, - {"colorControlHighlight", 21}, - {"persistableMode", 21}, - {"titleTextAppearance", 21}, - {"subtitleTextAppearance", 21}, - {"slideEdge", 21}, - {"actionBarTheme", 21}, - {"textAppearanceListItemSecondary", 21}, - {"colorPrimary", 21}, - {"colorPrimaryDark", 21}, - {"colorAccent", 21}, - {"nestedScrollingEnabled", 21}, - {"windowEnterTransition", 21}, - {"windowExitTransition", 21}, - {"windowSharedElementEnterTransition", 21}, - {"windowSharedElementExitTransition", 21}, - {"windowAllowReturnTransitionOverlap", 21}, - {"windowAllowEnterTransitionOverlap", 21}, - {"sessionService", 21}, - {"stackViewStyle", 21}, - {"switchStyle", 21}, - {"elevation", 21}, - {"excludeId", 21}, - {"excludeClass", 21}, - {"hideOnContentScroll", 21}, - {"actionOverflowMenuStyle", 21}, - {"documentLaunchMode", 21}, - {"maxRecents", 21}, - {"autoRemoveFromRecents", 21}, - {"stateListAnimator", 21}, - {"toId", 21}, - {"fromId", 21}, - {"reversible", 21}, - {"splitTrack", 21}, - {"targetName", 21}, - {"excludeName", 21}, - {"matchOrder", 21}, - {"windowDrawsSystemBarBackgrounds", 21}, - {"statusBarColor", 21}, - {"navigationBarColor", 21}, - {"contentInsetStart", 21}, - {"contentInsetEnd", 21}, - {"contentInsetLeft", 21}, - {"contentInsetRight", 21}, - {"paddingMode", 21}, - {"layout_rowWeight", 21}, - {"layout_columnWeight", 21}, - {"translateX", 21}, - {"translateY", 21}, - {"selectableItemBackgroundBorderless", 21}, - {"elegantTextHeight", 21}, - {"searchKeyphraseId", 21}, - {"searchKeyphrase", 21}, - {"searchKeyphraseSupportedLocales", 21}, - {"windowTransitionBackgroundFadeDuration", 21}, - {"overlapAnchor", 21}, - {"progressTint", 21}, - {"progressTintMode", 21}, - {"progressBackgroundTint", 21}, - {"progressBackgroundTintMode", 21}, - {"secondaryProgressTint", 21}, - {"secondaryProgressTintMode", 21}, - {"indeterminateTint", 21}, - {"indeterminateTintMode", 21}, - {"backgroundTint", 21}, - {"backgroundTintMode", 21}, - {"foregroundTint", 21}, - {"foregroundTintMode", 21}, - {"buttonTint", 21}, - {"buttonTintMode", 21}, - {"thumbTint", 21}, - {"thumbTintMode", 21}, - {"fullBackupOnly", 21}, - {"propertyXName", 21}, - {"propertyYName", 21}, - {"relinquishTaskIdentity", 21}, - {"tileModeX", 21}, - {"tileModeY", 21}, - {"actionModeShareDrawable", 21}, - {"actionModeFindDrawable", 21}, - {"actionModeWebSearchDrawable", 21}, - {"transitionVisibilityMode", 21}, - {"minimumHorizontalAngle", 21}, - {"minimumVerticalAngle", 21}, - {"maximumAngle", 21}, - {"searchViewStyle", 21}, - {"closeIcon", 21}, - {"goIcon", 21}, - {"searchIcon", 21}, - {"voiceIcon", 21}, - {"commitIcon", 21}, - {"suggestionRowLayout", 21}, - {"queryBackground", 21}, - {"submitBackground", 21}, - {"buttonBarPositiveButtonStyle", 21}, - {"buttonBarNeutralButtonStyle", 21}, - {"buttonBarNegativeButtonStyle", 21}, - {"popupElevation", 21}, - {"actionBarPopupTheme", 21}, - {"multiArch", 21}, - {"touchscreenBlocksFocus", 21}, - {"windowElevation", 21}, - {"launchTaskBehindTargetAnimation", 21}, - {"launchTaskBehindSourceAnimation", 21}, - {"restrictionType", 21}, - {"dayOfWeekBackground", 21}, - {"dayOfWeekTextAppearance", 21}, - {"headerMonthTextAppearance", 21}, - {"headerDayOfMonthTextAppearance", 21}, - {"headerYearTextAppearance", 21}, - {"yearListItemTextAppearance", 21}, - {"yearListSelectorColor", 21}, - {"calendarTextColor", 21}, - {"recognitionService", 21}, - {"timePickerStyle", 21}, - {"timePickerDialogTheme", 21}, - {"headerTimeTextAppearance", 21}, - {"headerAmPmTextAppearance", 21}, - {"numbersTextColor", 21}, - {"numbersBackgroundColor", 21}, - {"numbersSelectorColor", 21}, - {"amPmTextColor", 21}, - {"amPmBackgroundColor", 21}, - {"searchKeyphraseRecognitionFlags", 21}, - {"checkMarkTint", 21}, - {"checkMarkTintMode", 21}, - {"popupTheme", 21}, - {"toolbarStyle", 21}, - {"windowClipToOutline", 21}, - {"datePickerDialogTheme", 21}, - {"showText", 21}, - {"windowReturnTransition", 21}, - {"windowReenterTransition", 21}, - {"windowSharedElementReturnTransition", 21}, - {"windowSharedElementReenterTransition", 21}, - {"resumeWhilePausing", 21}, - {"datePickerMode", 21}, - {"timePickerMode", 21}, - {"inset", 21}, - {"letterSpacing", 21}, - {"fontFeatureSettings", 21}, - {"outlineProvider", 21}, - {"contentAgeHint", 21}, - {"country", 21}, - {"windowSharedElementsUseOverlay", 21}, - {"reparent", 21}, - {"reparentWithOverlay", 21}, - {"ambientShadowAlpha", 21}, - {"spotShadowAlpha", 21}, - {"navigationIcon", 21}, - {"navigationContentDescription", 21}, - {"fragmentExitTransition", 21}, - {"fragmentEnterTransition", 21}, - {"fragmentSharedElementEnterTransition", 21}, - {"fragmentReturnTransition", 21}, - {"fragmentSharedElementReturnTransition", 21}, - {"fragmentReenterTransition", 21}, - {"fragmentAllowEnterTransitionOverlap", 21}, - {"fragmentAllowReturnTransitionOverlap", 21}, - {"patternPathData", 21}, - {"strokeAlpha", 21}, - {"fillAlpha", 21}, - {"windowActivityTransitions", 21}, - {"colorEdgeEffect", 21}}; - -ApiVersion FindAttributeSdkLevel(const ResourceName& name) { - if (name.package != "android" && name.type != ResourceType::kAttr) { - return 0; - } - - auto iter = sAttrMap.find(name.entry); - if (iter != sAttrMap.end()) { - return iter->second; - } - return SDK_LOLLIPOP_MR1; -} - std::pair<StringPiece, ApiVersion> GetDevelopmentSdkCodeNameAndVersion() { return std::make_pair(StringPiece(sDevelopmentSdkCodeName), sDevelopmentSdkLevel); } diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h index 5c32ed4fd849..5b7be3bb45ae 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -53,10 +53,10 @@ enum : ApiVersion { SDK_NOUGAT_MR1 = 25, SDK_O = 26, SDK_O_MR1 = 27, + SDK_P = 10000, // STOPSHIP Replace with the real version. }; ApiVersion FindAttributeSdkLevel(const ResourceId& id); -ApiVersion FindAttributeSdkLevel(const ResourceName& name); std::pair<android::StringPiece, ApiVersion> GetDevelopmentSdkCodeNameAndVersion(); } // namespace aapt diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h index d7f2a668477c..0f312d6998f1 100644 --- a/tools/aapt2/Source.h +++ b/tools/aapt2/Source.h @@ -20,16 +20,14 @@ #include <ostream> #include <string> +#include "android-base/stringprintf.h" #include "androidfw/StringPiece.h" #include "util/Maybe.h" namespace aapt { -/** - * Represents a file on disk. Used for logging and - * showing errors. - */ +// Represents a file on disk. Used for logging and showing errors. struct Source { std::string path; Maybe<size_t> line; @@ -42,7 +40,16 @@ struct Source { inline Source(const android::StringPiece& path, size_t line) : path(path.to_string()), line(line) {} - inline Source WithLine(size_t line) const { return Source(path, line); } + inline Source WithLine(size_t line) const { + return Source(path, line); + } + + std::string to_string() const { + if (line) { + return ::android::base::StringPrintf("%s:%zd", path.c_str(), line.value()); + } + return path; + } }; // @@ -50,11 +57,7 @@ struct Source { // inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) { - out << source.path; - if (source.line) { - out << ":" << source.line.value(); - } - return out; + return out << source.to_string(); } inline bool operator==(const Source& lhs, const Source& rhs) { diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h index eb4fa494e53f..4e74ec366dab 100644 --- a/tools/aapt2/ValueVisitor.h +++ b/tools/aapt2/ValueVisitor.h @@ -22,12 +22,11 @@ namespace aapt { -/** - * Visits a value and invokes the appropriate method based on its type. Does not - * traverse into compound types. Use ValueVisitor for that. - */ -struct RawValueVisitor { - virtual ~RawValueVisitor() = default; +// Visits a value and invokes the appropriate method based on its type. +// Does not traverse into compound types. Use ValueVisitor for that. +class ValueVisitor { + public: + virtual ~ValueVisitor() = default; virtual void VisitAny(Value* value) {} virtual void VisitItem(Item* value) { VisitAny(value); } @@ -46,21 +45,67 @@ struct RawValueVisitor { virtual void Visit(Styleable* value) { VisitAny(value); } }; +// Const version of ValueVisitor. +class ConstValueVisitor { + public: + virtual ~ConstValueVisitor() = default; + + virtual void VisitAny(const Value* value) { + } + virtual void VisitItem(const Item* value) { + VisitAny(value); + } + virtual void Visit(const Reference* value) { + VisitItem(value); + } + virtual void Visit(const RawString* value) { + VisitItem(value); + } + virtual void Visit(const String* value) { + VisitItem(value); + } + virtual void Visit(const StyledString* value) { + VisitItem(value); + } + virtual void Visit(const FileReference* value) { + VisitItem(value); + } + virtual void Visit(const Id* value) { + VisitItem(value); + } + virtual void Visit(const BinaryPrimitive* value) { + VisitItem(value); + } + + virtual void Visit(const Attribute* value) { + VisitAny(value); + } + virtual void Visit(const Style* value) { + VisitAny(value); + } + virtual void Visit(const Array* value) { + VisitAny(value); + } + virtual void Visit(const Plural* value) { + VisitAny(value); + } + virtual void Visit(const Styleable* value) { + VisitAny(value); + } +}; + // NOLINT, do not add parentheses around T. #define DECL_VISIT_COMPOUND_VALUE(T) \ virtual void Visit(T* value) override { /* NOLINT */ \ VisitSubValues(value); \ } -/** - * Visits values, and if they are compound values, visits the components as - * well. - */ -struct ValueVisitor : public RawValueVisitor { +// Visits values, and if they are compound values, descends into their components as well. +struct DescendingValueVisitor : public ValueVisitor { // The compiler will think we're hiding an overload, when we actually intend // to call into RawValueVisitor. This will expose the visit methods in the // super class so the compiler knows we are trying to call them. - using RawValueVisitor::Visit; + using ValueVisitor::Visit; void VisitSubValues(Attribute* attribute) { for (Attribute::Symbol& symbol : attribute->symbols) { @@ -106,37 +151,30 @@ struct ValueVisitor : public RawValueVisitor { DECL_VISIT_COMPOUND_VALUE(Styleable); }; -/** - * Do not use directly. Helper struct for dyn_cast. - */ +// Do not use directly. Helper struct for dyn_cast. template <typename T> -struct DynCastVisitor : public RawValueVisitor { - T* value = nullptr; +struct DynCastVisitor : public ConstValueVisitor { + const T* value = nullptr; - void Visit(T* v) override { value = v; } + void Visit(const T* v) override { + value = v; + } }; -/** - * Specialization that checks if the value is an Item. - */ +// Specialization that checks if the value is an Item. template <> -struct DynCastVisitor<Item> : public RawValueVisitor { - Item* value = nullptr; +struct DynCastVisitor<Item> : public ConstValueVisitor { + const Item* value = nullptr; - void VisitItem(Item* item) override { value = item; } + void VisitItem(const Item* item) override { + value = item; + } }; +// Returns a valid pointer to T if the value is an instance of T. Returns nullptr if value is +// nullptr of if value is not an instance of T. template <typename T> const T* ValueCast(const Value* value) { - return ValueCast<T>(const_cast<Value*>(value)); -} - -/** - * Returns a valid pointer to T if the Value is of subtype T. - * Otherwise, returns nullptr. - */ -template <typename T> -T* ValueCast(Value* value) { if (!value) { return nullptr; } @@ -145,8 +183,13 @@ T* ValueCast(Value* value) { return visitor.value; } -inline void VisitAllValuesInPackage(ResourceTablePackage* pkg, - RawValueVisitor* visitor) { +// Non-const version of ValueCast. +template <typename T> +T* ValueCast(Value* value) { + return const_cast<T*>(ValueCast<T>(static_cast<const Value*>(value))); +} + +inline void VisitAllValuesInPackage(ResourceTablePackage* pkg, ValueVisitor* visitor) { for (auto& type : pkg->types) { for (auto& entry : type->entries) { for (auto& config_value : entry->values) { @@ -156,8 +199,7 @@ inline void VisitAllValuesInPackage(ResourceTablePackage* pkg, } } -inline void VisitAllValuesInTable(ResourceTable* table, - RawValueVisitor* visitor) { +inline void VisitAllValuesInTable(ResourceTable* table, ValueVisitor* visitor) { for (auto& pkg : table->packages) { VisitAllValuesInPackage(pkg.get(), visitor); } diff --git a/tools/aapt2/ValueVisitor_test.cpp b/tools/aapt2/ValueVisitor_test.cpp index eb75b102e427..5aea77d67950 100644 --- a/tools/aapt2/ValueVisitor_test.cpp +++ b/tools/aapt2/ValueVisitor_test.cpp @@ -24,16 +24,16 @@ namespace aapt { -struct SingleReferenceVisitor : public ValueVisitor { - using ValueVisitor::Visit; +struct SingleReferenceVisitor : public DescendingValueVisitor { + using DescendingValueVisitor::Visit; Reference* visited = nullptr; void Visit(Reference* ref) override { visited = ref; } }; -struct StyleVisitor : public ValueVisitor { - using ValueVisitor::Visit; +struct StyleVisitor : public DescendingValueVisitor { + using DescendingValueVisitor::Visit; std::list<Reference*> visited_refs; Style* visited_style = nullptr; @@ -42,7 +42,7 @@ struct StyleVisitor : public ValueVisitor { void Visit(Style* style) override { visited_style = style; - ValueVisitor::Visit(style); + DescendingValueVisitor::Visit(style); } }; diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 7f5bbf042766..83512b9126da 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -35,12 +35,13 @@ #include "compile/Png.h" #include "compile/PseudolocaleGenerator.h" #include "compile/XmlIdCollector.h" -#include "flatten/Archive.h" -#include "flatten/XmlFlattener.h" -#include "io/BigBufferOutputStream.h" -#include "io/FileInputStream.h" +#include "format/Archive.h" +#include "format/Container.h" +#include "format/proto/ProtoSerialize.h" +#include "io/BigBufferStream.h" +#include "io/FileStream.h" +#include "io/StringStream.h" #include "io/Util.h" -#include "proto/ProtoSerialize.h" #include "util/Files.h" #include "util/Maybe.h" #include "util/Util.h" @@ -49,6 +50,7 @@ using ::aapt::io::FileInputStream; using ::android::StringPiece; +using ::android::base::SystemErrorCodeToString; using ::google::protobuf::io::CopyingOutputStreamAdaptor; namespace aapt { @@ -116,7 +118,7 @@ struct CompileOptions { bool verbose = false; }; -static std::string BuildIntermediateFilename(const ResourcePathData& data) { +static std::string BuildIntermediateContainerFilename(const ResourcePathData& data) { std::stringstream name; name << data.resource_dir; if (!data.config_str.empty()) { @@ -141,7 +143,7 @@ static bool LoadInputFilesFromDir(IAaptContext* context, const CompileOptions& o std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir); if (!d) { context->GetDiagnostics()->Error(DiagMessage(root_dir) << "failed to open directory: " - << android::base::SystemErrorCodeToString(errno)); + << SystemErrorCodeToString(errno)); return false; } @@ -160,7 +162,7 @@ static bool LoadInputFilesFromDir(IAaptContext* context, const CompileOptions& o std::unique_ptr<DIR, decltype(closedir)*> subdir(opendir(prefix_path.data()), closedir); if (!subdir) { context->GetDiagnostics()->Error(DiagMessage(prefix_path) << "failed to open directory: " - << android::base::SystemErrorCodeToString(errno)); + << SystemErrorCodeToString(errno)); return false; } @@ -241,15 +243,15 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, return false; } - // Make sure CopyingOutputStreamAdaptor is deleted before we call - // writer->FinishEntry(). + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry(). { - // Wrap our IArchiveWriter with an adaptor that implements the - // ZeroCopyOutputStream interface. + // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface. CopyingOutputStreamAdaptor copying_adaptor(writer); + ContainerWriter container_writer(©ing_adaptor, 1u); - std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(&table); - if (!pb_table->SerializeToZeroCopyStream(©ing_adaptor)) { + pb::ResourceTable pb_table; + SerializeTableToPb(table, &pb_table); + if (!container_writer.AddResTableEntry(pb_table)) { context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write"); return false; } @@ -262,45 +264,8 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, return true; } -static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path, const ResourceFile& file, - const BigBuffer& buffer, IArchiveWriter* writer, - IDiagnostics* diag) { - // Start the entry so we can write the header. - if (!writer->StartEntry(output_path, 0)) { - diag->Error(DiagMessage(output_path) << "failed to open file"); - return false; - } - - // Make sure CopyingOutputStreamAdaptor is deleted before we call - // writer->FinishEntry(). - { - // Wrap our IArchiveWriter with an adaptor that implements the - // ZeroCopyOutputStream interface. - CopyingOutputStreamAdaptor copying_adaptor(writer); - CompiledFileOutputStream output_stream(©ing_adaptor); - - // Number of CompiledFiles. - output_stream.WriteLittleEndian32(1); - - std::unique_ptr<pb::internal::CompiledFile> compiled_file = SerializeCompiledFileToPb(file); - output_stream.WriteCompiledFile(compiled_file.get()); - output_stream.WriteData(&buffer); - - if (output_stream.HadError()) { - diag->Error(DiagMessage(output_path) << "failed to write data"); - return false; - } - } - - if (!writer->FinishEntry()) { - diag->Error(DiagMessage(output_path) << "failed to finish writing data"); - return false; - } - return true; -} - -static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const ResourceFile& file, - const android::FileMap& map, IArchiveWriter* writer, +static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const ResourceFile& file, + io::KnownSizeInputStream* in, IArchiveWriter* writer, IDiagnostics* diag) { // Start the entry so we can write the header. if (!writer->StartEntry(output_path, 0)) { @@ -308,23 +273,17 @@ static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const Res return false; } - // Make sure CopyingOutputStreamAdaptor is deleted before we call - // writer->FinishEntry(). + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry(). { - // Wrap our IArchiveWriter with an adaptor that implements the - // ZeroCopyOutputStream interface. + // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface. CopyingOutputStreamAdaptor copying_adaptor(writer); - CompiledFileOutputStream output_stream(©ing_adaptor); + ContainerWriter container_writer(©ing_adaptor, 1u); - // Number of CompiledFiles. - output_stream.WriteLittleEndian32(1); + pb::internal::CompiledFile pb_compiled_file; + SerializeCompiledFileToPb(file, &pb_compiled_file); - std::unique_ptr<pb::internal::CompiledFile> compiled_file = SerializeCompiledFileToPb(file); - output_stream.WriteCompiledFile(compiled_file.get()); - output_stream.WriteData(map.getDataPtr(), map.getDataLength()); - - if (output_stream.HadError()) { - diag->Error(DiagMessage(output_path) << "failed to write data"); + if (!container_writer.AddResFileEntry(pb_compiled_file, in)) { + diag->Error(DiagMessage(output_path) << "failed to write entry data"); return false; } } @@ -336,23 +295,19 @@ static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const Res return true; } -static bool FlattenXmlToOutStream(IAaptContext* context, const StringPiece& output_path, - xml::XmlResource* xmlres, CompiledFileOutputStream* out) { - BigBuffer buffer(1024); - XmlFlattenerOptions xml_flattener_options; - xml_flattener_options.keep_raw_values = true; - XmlFlattener flattener(&buffer, xml_flattener_options); - if (!flattener.Consume(context, xmlres)) { - return false; - } +static bool FlattenXmlToOutStream(const StringPiece& output_path, const xml::XmlResource& xmlres, + ContainerWriter* container_writer, IDiagnostics* diag) { + pb::internal::CompiledFile pb_compiled_file; + SerializeCompiledFileToPb(xmlres.file, &pb_compiled_file); - std::unique_ptr<pb::internal::CompiledFile> pb_compiled_file = - SerializeCompiledFileToPb(xmlres->file); - out->WriteCompiledFile(pb_compiled_file.get()); - out->WriteData(&buffer); + pb::XmlNode pb_xml_node; + SerializeXmlToPb(*xmlres.root, &pb_xml_node); - if (out->HadError()) { - context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write data"); + std::string serialized_xml = pb_xml_node.SerializeAsString(); + io::StringInputStream serialized_in(serialized_xml); + + if (!container_writer->AddResFileEntry(pb_compiled_file, &serialized_in)) { + diag->Error(DiagMessage(output_path) << "failed to write entry data"); return false; } return true; @@ -401,6 +356,7 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, xmlres->file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name); xmlres->file.config = path_data.config; xmlres->file.source = path_data.source; + xmlres->file.type = ResourceFile::Type::kProtoXml; // Collect IDs that are defined here. XmlIdCollector collector; @@ -420,24 +376,23 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, return false; } + std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents = + inline_xml_format_parser.GetExtractedInlineXmlDocuments(); + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry(). { // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface. CopyingOutputStreamAdaptor copying_adaptor(writer); - CompiledFileOutputStream output_stream(©ing_adaptor); - - std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents = - inline_xml_format_parser.GetExtractedInlineXmlDocuments(); + ContainerWriter container_writer(©ing_adaptor, 1u + inline_documents.size()); - // Number of CompiledFiles. - output_stream.WriteLittleEndian32(1 + inline_documents.size()); - - if (!FlattenXmlToOutStream(context, output_path, xmlres.get(), &output_stream)) { + if (!FlattenXmlToOutStream(output_path, *xmlres, &container_writer, + context->GetDiagnostics())) { return false; } - for (auto& inline_xml_doc : inline_documents) { - if (!FlattenXmlToOutStream(context, output_path, inline_xml_doc.get(), &output_stream)) { + for (const std::unique_ptr<xml::XmlResource>& inline_xml_doc : inline_documents) { + if (!FlattenXmlToOutStream(output_path, *inline_xml_doc, &container_writer, + context->GetDiagnostics())) { return false; } } @@ -462,6 +417,7 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name); res_file.config = path_data.config; res_file.source = path_data.source; + res_file.type = ResourceFile::Type::kPng; { std::string content; @@ -469,7 +425,7 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, true /*follow_symlinks*/)) { context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to open file: " - << android::base::SystemErrorCodeToString(errno)); + << SystemErrorCodeToString(errno)); return false; } @@ -553,8 +509,9 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, } } - if (!WriteHeaderAndBufferToWriter(output_path, res_file, buffer, writer, - context->GetDiagnostics())) { + io::BigBufferInputStream buffer_in(&buffer); + if (!WriteHeaderAndDataToWriter(output_path, res_file, &buffer_in, writer, + context->GetDiagnostics())) { return false; } return true; @@ -572,6 +529,7 @@ static bool CompileFile(IAaptContext* context, const CompileOptions& options, res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name); res_file.config = path_data.config; res_file.source = path_data.source; + res_file.type = ResourceFile::Type::kUnknown; std::string error_str; Maybe<android::FileMap> f = file::MmapPath(path_data.source.path, &error_str); @@ -581,7 +539,8 @@ static bool CompileFile(IAaptContext* context, const CompileOptions& options, return false; } - if (!WriteHeaderAndMmapToWriter(output_path, res_file, f.value(), writer, + io::MmappedData mmapped_in(std::move(f.value())); + if (!WriteHeaderAndDataToWriter(output_path, res_file, &mmapped_in, writer, context->GetDiagnostics())) { return false; } @@ -611,7 +570,7 @@ class CompileContext : public IAaptContext { } NameMangler* GetNameMangler() override { - abort(); + UNIMPLEMENTED(FATAL) << "No name mangling should be needed in compile phase"; return nullptr; } @@ -625,7 +584,7 @@ class CompileContext : public IAaptContext { } SymbolTable* GetExternalSymbols() override { - abort(); + UNIMPLEMENTED(FATAL) << "No symbols should be needed in compile phase"; return nullptr; } @@ -634,14 +593,13 @@ class CompileContext : public IAaptContext { } private: + DISALLOW_COPY_AND_ASSIGN(CompileContext); + IDiagnostics* diagnostics_; bool verbose_ = false; }; -/** - * Entry point for compilation phase. Parses arguments and dispatches to the - * correct steps. - */ +// Entry point for compilation phase. Parses arguments and dispatches to the correct steps. int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { CompileContext context(diagnostics); CompileOptions options; @@ -714,50 +672,34 @@ int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { continue; } - if (path_data.resource_dir == "values") { - // Overwrite the extension. + // Determine how to compile the file based on its type. + auto compile_func = &CompileFile; + if (path_data.resource_dir == "values" && path_data.extension == "xml") { + compile_func = &CompileTable; + // We use a different extension (not necessary anymore, but avoids altering the existing + // build system logic). path_data.extension = "arsc"; - - const std::string output_filename = BuildIntermediateFilename(path_data); - if (!CompileTable(&context, options, path_data, archive_writer.get(), output_filename)) { - error = true; - } - - } else { - const std::string output_filename = BuildIntermediateFilename(path_data); - if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) { - if (*type != ResourceType::kRaw) { - if (path_data.extension == "xml") { - if (!CompileXml(&context, options, path_data, archive_writer.get(), output_filename)) { - error = true; - } - } else if (!options.no_png_crunch && - (path_data.extension == "png" || path_data.extension == "9.png")) { - if (!CompilePng(&context, options, path_data, archive_writer.get(), output_filename)) { - error = true; - } - } else { - if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) { - error = true; - } - } - } else { - if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) { - error = true; - } + } else if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) { + if (*type != ResourceType::kRaw) { + if (path_data.extension == "xml") { + compile_func = &CompileXml; + } else if (!options.no_png_crunch && + (path_data.extension == "png" || path_data.extension == "9.png")) { + compile_func = &CompilePng; } - } else { - context.GetDiagnostics()->Error(DiagMessage() << "invalid file path '" << path_data.source - << "'"); - error = true; } + } else { + context.GetDiagnostics()->Error(DiagMessage() + << "invalid file path '" << path_data.source << "'"); + error = true; + continue; } - } - if (error) { - return 1; + // Compile the file. + const std::string out_path = BuildIntermediateContainerFilename(path_data); + error |= !compile_func(&context, options, path_data, archive_writer.get(), out_path); } - return 0; + return error ? 1 : 0; } } // namespace aapt diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp new file mode 100644 index 000000000000..964dacfeafef --- /dev/null +++ b/tools/aapt2/cmd/Convert.cpp @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2017 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 <vector> + +#include "android-base/macros.h" +#include "androidfw/StringPiece.h" + +#include "Flags.h" +#include "LoadedApk.h" +#include "ValueVisitor.h" +#include "cmd/Util.h" +#include "format/binary/TableFlattener.h" +#include "format/binary/XmlFlattener.h" +#include "format/proto/ProtoDeserialize.h" +#include "format/proto/ProtoSerialize.h" +#include "io/BigBufferStream.h" +#include "io/Util.h" +#include "process/IResourceTableConsumer.h" +#include "process/SymbolTable.h" +#include "util/Util.h" + +using ::android::StringPiece; +using ::android::base::StringPrintf; +using ::std::unique_ptr; +using ::std::vector; + +namespace aapt { + +class IApkSerializer { + public: + IApkSerializer(IAaptContext* context, const Source& source) : context_(context), source_(source) {} + + virtual bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16, + IArchiveWriter* writer) = 0; + virtual bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) = 0; + virtual bool SerializeFile(const FileReference* file, IArchiveWriter* writer) = 0; + + virtual ~IApkSerializer() = default; + + protected: + IAaptContext* context_; + Source source_; +}; + +bool ConvertApk(IAaptContext* context, unique_ptr<LoadedApk> apk, IApkSerializer* serializer, + IArchiveWriter* writer) { + if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) { + context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + << "failed to serialize AndroidManifest.xml"); + return false; + } + + if (apk->GetResourceTable() != nullptr) { + // Resource table + if (!serializer->SerializeTable(apk->GetResourceTable(), writer)) { + context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + << "failed to serialize the resource table"); + return false; + } + + // Resources + for (const auto& package : apk->GetResourceTable()->packages) { + for (const auto& type : package->types) { + for (const auto& entry : type->entries) { + for (const auto& config_value : entry->values) { + const FileReference* file = ValueCast<FileReference>(config_value->value.get()); + if (file != nullptr) { + if (file->file == nullptr) { + context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + << "no file associated with " << *file); + return false; + } + + if (!serializer->SerializeFile(file, writer)) { + context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + << "failed to serialize file " << *file->path); + return false; + } + } // file + } // config_value + } // entry + } // type + } // package + } + + // Other files + std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator(); + while (iterator->HasNext()) { + io::IFile* file = iterator->Next(); + + std::string path = file->GetSource().path; + // The name of the path has the format "<zip-file-name>@<path-to-file>". + path = path.substr(path.find('@') + 1); + + // Manifest, resource table and resources have already been taken care of. + if (path == kAndroidManifestPath || + path == kApkResourceTablePath || + path == kProtoResourceTablePath || + path.find("res/") == 0) { + continue; + } + + if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) { + context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + << "failed to copy file " << path); + return false; + } + } + + return true; +} + + +class BinaryApkSerializer : public IApkSerializer { + public: + BinaryApkSerializer(IAaptContext* context, const Source& source, + const TableFlattenerOptions& options) + : IApkSerializer(context, source), tableFlattenerOptions_(options) {} + + bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16, + IArchiveWriter* writer) override { + BigBuffer buffer(4096); + XmlFlattenerOptions options = {}; + options.use_utf16 = utf16; + XmlFlattener flattener(&buffer, options); + if (!flattener.Consume(context_, xml)) { + return false; + } + + io::BigBufferInputStream input_stream(&buffer); + return io::CopyInputStreamToArchive(context_, &input_stream, path, ArchiveEntry::kCompress, + writer); + } + + bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override { + BigBuffer buffer(4096); + TableFlattener table_flattener(tableFlattenerOptions_, &buffer); + if (!table_flattener.Consume(context_, table)) { + return false; + } + + io::BigBufferInputStream input_stream(&buffer); + return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath, + ArchiveEntry::kAlign, writer); + } + + bool SerializeFile(const FileReference* file, IArchiveWriter* writer) override { + if (file->type == ResourceFile::Type::kProtoXml) { + unique_ptr<io::InputStream> in = file->file->OpenInputStream(); + if (in == nullptr) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to open file " << *file->path); + return false; + } + + pb::XmlNode pb_node; + io::ZeroCopyInputAdaptor adaptor(in.get()); + if (!pb_node.ParseFromZeroCopyStream(&adaptor)) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to parse proto XML " << *file->path); + return false; + } + + std::string error; + unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error); + if (xml == nullptr) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to deserialize proto XML " + << *file->path << ": " << error); + return false; + } + + if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to serialize to binary XML: " << *file->path); + return false; + } + } else { + if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to copy file " << *file->path); + return false; + } + } + + return true; + } + + private: + TableFlattenerOptions tableFlattenerOptions_; + + DISALLOW_COPY_AND_ASSIGN(BinaryApkSerializer); +}; + +class ProtoApkSerializer : public IApkSerializer { + public: + ProtoApkSerializer(IAaptContext* context, const Source& source) + : IApkSerializer(context, source) {} + + bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16, + IArchiveWriter* writer) override { + pb::XmlNode pb_node; + SerializeXmlResourceToPb(*xml, &pb_node); + return io::CopyProtoToArchive(context_, &pb_node, path, ArchiveEntry::kCompress, writer); + } + + bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override { + pb::ResourceTable pb_table; + SerializeTableToPb(*table, &pb_table); + return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath, + ArchiveEntry::kCompress, writer); + } + + bool SerializeFile(const FileReference* file, IArchiveWriter* writer) override { + if (file->type == ResourceFile::Type::kBinaryXml) { + std::unique_ptr<io::IData> data = file->file->OpenAsData(); + if (!data) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to open file " << *file->path); + return false; + } + + std::string error; + std::unique_ptr<xml::XmlResource> xml = xml::Inflate(data->data(), data->size(), &error); + if (xml == nullptr) { + context_->GetDiagnostics()->Error(DiagMessage(source_) << "failed to parse binary XML: " + << error); + return false; + } + + if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to serialize to proto XML: " << *file->path); + return false; + } + } else { + if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to copy file " << *file->path); + return false; + } + } + + return true; + } + + private: + DISALLOW_COPY_AND_ASSIGN(ProtoApkSerializer); +}; + +class Context : public IAaptContext { + public: + Context() : mangler_({}), symbols_(&mangler_) { + } + + PackageType GetPackageType() override { + return PackageType::kApp; + } + + SymbolTable* GetExternalSymbols() override { + return &symbols_; + } + + IDiagnostics* GetDiagnostics() override { + return &diag_; + } + + const std::string& GetCompilationPackage() override { + return package_; + } + + uint8_t GetPackageId() override { + // Nothing should call this. + UNIMPLEMENTED(FATAL) << "PackageID should not be necessary"; + return 0; + } + + NameMangler* GetNameMangler() override { + UNIMPLEMENTED(FATAL); + return nullptr; + } + + bool IsVerbose() override { + return verbose_; + } + + int GetMinSdkVersion() override { + return 0u; + } + + bool verbose_ = false; + std::string package_; + + private: + DISALLOW_COPY_AND_ASSIGN(Context); + + NameMangler mangler_; + SymbolTable symbols_; + StdErrDiagnostics diag_; +}; + +int Convert(const vector<StringPiece>& args) { + + static const char* kOutputFormatProto = "proto"; + static const char* kOutputFormatBinary = "binary"; + + Context context; + std::string output_path; + Maybe<std::string> output_format; + TableFlattenerOptions options; + Flags flags = + Flags() + .RequiredFlag("-o", "Output path", &output_path) + .OptionalFlag("--output-format", StringPrintf("Format of the output. Accepted values are " + "'%s' and '%s'. When not set, defaults to '%s'.", kOutputFormatProto, + kOutputFormatBinary, kOutputFormatBinary), &output_format) + .OptionalSwitch("--enable-sparse-encoding", + "Enables encoding sparse entries using a binary search tree.\n" + "This decreases APK size at the cost of resource retrieval performance.", + &options.use_sparse_entries) + .OptionalSwitch("-v", "Enables verbose logging", &context.verbose_); + if (!flags.Parse("aapt2 convert", args, &std::cerr)) { + return 1; + } + + if (flags.GetArgs().size() != 1) { + std::cerr << "must supply a single proto APK\n"; + flags.Usage("aapt2 convert", &std::cerr); + return 1; + } + + const StringPiece& path = flags.GetArgs()[0]; + unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics()); + if (apk == nullptr) { + context.GetDiagnostics()->Error(DiagMessage(path) << "failed to load APK"); + return 1; + } + + Maybe<AppInfo> app_info = + ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics()); + if (!app_info) { + return 1; + } + + context.package_ = app_info.value().package; + + unique_ptr<IArchiveWriter> writer = + CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path); + if (writer == nullptr) { + return 1; + } + + unique_ptr<IApkSerializer> serializer; + if (!output_format || output_format.value() == kOutputFormatBinary) { + serializer.reset(new BinaryApkSerializer(&context, apk->GetSource(), options)); + } else if (output_format.value() == kOutputFormatProto) { + serializer.reset(new ProtoApkSerializer(&context, apk->GetSource())); + } else { + context.GetDiagnostics()->Error(DiagMessage(path) + << "Invalid value for flag --output-format: " + << output_format.value()); + return 1; + } + + + return ConvertApk(&context, std::move(apk), serializer.get(), writer.get()) ? 0 : 1; +} + +} // namespace aapt diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp index 1a6f348a3edc..fc1f1d6342ae 100644 --- a/tools/aapt2/cmd/Diff.cpp +++ b/tools/aapt2/cmd/Diff.cpp @@ -22,7 +22,7 @@ #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { @@ -325,9 +325,9 @@ static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a, Loade return diff; } -class ZeroingReferenceVisitor : public ValueVisitor { +class ZeroingReferenceVisitor : public DescendingValueVisitor { public: - using ValueVisitor::Visit; + using DescendingValueVisitor::Visit; void Visit(Reference* ref) override { if (ref->name && ref->id) { @@ -357,8 +357,9 @@ int Diff(const std::vector<StringPiece>& args) { return 1; } - std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]); - std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[1]); + IDiagnostics* diag = context.GetDiagnostics(); + std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(flags.GetArgs()[0], diag); + std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(flags.GetArgs()[1], diag); if (!apk_a || !apk_b) { return 1; } diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp index 0965910ca853..bc7f5a86b043 100644 --- a/tools/aapt2/cmd/Dump.cpp +++ b/tools/aapt2/cmd/Dump.cpp @@ -14,139 +14,184 @@ * limitations under the License. */ +#include <cinttypes> #include <vector> +#include "android-base/stringprintf.h" #include "androidfw/StringPiece.h" #include "Debug.h" #include "Diagnostics.h" #include "Flags.h" +#include "format/Container.h" +#include "format/binary/BinaryResourceParser.h" +#include "format/proto/ProtoDeserialize.h" +#include "io/FileStream.h" #include "io/ZipArchive.h" #include "process/IResourceTableConsumer.h" -#include "proto/ProtoSerialize.h" -#include "unflatten/BinaryResourceParser.h" +#include "text/Printer.h" #include "util/Files.h" +using ::aapt::text::Printer; using ::android::StringPiece; +using ::android::base::StringPrintf; namespace aapt { -bool DumpCompiledFile(const pb::internal::CompiledFile& pb_file, const void* data, size_t len, - const Source& source, IAaptContext* context) { - std::unique_ptr<ResourceFile> file = - DeserializeCompiledFileFromPb(pb_file, source, context->GetDiagnostics()); - if (!file) { - context->GetDiagnostics()->Warn(DiagMessage() << "failed to read compiled file"); - return false; +static const char* ResourceFileTypeToString(const ResourceFile::Type& type) { + switch (type) { + case ResourceFile::Type::kPng: + return "PNG"; + case ResourceFile::Type::kBinaryXml: + return "BINARY_XML"; + case ResourceFile::Type::kProtoXml: + return "PROTO_XML"; + default: + break; } + return "UNKNOWN"; +} - std::cout << "Resource: " << file->name << "\n" - << "Config: " << file->config << "\n" - << "Source: " << file->source << "\n"; - return true; +static void DumpCompiledFile(const ResourceFile& file, const Source& source, off64_t offset, + size_t len, Printer* printer) { + printer->Print("Resource: "); + printer->Println(file.name.to_string()); + + printer->Print("Config: "); + printer->Println(file.config.to_string()); + + printer->Print("Source: "); + printer->Println(file.source.to_string()); + + printer->Print("Type: "); + printer->Println(ResourceFileTypeToString(file.type)); + + printer->Println(StringPrintf("Data: offset=%" PRIi64 " length=%zd", offset, len)); } -bool TryDumpFile(IAaptContext* context, const std::string& file_path) { - std::unique_ptr<ResourceTable> table; +static bool TryDumpFile(IAaptContext* context, const std::string& file_path) { + // Use a smaller buffer so that there is less latency for dumping to stdout. + constexpr size_t kStdOutBufferSize = 1024u; + io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize); + Printer printer(&fout); + + DebugPrintTableOptions print_options; + print_options.show_sources = true; std::string err; std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(file_path, &err); if (zip) { - io::IFile* file = zip->FindFile("resources.arsc.flat"); - if (file) { + ResourceTable table; + if (io::IFile* file = zip->FindFile("resources.pb")) { std::unique_ptr<io::IData> data = file->OpenAsData(); - if (!data) { - context->GetDiagnostics()->Error(DiagMessage(file_path) - << "failed to open resources.arsc.flat"); + if (data == nullptr) { + context->GetDiagnostics()->Error(DiagMessage(file_path) << "failed to open resources.pb"); return false; } pb::ResourceTable pb_table; if (!pb_table.ParseFromArray(data->data(), data->size())) { - context->GetDiagnostics()->Error(DiagMessage(file_path) << "invalid resources.arsc.flat"); + context->GetDiagnostics()->Error(DiagMessage(file_path) << "invalid resources.pb"); return false; } - table = DeserializeTableFromPb(pb_table, Source(file_path), context->GetDiagnostics()); - if (!table) { + if (!DeserializeTableFromPb(pb_table, zip.get(), &table, &err)) { + context->GetDiagnostics()->Error(DiagMessage(file_path) + << "failed to parse table: " << err); + return false; + } + + printer.Println("Proto APK"); + } else if (io::IFile* file = zip->FindFile("resources.arsc")) { + std::unique_ptr<io::IData> data = file->OpenAsData(); + if (!data) { + context->GetDiagnostics()->Error(DiagMessage(file_path) << "failed to open resources.arsc"); return false; } - } - if (!table) { - file = zip->FindFile("resources.arsc"); - if (file) { - std::unique_ptr<io::IData> data = file->OpenAsData(); - if (!data) { - context->GetDiagnostics()->Error(DiagMessage(file_path) - << "failed to open resources.arsc"); - return false; - } - - table = util::make_unique<ResourceTable>(); - BinaryResourceParser parser(context, table.get(), Source(file_path), data->data(), - data->size()); - if (!parser.Parse()) { - return false; - } + BinaryResourceParser parser(context->GetDiagnostics(), &table, Source(file_path), + data->data(), data->size()); + if (!parser.Parse()) { + return false; } + + printer.Println("Binary APK"); } + + Debug::PrintTable(table, print_options, &printer); + return true; } - if (!table) { - Maybe<android::FileMap> file = file::MmapPath(file_path, &err); - if (!file) { - context->GetDiagnostics()->Error(DiagMessage(file_path) << err); - return false; - } + err.clear(); - android::FileMap* file_map = &file.value(); + io::FileInputStream input(file_path); + if (input.HadError()) { + context->GetDiagnostics()->Error(DiagMessage(file_path) + << "failed to open file: " << input.GetError()); + return false; + } - // Try as a compiled table. - pb::ResourceTable pb_table; - if (pb_table.ParseFromArray(file_map->getDataPtr(), file_map->getDataLength())) { - table = DeserializeTableFromPb(pb_table, Source(file_path), context->GetDiagnostics()); - } + // Try as a compiled file. + ContainerReader reader(&input); + if (reader.HadError()) { + context->GetDiagnostics()->Error(DiagMessage(file_path) + << "failed to read container: " << reader.GetError()); + return false; + } - if (!table) { - // Try as a compiled file. - CompiledFileInputStream input(file_map->getDataPtr(), file_map->getDataLength()); + printer.Println("AAPT2 Container (APC)"); + ContainerReaderEntry* entry; + while ((entry = reader.Next()) != nullptr) { + if (entry->Type() == ContainerEntryType::kResTable) { + printer.Println("kResTable"); - uint32_t num_files = 0; - if (!input.ReadLittleEndian32(&num_files)) { - return false; + pb::ResourceTable pb_table; + if (!entry->GetResTable(&pb_table)) { + context->GetDiagnostics()->Error(DiagMessage(file_path) + << "failed to parse proto table: " << entry->GetError()); + continue; } - for (uint32_t i = 0; i < num_files; i++) { - pb::internal::CompiledFile compiled_file; - if (!input.ReadCompiledFile(&compiled_file)) { - context->GetDiagnostics()->Warn(DiagMessage() << "failed to read compiled file"); - return false; - } - - uint64_t offset, len; - if (!input.ReadDataMetaData(&offset, &len)) { - context->GetDiagnostics()->Warn(DiagMessage() << "failed to read meta data"); - return false; - } - - const void* data = static_cast<const uint8_t*>(file_map->getDataPtr()) + offset; - if (!DumpCompiledFile(compiled_file, data, len, Source(file_path), context)) { - return false; - } + ResourceTable table; + err.clear(); + if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &err)) { + context->GetDiagnostics()->Error(DiagMessage(file_path) + << "failed to parse table: " << err); + continue; } - } - } - if (table) { - DebugPrintTableOptions options; - options.show_sources = true; - Debug::PrintTable(table.get(), options); - } + printer.Indent(); + Debug::PrintTable(table, print_options, &printer); + printer.Undent(); + } else if (entry->Type() == ContainerEntryType::kResFile) { + printer.Println("kResFile"); + pb::internal::CompiledFile pb_compiled_file; + off64_t offset; + size_t length; + if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &length)) { + context->GetDiagnostics()->Error( + DiagMessage(file_path) << "failed to parse compiled proto file: " << entry->GetError()); + continue; + } + ResourceFile file; + std::string error; + if (!DeserializeCompiledFileFromPb(pb_compiled_file, &file, &error)) { + context->GetDiagnostics()->Warn(DiagMessage(file_path) + << "failed to parse compiled file: " << error); + continue; + } + + printer.Indent(); + DumpCompiledFile(file, Source(file_path), offset, length, &printer); + printer.Undent(); + } + } return true; } +namespace { + class DumpContext : public IAaptContext { public: PackageType GetPackageType() override { @@ -159,7 +204,7 @@ class DumpContext : public IAaptContext { } NameMangler* GetNameMangler() override { - abort(); + UNIMPLEMENTED(FATAL); return nullptr; } @@ -173,7 +218,7 @@ class DumpContext : public IAaptContext { } SymbolTable* GetExternalSymbols() override { - abort(); + UNIMPLEMENTED(FATAL); return nullptr; } @@ -194,9 +239,9 @@ class DumpContext : public IAaptContext { bool verbose_ = false; }; -/** - * Entry point for dump command. - */ +} // namespace + +// Entry point for dump command. int Dump(const std::vector<StringPiece>& args) { bool verbose = false; Flags flags = Flags().OptionalSwitch("-v", "increase verbosity of output", &verbose); @@ -212,7 +257,6 @@ int Dump(const std::vector<StringPiece>& args) { return 1; } } - return 0; } diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 5fc35b811213..d782de55f66a 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -15,8 +15,8 @@ */ #include <sys/stat.h> +#include <cinttypes> -#include <fstream> #include <queue> #include <unordered_map> #include <vector> @@ -25,22 +25,28 @@ #include "android-base/file.h" #include "android-base/stringprintf.h" #include "androidfw/StringPiece.h" -#include "google/protobuf/io/coded_stream.h" #include "AppInfo.h" #include "Debug.h" #include "Flags.h" +#include "LoadedApk.h" #include "Locale.h" #include "NameMangler.h" #include "ResourceUtils.h" +#include "ResourceValues.h" +#include "ValueVisitor.h" #include "cmd/Util.h" #include "compile/IdAssigner.h" +#include "compile/XmlIdCollector.h" #include "filter/ConfigFilter.h" -#include "flatten/Archive.h" -#include "flatten/TableFlattener.h" -#include "flatten/XmlFlattener.h" -#include "io/BigBufferInputStream.h" -#include "io/FileInputStream.h" +#include "format/Archive.h" +#include "format/Container.h" +#include "format/binary/TableFlattener.h" +#include "format/binary/XmlFlattener.h" +#include "format/proto/ProtoDeserialize.h" +#include "format/proto/ProtoSerialize.h" +#include "io/BigBufferStream.h" +#include "io/FileStream.h" #include "io/FileSystem.h" #include "io/Util.h" #include "io/ZipArchive.h" @@ -56,9 +62,7 @@ #include "optimize/VersionCollapser.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" -#include "proto/ProtoSerialize.h" #include "split/TableSplitter.h" -#include "unflatten/BinaryResourceParser.h" #include "util/Files.h" #include "xml/XmlDom.h" @@ -68,6 +72,11 @@ using ::android::base::StringPrintf; namespace aapt { +enum class OutputFormat { + kApk, + kProto, +}; + struct LinkOptions { std::string output_path; std::string manifest_path; @@ -76,6 +85,7 @@ struct LinkOptions { std::vector<std::string> assets_dirs; bool output_to_directory = false; bool auto_add_overlay = false; + OutputFormat output_format = OutputFormat::kApk; // Java/Proguard options. Maybe<std::string> generate_java_class_path; @@ -84,6 +94,7 @@ struct LinkOptions { Maybe<std::string> generate_text_symbols_path; Maybe<std::string> generate_proguard_rules_path; Maybe<std::string> generate_main_dex_proguard_rules_path; + bool generate_conditional_proguard_rules = false; bool generate_non_final_ids = false; std::vector<std::string> javadoc_annotations; Maybe<std::string> private_symbols; @@ -202,6 +213,8 @@ class LinkContext : public IAaptContext { // This delegate will attempt to masquerade any '@id/' references with ID 0xPPTTEEEE, // where PP > 7f, as 0x7fPPEEEE. Any potential overlapping is verified and an error occurs if such // an overlap exists. +// +// See b/37498913. class FeatureSplitSymbolTableDelegate : public DefaultSymbolTableDelegate { public: FeatureSplitSymbolTableDelegate(IAaptContext* context) : context_(context) { @@ -250,41 +263,39 @@ class FeatureSplitSymbolTableDelegate : public DefaultSymbolTableDelegate { IAaptContext* context_; }; -static bool FlattenXml(IAaptContext* context, xml::XmlResource* xml_res, const StringPiece& path, - bool keep_raw_values, bool utf16, IArchiveWriter* writer) { - BigBuffer buffer(1024); - XmlFlattenerOptions options = {}; - options.keep_raw_values = keep_raw_values; - options.use_utf16 = utf16; - XmlFlattener flattener(&buffer, options); - if (!flattener.Consume(context, xml_res)) { - return false; - } - +static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, + const StringPiece& path, bool keep_raw_values, bool utf16, + OutputFormat format, IArchiveWriter* writer) { if (context->IsVerbose()) { context->GetDiagnostics()->Note(DiagMessage(path) << "writing to archive (keep_raw_values=" << (keep_raw_values ? "true" : "false") << ")"); } - io::BigBufferInputStream input_stream(&buffer); - return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(), - ArchiveEntry::kCompress, writer); -} + switch (format) { + case OutputFormat::kApk: { + BigBuffer buffer(1024); + XmlFlattenerOptions options = {}; + options.keep_raw_values = keep_raw_values; + options.use_utf16 = utf16; + XmlFlattener flattener(&buffer, options); + if (!flattener.Consume(context, &xml_res)) { + return false; + } -static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, const void* data, - size_t len, IDiagnostics* diag) { - pb::ResourceTable pb_table; - if (!pb_table.ParseFromArray(data, len)) { - diag->Error(DiagMessage(source) << "invalid compiled table"); - return {}; - } + io::BigBufferInputStream input_stream(&buffer); + return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(), + ArchiveEntry::kCompress, writer); + } break; - std::unique_ptr<ResourceTable> table = DeserializeTableFromPb(pb_table, source, diag); - if (!table) { - return {}; + case OutputFormat::kProto: { + pb::XmlNode pb_node; + SerializeXmlResourceToPb(xml_res, &pb_node); + return io::CopyProtoToArchive(context, &pb_node, path.to_string(), ArchiveEntry::kCompress, + writer); + } break; } - return table; + return false; } // Inflates an XML file from the source path. @@ -305,6 +316,7 @@ struct ResourceFileFlattenerOptions { bool keep_raw_values = false; bool do_not_compress_anything = false; bool update_proguard_spec = false; + OutputFormat output_format = OutputFormat::kApk; std::unordered_set<std::string> extensions_to_not_compress; }; @@ -458,15 +470,20 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer const Source& src = doc->file.source; if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() << "linking " << src.path); + context_->GetDiagnostics()->Note(DiagMessage() + << "linking " << src.path << " (" << doc->file.name << ")"); } + // First, strip out any tools namespace attributes. AAPT stripped them out early, which means + // that existing projects have out-of-date references which pass compilation. + xml::StripAndroidStudioAttributes(doc->root.get()); + XmlReferenceLinker xml_linker; if (!xml_linker.Consume(context_, doc)) { return {}; } - if (options_.update_proguard_spec && !proguard::CollectProguardRules(src, doc, keep_set_)) { + if (options_.update_proguard_spec && !proguard::CollectProguardRules(doc, keep_set_)) { return {}; } @@ -505,7 +522,11 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv bool error = false; std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> config_sorted_files; + proguard::CollectResourceReferences(context_, table, keep_set_); + for (auto& pkg : table->packages) { + CHECK(!pkg->name.empty()) << "Packages must have names when being linked"; + for (auto& type : pkg->types) { // Sort by config and name, so that we get better locality in the zip file. config_sorted_files.clear(); @@ -535,9 +556,9 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv file_op.config = config_value->config; file_op.file_to_copy = file; - const StringPiece src_path = file->GetSource().path; if (type->type != ResourceType::kRaw && - (util::EndsWith(src_path, ".xml.flat") || util::EndsWith(src_path, ".xml"))) { + (file_ref->type == ResourceFile::Type::kBinaryXml || + file_ref->type == ResourceFile::Type::kProtoXml)) { std::unique_ptr<io::IData> data = file->OpenAsData(); if (!data) { context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) @@ -545,11 +566,29 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv return false; } - file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(), - context_->GetDiagnostics(), file->GetSource()); + if (file_ref->type == ResourceFile::Type::kProtoXml) { + pb::XmlNode pb_xml_node; + if (!pb_xml_node.ParseFromArray(data->data(), static_cast<int>(data->size()))) { + context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + << "failed to parse proto XML"); + return false; + } - if (!file_op.xml_to_flatten) { - return false; + std::string error; + file_op.xml_to_flatten = DeserializeXmlResourceFromPb(pb_xml_node, &error); + if (file_op.xml_to_flatten == nullptr) { + context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + << "failed to deserialize proto XML: " << error); + return false; + } + } else { + std::string error_str; + file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(), &error_str); + if (file_op.xml_to_flatten == nullptr) { + context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + << "failed to parse binary XML: " << error_str); + return false; + } } file_op.xml_to_flatten->file.config = config_value->config; @@ -599,8 +638,9 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv return false; } } - error |= !FlattenXml(context_, doc.get(), dst_path, options_.keep_raw_values, - false /*utf16*/, archive_writer); + + error |= !FlattenXml(context_, *doc, dst_path, options_.keep_raw_values, + false /*utf16*/, options_.output_format, archive_writer); } } else { error |= !io::CopyFileToArchive(context_, file_op.file_to_copy, file_op.dst_path, @@ -615,24 +655,26 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv static bool WriteStableIdMapToPath(IDiagnostics* diag, const std::unordered_map<ResourceName, ResourceId>& id_map, const std::string& id_map_path) { - std::ofstream fout(id_map_path, std::ofstream::binary); - if (!fout) { - diag->Error(DiagMessage(id_map_path) << strerror(errno)); + io::FileOutputStream fout(id_map_path); + if (fout.HadError()) { + diag->Error(DiagMessage(id_map_path) << "failed to open: " << fout.GetError()); return false; } + text::Printer printer(&fout); for (const auto& entry : id_map) { const ResourceName& name = entry.first; const ResourceId& id = entry.second; - fout << name << " = " << id << "\n"; + printer.Print(name.to_string()); + printer.Print(" = "); + printer.Println(id.to_string()); } + fout.Flush(); - if (!fout) { - diag->Error(DiagMessage(id_map_path) << "failed writing to file: " - << android::base::SystemErrorCodeToString(errno)); + if (fout.HadError()) { + diag->Error(DiagMessage(id_map_path) << "failed writing to file: " << fout.GetError()); return false; } - return true; } @@ -684,6 +726,30 @@ static bool LoadStableIdMap(IDiagnostics* diag, const std::string& path, return true; } +static int32_t FindFrameworkAssetManagerCookie(const android::AssetManager& assets) { + using namespace android; + + // Find the system package (0x01). AAPT always generates attributes with the type 0x01, so + // we're looking for the first attribute resource in the system package. + const ResTable& table = assets.getResources(true); + Res_value val; + ssize_t idx = table.getResource(0x01010000, &val, true); + if (idx != NO_ERROR) { + // Try as a bag. + const ResTable::bag_entry* entry; + ssize_t cnt = table.lockBag(0x01010000, &entry); + if (cnt >= 0) { + idx = entry->stringBlock; + } + table.unlockBag(entry); + } + + if (idx < 0) { + return 0; + } + return table.getTableCookie(idx); +} + class LinkCommand { public: LinkCommand(LinkContext* context, const LinkOptions& options) @@ -693,22 +759,87 @@ class LinkCommand { file_collection_(util::make_unique<io::FileCollection>()) { } - /** - * Creates a SymbolTable that loads symbols from the various APKs and caches - * the results for faster lookup. - */ + void ExtractCompileSdkVersions(android::AssetManager* assets) { + using namespace android; + + int32_t cookie = FindFrameworkAssetManagerCookie(*assets); + if (cookie == 0) { + // No Framework assets loaded. Not a failure. + return; + } + + std::unique_ptr<Asset> manifest( + assets->openNonAsset(cookie, kAndroidManifestPath, Asset::AccessMode::ACCESS_BUFFER)); + if (manifest == nullptr) { + // No errors. + return; + } + + std::string error; + std::unique_ptr<xml::XmlResource> manifest_xml = + xml::Inflate(manifest->getBuffer(true /*wordAligned*/), manifest->getLength(), &error); + if (manifest_xml == nullptr) { + // No errors. + return; + } + + xml::Attribute* attr = manifest_xml->root->FindAttribute(xml::kSchemaAndroid, "versionCode"); + if (attr != nullptr) { + Maybe<std::string>& compile_sdk_version = options_.manifest_fixer_options.compile_sdk_version; + if (BinaryPrimitive* prim = ValueCast<BinaryPrimitive>(attr->compiled_value.get())) { + switch (prim->value.dataType) { + case Res_value::TYPE_INT_DEC: + compile_sdk_version = StringPrintf("%" PRId32, static_cast<int32_t>(prim->value.data)); + break; + case Res_value::TYPE_INT_HEX: + compile_sdk_version = StringPrintf("%" PRIx32, prim->value.data); + break; + default: + break; + } + } else if (String* str = ValueCast<String>(attr->compiled_value.get())) { + compile_sdk_version = *str->value; + } else { + compile_sdk_version = attr->value; + } + } + + attr = manifest_xml->root->FindAttribute(xml::kSchemaAndroid, "versionName"); + if (attr != nullptr) { + Maybe<std::string>& compile_sdk_version_codename = + options_.manifest_fixer_options.compile_sdk_version_codename; + if (String* str = ValueCast<String>(attr->compiled_value.get())) { + compile_sdk_version_codename = *str->value; + } else { + compile_sdk_version_codename = attr->value; + } + } + } + + // Creates a SymbolTable that loads symbols from the various APKs. + // Pre-condition: context_->GetCompilationPackage() needs to be set. bool LoadSymbolsFromIncludePaths() { - std::unique_ptr<AssetManagerSymbolSource> asset_source = - util::make_unique<AssetManagerSymbolSource>(); + auto asset_source = util::make_unique<AssetManagerSymbolSource>(); for (const std::string& path : options_.include_paths) { if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage(path) << "loading include path"); + context_->GetDiagnostics()->Note(DiagMessage() << "including " << path); } - // First try to load the file as a static lib. - std::string error_str; - std::unique_ptr<ResourceTable> include_static = LoadStaticLibrary(path, &error_str); - if (include_static) { + std::string error; + auto zip_collection = io::ZipFileCollection::Create(path, &error); + if (zip_collection == nullptr) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed to open APK: " << error); + return false; + } + + if (zip_collection->FindFile(kProtoResourceTablePath) != nullptr) { + // Load this as a static library include. + std::unique_ptr<LoadedApk> static_apk = LoadedApk::LoadProtoApkFromFileCollection( + Source(path), std::move(zip_collection), context_->GetDiagnostics()); + if (static_apk == nullptr) { + return false; + } + if (context_->GetPackageType() != PackageType::kStaticLib) { // Can't include static libraries when not building a static library (they have no IDs // assigned). @@ -717,13 +848,15 @@ class LinkCommand { return false; } - // If we are using --no-static-lib-packages, we need to rename the - // package of this table to our compilation package. + ResourceTable* table = static_apk->GetResourceTable(); + + // If we are using --no-static-lib-packages, we need to rename the package of this table to + // our compilation package. if (options_.no_static_lib_packages) { // Since package names can differ, and multiple packages can exist in a ResourceTable, // we place the requirement that all static libraries are built with the package // ID 0x7f. So if one is not found, this is an error. - if (ResourceTablePackage* pkg = include_static->FindPackageById(kAppPackageId)) { + if (ResourceTablePackage* pkg = table->FindPackageById(kAppPackageId)) { pkg->name = context_->GetCompilationPackage(); } else { context_->GetDiagnostics()->Error(DiagMessage(path) @@ -733,19 +866,14 @@ class LinkCommand { } context_->GetExternalSymbols()->AppendSource( - util::make_unique<ResourceTableSymbolSource>(include_static.get())); - - static_table_includes_.push_back(std::move(include_static)); - - } else if (!error_str.empty()) { - // We had an error with reading, so fail. - context_->GetDiagnostics()->Error(DiagMessage(path) << error_str); - return false; - } - - if (!asset_source->AddAssetPath(path)) { - context_->GetDiagnostics()->Error(DiagMessage(path) << "failed to load include path"); - return false; + util::make_unique<ResourceTableSymbolSource>(table)); + static_library_includes_.push_back(std::move(static_apk)); + } else { + if (!asset_source->AddAssetPath(path)) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed to load include path " << path); + return false; + } } } @@ -757,6 +885,17 @@ class LinkCommand { } else if (entry.first == kAppPackageId) { // Capture the included base feature package. included_feature_base_ = entry.second; + } else if (entry.first == kFrameworkPackageId) { + // Try to embed which version of the framework we're compiling against. + // First check if we should use compileSdkVersion at all. Otherwise compilation may fail + // when linking our synthesized 'android:compileSdkVersion' attribute. + std::unique_ptr<SymbolTable::Symbol> symbol = asset_source->FindByName( + ResourceName("android", ResourceType::kAttr, "compileSdkVersion")); + if (symbol != nullptr && symbol->is_public) { + // The symbol is present and public, extract the android:versionName and + // android:versionCode from the framework AndroidManifest.xml. + ExtractCompileSdkVersions(asset_source->GetAssetManager()); + } } } @@ -823,11 +962,9 @@ class LinkCommand { return app_info; } - /** - * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked. - * Postcondition: ResourceTable has only one package left. All others are - * stripped, or there is an error and false is returned. - */ + // Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked. + // Postcondition: ResourceTable has only one package left. All others are + // stripped, or there is an error and false is returned. bool VerifyNoExternalPackages() { auto is_ext_package_func = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool { return context_->GetCompilationPackage() != pkg->name || !pkg->id || @@ -904,72 +1041,159 @@ class LinkCommand { } } - bool FlattenTable(ResourceTable* table, IArchiveWriter* writer) { - BigBuffer buffer(1024); - TableFlattener flattener(options_.table_flattener_options, &buffer); - if (!flattener.Consume(context_, table)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table"); - return false; - } + bool FlattenTable(ResourceTable* table, OutputFormat format, IArchiveWriter* writer) { + switch (format) { + case OutputFormat::kApk: { + BigBuffer buffer(1024); + TableFlattener flattener(options_.table_flattener_options, &buffer); + if (!flattener.Consume(context_, table)) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table"); + return false; + } - io::BigBufferInputStream input_stream(&buffer); - return io::CopyInputStreamToArchive(context_, &input_stream, "resources.arsc", - ArchiveEntry::kAlign, writer); - } + io::BigBufferInputStream input_stream(&buffer); + return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath, + ArchiveEntry::kAlign, writer); + } break; - bool FlattenTableToPb(ResourceTable* table, IArchiveWriter* writer) { - std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table); - return io::CopyProtoToArchive(context_, pb_table.get(), "resources.arsc.flat", 0, writer); + case OutputFormat::kProto: { + pb::ResourceTable pb_table; + SerializeTableToPb(*table, &pb_table); + return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath, + ArchiveEntry::kCompress, writer); + } break; + } + return false; } bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate, const StringPiece& out_package, const JavaClassGeneratorOptions& java_options, - const Maybe<std::string> out_text_symbols_path = {}) { - if (!options_.generate_java_class_path) { + const Maybe<std::string>& out_text_symbols_path = {}) { + if (!options_.generate_java_class_path && !out_text_symbols_path) { return true; } - std::string out_path = options_.generate_java_class_path.value(); - file::AppendPath(&out_path, file::PackageToPath(out_package)); - if (!file::mkdirs(out_path)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to create directory '" << out_path - << "'"); - return false; - } + std::string out_path; + std::unique_ptr<io::FileOutputStream> fout; + if (options_.generate_java_class_path) { + out_path = options_.generate_java_class_path.value(); + file::AppendPath(&out_path, file::PackageToPath(out_package)); + if (!file::mkdirs(out_path)) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed to create directory '" << out_path << "'"); + return false; + } - file::AppendPath(&out_path, "R.java"); + file::AppendPath(&out_path, "R.java"); - std::ofstream fout(out_path, std::ofstream::binary); - if (!fout) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed writing to '" << out_path - << "': " << android::base::SystemErrorCodeToString(errno)); - return false; + fout = util::make_unique<io::FileOutputStream>(out_path); + if (fout->HadError()) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed writing to '" << out_path + << "': " << fout->GetError()); + return false; + } } - std::unique_ptr<std::ofstream> fout_text; + std::unique_ptr<io::FileOutputStream> fout_text; if (out_text_symbols_path) { - fout_text = - util::make_unique<std::ofstream>(out_text_symbols_path.value(), std::ofstream::binary); - if (!*fout_text) { - context_->GetDiagnostics()->Error( - DiagMessage() << "failed writing to '" << out_text_symbols_path.value() - << "': " << android::base::SystemErrorCodeToString(errno)); + fout_text = util::make_unique<io::FileOutputStream>(out_text_symbols_path.value()); + if (fout_text->HadError()) { + context_->GetDiagnostics()->Error(DiagMessage() + << "failed writing to '" << out_text_symbols_path.value() + << "': " << fout_text->GetError()); return false; } } JavaClassGenerator generator(context_, table, java_options); - if (!generator.Generate(package_name_to_generate, out_package, &fout, fout_text.get())) { - context_->GetDiagnostics()->Error(DiagMessage(out_path) << generator.getError()); + if (!generator.Generate(package_name_to_generate, out_package, fout.get(), fout_text.get())) { + context_->GetDiagnostics()->Error(DiagMessage(out_path) << generator.GetError()); return false; } - if (!fout) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed writing to '" << out_path - << "': " << android::base::SystemErrorCodeToString(errno)); + return true; + } + + bool GenerateJavaClasses() { + // The set of packages whose R class to call in the main classes onResourcesLoaded callback. + std::vector<std::string> packages_to_callback; + + JavaClassGeneratorOptions template_options; + template_options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; + template_options.javadoc_annotations = options_.javadoc_annotations; + + if (context_->GetPackageType() == PackageType::kStaticLib || options_.generate_non_final_ids) { + template_options.use_final = false; + } + + if (context_->GetPackageType() == PackageType::kSharedLib) { + template_options.use_final = false; + template_options.rewrite_callback_options = OnResourcesLoadedCallbackOptions{}; + } + + const StringPiece actual_package = context_->GetCompilationPackage(); + StringPiece output_package = context_->GetCompilationPackage(); + if (options_.custom_java_package) { + // Override the output java package to the custom one. + output_package = options_.custom_java_package.value(); + } + + // Generate the private symbols if required. + if (options_.private_symbols) { + packages_to_callback.push_back(options_.private_symbols.value()); + + // If we defined a private symbols package, we only emit Public symbols + // to the original package, and private and public symbols to the private package. + JavaClassGeneratorOptions options = template_options; + options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; + if (!WriteJavaFile(&final_table_, actual_package, options_.private_symbols.value(), + options)) { + return false; + } + } + + // Generate copies of the original package R class but with different package names. + // This is to support non-namespaced builds. + for (const std::string& extra_package : options_.extra_java_packages) { + packages_to_callback.push_back(extra_package); + + JavaClassGeneratorOptions options = template_options; + options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; + if (!WriteJavaFile(&final_table_, actual_package, extra_package, options)) { + return false; + } + } + + // Generate R classes for each package that was merged (static library). + // Use the actual package's resources only. + for (const std::string& package : table_merger_->merged_packages()) { + packages_to_callback.push_back(package); + + JavaClassGeneratorOptions options = template_options; + options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; + if (!WriteJavaFile(&final_table_, package, package, options)) { + return false; + } + } + + // Generate the main public R class. + JavaClassGeneratorOptions options = template_options; + + // Only generate public symbols if we have a private package. + if (options_.private_symbols) { + options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; } + + if (options.rewrite_callback_options) { + options.rewrite_callback_options.value().packages_to_callback = + std::move(packages_to_callback); + } + + if (!WriteJavaFile(&final_table_, actual_package, output_package, options, + options_.generate_text_symbols_path)) { + return false; + } + return true; } @@ -1012,18 +1236,19 @@ class LinkCommand { file::AppendPath(&out_path, "Manifest.java"); - std::ofstream fout(out_path, std::ofstream::binary); - if (!fout) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed writing to '" << out_path - << "': " << android::base::SystemErrorCodeToString(errno)); + io::FileOutputStream fout(out_path); + if (fout.HadError()) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed to open '" << out_path + << "': " << fout.GetError()); return false; } - if (!ClassDefinition::WriteJavaFile(manifest_class.get(), package_utf8, true, &fout)) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed writing to '" << out_path - << "': " << android::base::SystemErrorCodeToString(errno)); + ClassDefinition::WriteJavaFile(manifest_class.get(), package_utf8, true, &fout); + fout.Flush(); + + if (fout.HadError()) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed writing to '" << out_path + << "': " << fout.GetError()); return false; } return true; @@ -1035,64 +1260,36 @@ class LinkCommand { } const std::string& out_path = out.value(); - std::ofstream fout(out_path, std::ofstream::binary); - if (!fout) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed to open '" << out_path - << "': " << android::base::SystemErrorCodeToString(errno)); + io::FileOutputStream fout(out_path); + if (fout.HadError()) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed to open '" << out_path + << "': " << fout.GetError()); return false; } - proguard::WriteKeepSet(&fout, keep_set); - if (!fout) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed writing to '" << out_path - << "': " << android::base::SystemErrorCodeToString(errno)); + proguard::WriteKeepSet(keep_set, &fout); + fout.Flush(); + + if (fout.HadError()) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed writing to '" << out_path + << "': " << fout.GetError()); return false; } return true; } - std::unique_ptr<ResourceTable> LoadStaticLibrary(const std::string& input, - std::string* out_error) { - std::unique_ptr<io::ZipFileCollection> collection = - io::ZipFileCollection::Create(input, out_error); - if (!collection) { - return {}; - } - return LoadTablePbFromCollection(collection.get()); - } - - std::unique_ptr<ResourceTable> LoadTablePbFromCollection(io::IFileCollection* collection) { - io::IFile* file = collection->FindFile("resources.arsc.flat"); - if (!file) { - return {}; - } - - std::unique_ptr<io::IData> data = file->OpenAsData(); - return LoadTableFromPb(file->GetSource(), data->data(), data->size(), - context_->GetDiagnostics()); - } - bool MergeStaticLibrary(const std::string& input, bool override) { if (context_->IsVerbose()) { context_->GetDiagnostics()->Note(DiagMessage() << "merging static library " << input); } - std::string error_str; - std::unique_ptr<io::ZipFileCollection> collection = - io::ZipFileCollection::Create(input, &error_str); - if (!collection) { - context_->GetDiagnostics()->Error(DiagMessage(input) << error_str); - return false; - } - - std::unique_ptr<ResourceTable> table = LoadTablePbFromCollection(collection.get()); - if (!table) { + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(input, context_->GetDiagnostics()); + if (apk == nullptr) { context_->GetDiagnostics()->Error(DiagMessage(input) << "invalid static library"); return false; } + ResourceTable* table = apk->GetResourceTable(); ResourceTablePackage* pkg = table->FindPackageById(kAppPackageId); if (!pkg) { context_->GetDiagnostics()->Error(DiagMessage(input) << "static library has no package"); @@ -1101,27 +1298,24 @@ class LinkCommand { bool result; if (options_.no_static_lib_packages) { - // Merge all resources as if they were in the compilation package. This is - // the old behavior of aapt. + // Merge all resources as if they were in the compilation package. This is the old behavior + // of aapt. - // Add the package to the set of --extra-packages so we emit an R.java for - // each library package. + // Add the package to the set of --extra-packages so we emit an R.java for each library + // package. if (!pkg->name.empty()) { options_.extra_java_packages.insert(pkg->name); } + // Clear the package name, so as to make the resources look like they are coming from the + // local package. pkg->name = ""; - if (override) { - result = table_merger_->MergeOverlay(Source(input), table.get(), collection.get()); - } else { - result = table_merger_->Merge(Source(input), table.get(), collection.get()); - } + result = table_merger_->Merge(Source(input), table, override); } else { // This is the proper way to merge libraries, where the package name is // preserved and resource names are mangled. - result = - table_merger_->MergeAndMangle(Source(input), pkg->name, table.get(), collection.get()); + result = table_merger_->MergeAndMangle(Source(input), pkg->name, table); } if (!result) { @@ -1129,71 +1323,26 @@ class LinkCommand { } // Make sure to move the collection into the set of IFileCollections. - collections_.push_back(std::move(collection)); + merged_apks_.push_back(std::move(apk)); return true; } - bool MergeResourceTable(io::IFile* file, bool override) { - if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() << "merging resource table " - << file->GetSource()); - } - - std::unique_ptr<io::IData> data = file->OpenAsData(); - if (!data) { - context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << "failed to open file"); - return false; - } - - std::unique_ptr<ResourceTable> table = - LoadTableFromPb(file->GetSource(), data->data(), data->size(), context_->GetDiagnostics()); - if (!table) { - return false; - } - - bool result = false; - if (override) { - result = table_merger_->MergeOverlay(file->GetSource(), table.get()); - } else { - result = table_merger_->Merge(file->GetSource(), table.get()); - } - return result; - } - - bool MergeCompiledFile(io::IFile* file, ResourceFile* file_desc, bool override) { - if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() << "merging '" << file_desc->name - << "' from compiled file " - << file->GetSource()); - } - - bool result = false; - if (override) { - result = table_merger_->MergeFileOverlay(*file_desc, file); - } else { - result = table_merger_->MergeFile(*file_desc, file); - } - - if (!result) { - return false; - } - + bool MergeExportedSymbols(const Source& source, + const std::vector<SourcedResourceName>& exported_symbols) { // Add the exports of this file to the table. - for (SourcedResourceName& exported_symbol : file_desc->exported_symbols) { - if (exported_symbol.name.package.empty()) { - exported_symbol.name.package = context_->GetCompilationPackage(); + for (const SourcedResourceName& exported_symbol : exported_symbols) { + ResourceName res_name = exported_symbol.name; + if (res_name.package.empty()) { + res_name.package = context_->GetCompilationPackage(); } - ResourceNameRef res_name = exported_symbol.name; - - Maybe<ResourceName> mangled_name = - context_->GetNameMangler()->MangleName(exported_symbol.name); + Maybe<ResourceName> mangled_name = context_->GetNameMangler()->MangleName(res_name); if (mangled_name) { res_name = mangled_name.value(); } std::unique_ptr<Id> id = util::make_unique<Id>(); - id->SetSource(file_desc->source.WithLine(exported_symbol.line)); + id->SetSource(source.WithLine(exported_symbol.line)); bool result = final_table_.AddResourceAllowMangled( res_name, ConfigDescription::DefaultConfig(), std::string(), std::move(id), context_->GetDiagnostics()); @@ -1204,15 +1353,24 @@ class LinkCommand { return true; } - /** - * Takes a path to load as a ZIP file and merges the files within into the - * master ResourceTable. - * If override is true, conflicting resources are allowed to override each - * other, in order of last seen. - * - * An io::IFileCollection is created from the ZIP file and added to the set of - * io::IFileCollections that are open. - */ + bool MergeCompiledFile(const ResourceFile& compiled_file, io::IFile* file, bool override) { + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note(DiagMessage() + << "merging '" << compiled_file.name + << "' from compiled file " << compiled_file.source); + } + + if (!table_merger_->MergeFile(compiled_file, override, file)) { + return false; + } + return MergeExportedSymbols(compiled_file.source, compiled_file.exported_symbols); + } + + // Takes a path to load as a ZIP file and merges the files within into the master ResourceTable. + // If override is true, conflicting resources are allowed to override each other, in order of last + // seen. + // An io::IFileCollection is created from the ZIP file and added to the set of + // io::IFileCollections that are open. bool MergeArchive(const std::string& input, bool override) { if (context_->IsVerbose()) { context_->GetDiagnostics()->Note(DiagMessage() << "merging archive " << input); @@ -1238,18 +1396,11 @@ class LinkCommand { return !error; } - /** - * Takes a path to load and merge into the master ResourceTable. If override - * is true, - * conflicting resources are allowed to override each other, in order of last - * seen. - * - * If the file path ends with .flata, .jar, .jack, or .zip the file is treated - * as ZIP archive - * and the files within are merged individually. - * - * Otherwise the files is processed on its own. - */ + // Takes a path to load and merge into the master ResourceTable. If override is true, + // conflicting resources are allowed to override each other, in order of last seen. + // If the file path ends with .flata, .jar, .jack, or .zip the file is treated + // as ZIP archive and the files within are merged individually. + // Otherwise the file is processed on its own. bool MergePath(const std::string& path, bool override) { if (util::EndsWith(path, ".flata") || util::EndsWith(path, ".jar") || util::EndsWith(path, ".jack") || util::EndsWith(path, ".zip")) { @@ -1262,81 +1413,87 @@ class LinkCommand { return MergeFile(file, override); } - /** - * Takes a file to load and merge into the master ResourceTable. If override - * is true, - * conflicting resources are allowed to override each other, in order of last - * seen. - * - * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and - * merged into the - * master ResourceTable. If the file ends with .flat, then it is treated like - * a compiled file - * and the header data is read and merged into the final ResourceTable. - * - * All other file types are ignored. This is because these files could be - * coming from a zip, - * where we could have other files like classes.dex. - */ + // Takes an AAPT Container file (.apc/.flat) to load and merge into the master ResourceTable. + // If override is true, conflicting resources are allowed to override each other, in order of last + // seen. + // All other file types are ignored. This is because these files could be coming from a zip, + // where we could have other files like classes.dex. bool MergeFile(io::IFile* file, bool override) { const Source& src = file->GetSource(); - if (util::EndsWith(src.path, ".arsc.flat")) { - return MergeResourceTable(file, override); - - } else if (util::EndsWith(src.path, ".flat")) { - // Try opening the file and looking for an Export header. - std::unique_ptr<io::IData> data = file->OpenAsData(); - if (!data) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open"); - return false; - } - CompiledFileInputStream input_stream(data->data(), data->size()); - uint32_t num_files = 0; - if (!input_stream.ReadLittleEndian32(&num_files)) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "failed read num files"); - return false; + if (util::EndsWith(src.path, ".xml") || util::EndsWith(src.path, ".png")) { + // Since AAPT compiles these file types and appends .flat to them, seeing + // their raw extensions is a sign that they weren't compiled. + const StringPiece file_type = util::EndsWith(src.path, ".xml") ? "XML" : "PNG"; + context_->GetDiagnostics()->Error(DiagMessage(src) << "uncompiled " << file_type + << " file passed as argument. Must be " + "compiled first into .flat file."); + return false; + } else if (!util::EndsWith(src.path, ".apc") && !util::EndsWith(src.path, ".flat")) { + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring unrecognized file"); + return true; } + } - for (uint32_t i = 0; i < num_files; i++) { - pb::internal::CompiledFile compiled_file; - if (!input_stream.ReadCompiledFile(&compiled_file)) { + std::unique_ptr<io::InputStream> input_stream = file->OpenInputStream(); + if (input_stream == nullptr) { + context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open file"); + return false; + } + + if (input_stream->HadError()) { + context_->GetDiagnostics()->Error(DiagMessage(src) + << "failed to open file: " << input_stream->GetError()); + return false; + } + + ContainerReaderEntry* entry; + ContainerReader reader(input_stream.get()); + while ((entry = reader.Next()) != nullptr) { + if (entry->Type() == ContainerEntryType::kResTable) { + pb::ResourceTable pb_table; + if (!entry->GetResTable(&pb_table)) { + context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read resource table: " + << entry->GetError()); + return false; + } + + ResourceTable table; + std::string error; + if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &error)) { context_->GetDiagnostics()->Error(DiagMessage(src) - << "failed to read compiled file header"); + << "failed to deserialize resource table: " << error); return false; } - uint64_t offset, len; - if (!input_stream.ReadDataMetaData(&offset, &len)) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read data meta data"); + if (!table_merger_->Merge(src, &table, override)) { + context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to merge resource table"); + return false; + } + } else if (entry->Type() == ContainerEntryType::kResFile) { + pb::internal::CompiledFile pb_compiled_file; + off64_t offset; + size_t len; + if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &len)) { + context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to get resource file: " + << entry->GetError()); return false; } - std::unique_ptr<ResourceFile> resource_file = DeserializeCompiledFileFromPb( - compiled_file, file->GetSource(), context_->GetDiagnostics()); - if (!resource_file) { + ResourceFile resource_file; + std::string error; + if (!DeserializeCompiledFileFromPb(pb_compiled_file, &resource_file, &error)) { + context_->GetDiagnostics()->Error(DiagMessage(src) + << "failed to read compiled header: " << error); return false; } - if (!MergeCompiledFile(file->CreateFileSegment(offset, len), resource_file.get(), - override)) { + if (!MergeCompiledFile(resource_file, file->CreateFileSegment(offset, len), override)) { return false; } } - return true; - } else if (util::EndsWith(src.path, ".xml") || util::EndsWith(src.path, ".png")) { - // Since AAPT compiles these file types and appends .flat to them, seeing - // their raw extensions is a sign that they weren't compiled. - const StringPiece file_type = util::EndsWith(src.path, ".xml") ? "XML" : "PNG"; - context_->GetDiagnostics()->Error(DiagMessage(src) << "uncompiled " << file_type - << " file passed as argument. Must be " - "compiled first into .flat file."); - return false; } - - // Ignore non .flat files. This could be classes.dex or something else that - // happens - // to be in an archive. return true; } @@ -1380,15 +1537,13 @@ class LinkCommand { return true; } - /** - * Writes the AndroidManifest, ResourceTable, and all XML files referenced by - * the ResourceTable to the IArchiveWriter. - */ + // Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable + // to the IArchiveWriter. bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest, ResourceTable* table) { const bool keep_raw_values = context_->GetPackageType() == PackageType::kStaticLib; - bool result = FlattenXml(context_, manifest, "AndroidManifest.xml", keep_raw_values, - true /*utf16*/, writer); + bool result = FlattenXml(context_, *manifest, "AndroidManifest.xml", keep_raw_values, + true /*utf16*/, options_.output_format, writer); if (!result) { return false; } @@ -1403,6 +1558,7 @@ class LinkCommand { file_flattener_options.no_xml_namespaces = options_.no_xml_namespaces; file_flattener_options.update_proguard_spec = static_cast<bool>(options_.generate_proguard_rules_path); + file_flattener_options.output_format = options_.output_format; ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set); @@ -1439,12 +1595,7 @@ class LinkCommand { } } - bool success; - if (context_->GetPackageType() == PackageType::kStaticLib) { - success = FlattenTableToPb(table, writer); - } else { - success = FlattenTable(table, writer); - } + bool success = FlattenTable(table, options_.output_format, writer); if (package_to_rewrite != nullptr) { // Change the name back. @@ -1475,6 +1626,12 @@ class LinkCommand { context_->SetCompilationPackage(app_info.package); } + // Now that the compilation package is set, load the dependencies. This will also extract + // the Android framework's versionCode and versionName, if they exist. + if (!LoadSymbolsFromIncludePaths()) { + return 1; + } + ManifestFixer manifest_fixer(options_.manifest_fixer_options); if (!manifest_fixer.Consume(context_, manifest_xml.get())) { return 1; @@ -1503,10 +1660,6 @@ class LinkCommand { } } - if (!LoadSymbolsFromIncludePaths()) { - return 1; - } - TableMergerOptions table_merger_options; table_merger_options.auto_add_overlay = options_.auto_add_overlay; table_merger_ = util::make_unique<TableMerger>(context_, &final_table_, table_merger_options); @@ -1518,6 +1671,19 @@ class LinkCommand { context_->GetPackageId())); } + // Extract symbols from AndroidManifest.xml, since this isn't merged like the other XML files + // in res/**/*. + { + XmlIdCollector collector; + if (!collector.Consume(context_, manifest_xml.get())) { + return false; + } + + if (!MergeExportedSymbols(manifest_xml->file.source, manifest_xml->file.exported_symbols)) { + return false; + } + } + for (const std::string& input : input_files) { if (!MergePath(input, false)) { context_->GetDiagnostics()->Error(DiagMessage() << "failed parsing input"); @@ -1646,7 +1812,8 @@ class LinkCommand { } } - proguard::KeepSet proguard_keep_set; + proguard::KeepSet proguard_keep_set = + proguard::KeepSet(options_.generate_conditional_proguard_rules); proguard::KeepSet proguard_main_dex_keep_set; if (context_->GetPackageType() == PackageType::kStaticLib) { @@ -1714,8 +1881,7 @@ class LinkCommand { bool error = false; { - // AndroidManifest.xml has no resource name, but the CallSite is built - // from the name + // AndroidManifest.xml has no resource name, but the CallSite is built from the name // (aka, which package the AndroidManifest.xml is coming from). // So we give it a package name so it can see local resources. manifest_xml->file.name.package = context_->GetCompilationPackage(); @@ -1723,14 +1889,12 @@ class LinkCommand { XmlReferenceLinker manifest_linker; if (manifest_linker.Consume(context_, manifest_xml.get())) { if (options_.generate_proguard_rules_path && - !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path), - manifest_xml.get(), &proguard_keep_set)) { + !proguard::CollectProguardRulesForManifest(manifest_xml.get(), &proguard_keep_set)) { error = true; } if (options_.generate_main_dex_proguard_rules_path && - !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path), - manifest_xml.get(), + !proguard::CollectProguardRulesForManifest(manifest_xml.get(), &proguard_main_dex_keep_set, true)) { error = true; } @@ -1767,73 +1931,8 @@ class LinkCommand { return 1; } - if (options_.generate_java_class_path) { - // The set of packages whose R class to call in the main classes - // onResourcesLoaded callback. - std::vector<std::string> packages_to_callback; - - JavaClassGeneratorOptions template_options; - template_options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; - template_options.javadoc_annotations = options_.javadoc_annotations; - - if (context_->GetPackageType() == PackageType::kStaticLib || - options_.generate_non_final_ids) { - template_options.use_final = false; - } - - if (context_->GetPackageType() == PackageType::kSharedLib) { - template_options.use_final = false; - template_options.rewrite_callback_options = OnResourcesLoadedCallbackOptions{}; - } - - const StringPiece actual_package = context_->GetCompilationPackage(); - StringPiece output_package = context_->GetCompilationPackage(); - if (options_.custom_java_package) { - // Override the output java package to the custom one. - output_package = options_.custom_java_package.value(); - } - - // Generate the private symbols if required. - if (options_.private_symbols) { - packages_to_callback.push_back(options_.private_symbols.value()); - - // If we defined a private symbols package, we only emit Public symbols - // to the original package, and private and public symbols to the - // private package. - JavaClassGeneratorOptions options = template_options; - options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; - if (!WriteJavaFile(&final_table_, actual_package, options_.private_symbols.value(), - options)) { - return 1; - } - } - - // Generate all the symbols for all extra packages. - for (const std::string& extra_package : options_.extra_java_packages) { - packages_to_callback.push_back(extra_package); - - JavaClassGeneratorOptions options = template_options; - options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; - if (!WriteJavaFile(&final_table_, actual_package, extra_package, options)) { - return 1; - } - } - - // Generate the main public R class. - JavaClassGeneratorOptions options = template_options; - - // Only generate public symbols if we have a private package. - if (options_.private_symbols) { - options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; - } - - if (options.rewrite_callback_options) { - options.rewrite_callback_options.value().packages_to_callback = - std::move(packages_to_callback); - } - - if (!WriteJavaFile(&final_table_, actual_package, output_package, options, - options_.generate_text_symbols_path)) { + if (options_.generate_java_class_path || options_.generate_text_symbols_path) { + if (!GenerateJavaClasses()) { return 1; } } @@ -1861,13 +1960,15 @@ class LinkCommand { // A pointer to the FileCollection representing the filesystem (not archives). std::unique_ptr<io::FileCollection> file_collection_; - // A vector of IFileCollections. This is mainly here to keep ownership of the + // A vector of IFileCollections. This is mainly here to retain ownership of the // collections. std::vector<std::unique_ptr<io::IFileCollection>> collections_; - // A vector of ResourceTables. This is here to retain ownership, so that the - // SymbolTable can use these. - std::vector<std::unique_ptr<ResourceTable>> static_table_includes_; + // The set of merged APKs. This is mainly here to retain ownership of the APKs. + std::vector<std::unique_ptr<LoadedApk>> merged_apks_; + + // The set of included APKs (not merged). This is mainly here to retain ownership of the APKs. + std::vector<std::unique_ptr<LoadedApk>> static_library_includes_; // The set of shared libraries being used, mapping their assigned package ID to package name. std::map<size_t, std::string> shared_libs_; @@ -1890,6 +1991,7 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { bool verbose = false; bool shared_lib = false; bool static_lib = false; + bool proto_format = false; Maybe<std::string> stable_id_file_path; std::vector<std::string> split_args; Flags flags = @@ -1916,6 +2018,9 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { .OptionalFlag("--proguard-main-dex", "Output file for generated Proguard rules for the main dex.", &options.generate_main_dex_proguard_rules_path) + .OptionalSwitch("--proguard-conditional-keep-rules", + "Generate conditional Proguard keep rules.", + &options.generate_conditional_proguard_rules) .OptionalSwitch("--no-auto-version", "Disables automatic style and layout SDK versioning.", &options.no_auto_version) @@ -1970,6 +2075,10 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { .OptionalSwitch("--shared-lib", "Generates a shared Android runtime library.", &shared_lib) .OptionalSwitch("--static-lib", "Generate a static Android library.", &static_lib) + .OptionalSwitch("--proto-format", + "Generates compiled resources in Protobuf format.\n" + "Suitable as input to the bundle tool for generating an App Bundle.", + &proto_format) .OptionalSwitch("--no-static-lib-packages", "Merge all library resources under the app's package.", &options.no_static_lib_packages) @@ -2056,21 +2165,25 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { context.SetVerbose(verbose); } - if (shared_lib && static_lib) { - context.GetDiagnostics()->Error(DiagMessage() - << "only one of --shared-lib and --static-lib can be defined"); + if (int{shared_lib} + int{static_lib} + int{proto_format} > 1) { + context.GetDiagnostics()->Error( + DiagMessage() + << "only one of --shared-lib, --static-lib, or --proto_format can be defined"); return 1; } + // The default build type. + context.SetPackageType(PackageType::kApp); + context.SetPackageId(kAppPackageId); + if (shared_lib) { context.SetPackageType(PackageType::kSharedLib); context.SetPackageId(0x00); } else if (static_lib) { context.SetPackageType(PackageType::kStaticLib); - context.SetPackageId(kAppPackageId); - } else { - context.SetPackageType(PackageType::kApp); - context.SetPackageId(kAppPackageId); + options.output_format = OutputFormat::kProto; + } else if (proto_format) { + options.output_format = OutputFormat::kProto; } if (package_id) { diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 9d71775889d4..1bdb762528b6 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -17,7 +17,10 @@ #include <memory> #include <vector> +#include "android-base/file.h" #include "android-base/stringprintf.h" + +#include "androidfw/ResourceTypes.h" #include "androidfw/StringPiece.h" #include "Diagnostics.h" @@ -29,19 +32,24 @@ #include "cmd/Util.h" #include "configuration/ConfigurationParser.h" #include "filter/AbiFilter.h" -#include "flatten/TableFlattener.h" -#include "flatten/XmlFlattener.h" -#include "io/BigBufferInputStream.h" +#include "format/binary/TableFlattener.h" +#include "format/binary/XmlFlattener.h" +#include "io/BigBufferStream.h" #include "io/Util.h" +#include "optimize/MultiApkGenerator.h" #include "optimize/ResourceDeduper.h" #include "optimize/VersionCollapser.h" #include "split/TableSplitter.h" #include "util/Files.h" +#include "util/Util.h" using ::aapt::configuration::Abi; using ::aapt::configuration::Artifact; using ::aapt::configuration::PostProcessingConfiguration; +using ::android::ResTable_config; using ::android::StringPiece; +using ::android::base::ReadFileToString; +using ::android::base::StringAppendF; using ::android::base::StringPrintf; namespace aapt { @@ -67,6 +75,10 @@ struct OptimizeOptions { TableFlattenerOptions table_flattener_options; Maybe<PostProcessingConfiguration> configuration; + + // Set of artifacts to keep when generating multi-APK splits. If the list is empty, all artifacts + // are kept and will be written as output. + std::unordered_set<std::string> kept_artifacts; }; class OptimizeContext : public IAaptContext { @@ -188,42 +200,15 @@ class OptimizeCommand { } if (options_.configuration && options_.output_dir) { - PostProcessingConfiguration& config = options_.configuration.value(); - - // For now, just write out the stripped APK since ABI splitting doesn't modify anything else. - for (const Artifact& artifact : config.artifacts) { - if (artifact.abi_group) { - const std::string& group = artifact.abi_group.value(); - - auto abi_group = config.abi_groups.find(group); - // TODO: Remove validation when configuration parser ensures referential integrity. - if (abi_group == config.abi_groups.end()) { - context_->GetDiagnostics()->Note( - DiagMessage() << "could not find referenced ABI group '" << group << "'"); - return 1; - } - FilterChain filters; - filters.AddFilter(AbiFilter::FromAbiList(abi_group->second)); - - const std::string& path = apk->GetSource().path; - const StringPiece ext = file::GetExtension(path); - const std::string name = path.substr(0, path.rfind(ext.to_string())); - - // Name is hard coded for now since only one split dimension is supported. - // TODO: Incorporate name generation into the configuration objects. - const std::string file_name = - StringPrintf("%s.%s%s", name.c_str(), group.c_str(), ext.data()); - std::string out = options_.output_dir.value(); - file::AppendPath(&out, file_name); - - std::unique_ptr<IArchiveWriter> writer = - CreateZipFileArchiveWriter(context_->GetDiagnostics(), out); - - if (!apk->WriteToArchive(context_, options_.table_flattener_options, &filters, - writer.get())) { - return 1; - } - } + MultiApkGenerator generator{apk.get(), context_}; + MultiApkGeneratorOptions generator_options = { + options_.output_dir.value(), + options_.configuration.value(), + options_.table_flattener_options, + options_.kept_artifacts, + }; + if (!generator.FromBaseApk(generator_options)) { + return 1; } } @@ -260,7 +245,7 @@ class OptimizeCommand { for (auto& entry : type->entries) { for (auto& config_value : entry->values) { - FileReference* file_ref = ValueCast<FileReference>(config_value->value.get()); + auto* file_ref = ValueCast<FileReference>(config_value->value.get()); if (file_ref == nullptr) { continue; } @@ -280,10 +265,8 @@ class OptimizeCommand { for (auto& entry : config_sorted_files) { FileReference* file_ref = entry.second; - uint32_t compression_flags = - file_ref->file->WasCompressed() ? ArchiveEntry::kCompress : 0u; - if (!io::CopyFileToArchive(context_, file_ref->file, *file_ref->path, compression_flags, - writer)) { + if (!io::CopyFileToArchivePreserveCompression(context_, file_ref->file, *file_ref->path, + writer)) { return false; } } @@ -297,42 +280,36 @@ class OptimizeCommand { } io::BigBufferInputStream table_buffer_in(&table_buffer); - if (!io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc", - ArchiveEntry::kAlign, writer)) { - return false; - } - return true; + return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc", + ArchiveEntry::kAlign, writer); } OptimizeOptions options_; OptimizeContext* context_; }; -bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk, - OptimizeOptions* out_options) { - io::IFile* manifest_file = apk->GetFileCollection()->FindFile("AndroidManifest.xml"); - if (manifest_file == nullptr) { - context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) - << "missing AndroidManifest.xml"); +bool ExtractWhitelistFromConfig(const std::string& path, OptimizeContext* context, + OptimizeOptions* options) { + std::string contents; + if (!ReadFileToString(path, &contents, true)) { + context->GetDiagnostics()->Error(DiagMessage() + << "failed to parse whitelist from config file: " << path); return false; } - - std::unique_ptr<io::IData> data = manifest_file->OpenAsData(); - if (data == nullptr) { - context->GetDiagnostics()->Error(DiagMessage(manifest_file->GetSource()) - << "failed to open file"); - return false; + for (const StringPiece& resource_name : util::Tokenize(contents, ',')) { + options->table_flattener_options.whitelisted_resources.insert(resource_name.to_string()); } + return true; +} - std::unique_ptr<xml::XmlResource> manifest = xml::Inflate( - data->data(), data->size(), context->GetDiagnostics(), manifest_file->GetSource()); +bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk, + OptimizeOptions* out_options) { + const xml::XmlResource* manifest = apk->GetManifest(); if (manifest == nullptr) { - context->GetDiagnostics()->Error(DiagMessage() << "failed to read binary AndroidManifest.xml"); return false; } - Maybe<AppInfo> app_info = - ExtractAppInfoFromBinaryManifest(manifest.get(), context->GetDiagnostics()); + Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics()); if (!app_info) { context->GetDiagnostics()->Error(DiagMessage() << "failed to extract data from AndroidManifest.xml"); @@ -348,21 +325,36 @@ int Optimize(const std::vector<StringPiece>& args) { OptimizeContext context; OptimizeOptions options; Maybe<std::string> config_path; + Maybe<std::string> whitelist_path; Maybe<std::string> target_densities; + Maybe<std::string> target_abis; std::vector<std::string> configs; std::vector<std::string> split_args; + std::unordered_set<std::string> kept_artifacts; bool verbose = false; + bool print_only = false; Flags flags = Flags() .OptionalFlag("-o", "Path to the output APK.", &options.output_path) .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir) .OptionalFlag("-x", "Path to XML configuration file.", &config_path) + .OptionalSwitch("-p", "Print the multi APK artifacts and exit.", &print_only) .OptionalFlag( "--target-densities", "Comma separated list of the screen densities that the APK will be optimized for.\n" "All the resources that would be unused on devices of the given densities will be \n" "removed from the APK.", &target_densities) + .OptionalFlag("--whitelist-config-path", + "Path to the whitelist.cfg file containing whitelisted resources \n" + "whose names should not be altered in final resource tables.", + &whitelist_path) + .OptionalFlag( + "--target-abis", + "Comma separated list of the CPU ABIs that the APK will be optimized for.\n" + "All the native libraries that would be unused on devices of the given ABIs will \n" + "be removed from the APK.", + &target_abis) .OptionalFlagList("-c", "Comma separated list of configurations to include. The default\n" "is all configurations.", @@ -372,10 +364,17 @@ int Optimize(const std::vector<StringPiece>& args) { "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n" "On Windows, use a semicolon ';' separator instead.", &split_args) + .OptionalFlagList("--keep-artifacts", + "Comma separated list of artifacts to keep. If none are specified,\n" + "all artifacts will be kept.", + &kept_artifacts) .OptionalSwitch("--enable-sparse-encoding", "Enables encoding sparse entries using a binary search tree.\n" "This decreases APK size at the cost of resource retrieval performance.", &options.table_flattener_options.use_sparse_entries) + .OptionalSwitch("--enable-resource-obfuscation", + "Enables obfuscation of key string pool to single value", + &options.table_flattener_options.collapse_key_stringpool) .OptionalSwitch("-v", "Enables verbose logging", &verbose); if (!flags.Parse("aapt2 optimize", args, &std::cerr)) { @@ -388,18 +387,19 @@ int Optimize(const std::vector<StringPiece>& args) { return 1; } - std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]); + const std::string& apk_path = flags.GetArgs()[0]; + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics()); if (!apk) { return 1; } context.SetVerbose(verbose); + IDiagnostics* diag = context.GetDiagnostics(); if (target_densities) { // Parse the target screen densities. for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) { - Maybe<uint16_t> target_density = - ParseTargetDensityParameter(config_str, context.GetDiagnostics()); + Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag); if (!target_density) { return 1; } @@ -409,7 +409,7 @@ int Optimize(const std::vector<StringPiece>& args) { std::unique_ptr<IConfigFilter> filter; if (!configs.empty()) { - filter = ParseConfigFilterParameters(configs, context.GetDiagnostics()); + filter = ParseConfigFilterParameters(configs, diag); if (filter == nullptr) { return 1; } @@ -418,28 +418,64 @@ int Optimize(const std::vector<StringPiece>& args) { // Parse the split parameters. for (const std::string& split_arg : split_args) { - options.split_paths.push_back({}); - options.split_constraints.push_back({}); - if (!ParseSplitParameter(split_arg, context.GetDiagnostics(), &options.split_paths.back(), + options.split_paths.emplace_back(); + options.split_constraints.emplace_back(); + if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(), &options.split_constraints.back())) { return 1; } } if (config_path) { - if (!options.output_dir) { - context.GetDiagnostics()->Error( - DiagMessage() << "Output directory is required when using a configuration file"); - return 1; - } std::string& path = config_path.value(); Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path); if (for_path) { - options.configuration = for_path.value().WithDiagnostics(context.GetDiagnostics()).Parse(); + options.configuration = for_path.value().WithDiagnostics(diag).Parse(); } else { - context.GetDiagnostics()->Error(DiagMessage() << "Could not parse config file " << path); + diag->Error(DiagMessage() << "Could not parse config file " << path); return 1; } + + if (print_only) { + std::vector<std::string> names; + const PostProcessingConfiguration& config = options.configuration.value(); + if (!config.AllArtifactNames(file::GetFilename(apk_path), &names, diag)) { + diag->Error(DiagMessage() << "Failed to generate output artifact list"); + return 1; + } + + for (const auto& name : names) { + std::cout << name << std::endl; + } + return 0; + } + + if (!kept_artifacts.empty()) { + for (const auto& artifact_str : kept_artifacts) { + for (const auto& artifact : util::Tokenize(artifact_str, ',')) { + options.kept_artifacts.insert(artifact.to_string()); + } + } + } + + // Since we know that we are going to process the APK (not just print targets), make sure we + // have somewhere to write them to. + if (!options.output_dir) { + diag->Error(DiagMessage() << "Output directory is required when using a configuration file"); + return 1; + } + } else if (print_only) { + diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations"); + return 1; + } + + if (options.table_flattener_options.collapse_key_stringpool) { + if (whitelist_path) { + std::string& path = whitelist_path.value(); + if (!ExtractWhitelistFromConfig(path, &context, &options)) { + return 1; + } + } } if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) { diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index 90fc10add7e6..8b3a6701b409 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -229,9 +229,10 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, return doc; } -static Maybe<std::string> ExtractCompiledString(xml::Attribute* attr, std::string* out_error) { - if (attr->compiled_value != nullptr) { - String* compiled_str = ValueCast<String>(attr->compiled_value.get()); +static Maybe<std::string> ExtractCompiledString(const xml::Attribute& attr, + std::string* out_error) { + if (attr.compiled_value != nullptr) { + const String* compiled_str = ValueCast<String>(attr.compiled_value.get()); if (compiled_str != nullptr) { if (!compiled_str->value->empty()) { return *compiled_str->value; @@ -245,16 +246,16 @@ static Maybe<std::string> ExtractCompiledString(xml::Attribute* attr, std::strin } // Fallback to the plain text value if there is one. - if (!attr->value.empty()) { - return attr->value; + if (!attr.value.empty()) { + return attr.value; } *out_error = "value is an empty string"; return {}; } -static Maybe<uint32_t> ExtractCompiledInt(xml::Attribute* attr, std::string* out_error) { - if (attr->compiled_value != nullptr) { - BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr->compiled_value.get()); +static Maybe<uint32_t> ExtractCompiledInt(const xml::Attribute& attr, std::string* out_error) { + if (attr.compiled_value != nullptr) { + const BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr.compiled_value.get()); if (compiled_prim != nullptr) { if (compiled_prim->value.dataType >= android::Res_value::TYPE_FIRST_INT && compiled_prim->value.dataType <= android::Res_value::TYPE_LAST_INT) { @@ -266,19 +267,19 @@ static Maybe<uint32_t> ExtractCompiledInt(xml::Attribute* attr, std::string* out } // Fallback to the plain text value if there is one. - Maybe<uint32_t> integer = ResourceUtils::ParseInt(attr->value); + Maybe<uint32_t> integer = ResourceUtils::ParseInt(attr.value); if (integer) { return integer; } std::stringstream error_msg; - error_msg << "'" << attr->value << "' is not a valid integer"; + error_msg << "'" << attr.value << "' is not a valid integer"; *out_error = error_msg.str(); return {}; } -static Maybe<int> ExtractSdkVersion(xml::Attribute* attr, std::string* out_error) { - if (attr->compiled_value != nullptr) { - BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr->compiled_value.get()); +static Maybe<int> ExtractSdkVersion(const xml::Attribute& attr, std::string* out_error) { + if (attr.compiled_value != nullptr) { + const BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr.compiled_value.get()); if (compiled_prim != nullptr) { if (compiled_prim->value.dataType >= android::Res_value::TYPE_FIRST_INT && compiled_prim->value.dataType <= android::Res_value::TYPE_LAST_INT) { @@ -288,7 +289,7 @@ static Maybe<int> ExtractSdkVersion(xml::Attribute* attr, std::string* out_error return {}; } - String* compiled_str = ValueCast<String>(attr->compiled_value.get()); + const String* compiled_str = ValueCast<String>(attr.compiled_value.get()); if (compiled_str != nullptr) { Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(*compiled_str->value); if (sdk_version) { @@ -303,19 +304,20 @@ static Maybe<int> ExtractSdkVersion(xml::Attribute* attr, std::string* out_error } // Fallback to the plain text value if there is one. - Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(attr->value); + Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(attr.value); if (sdk_version) { return sdk_version; } std::stringstream error_msg; - error_msg << "'" << attr->value << "' is not a valid SDK version"; + error_msg << "'" << attr.value << "' is not a valid SDK version"; *out_error = error_msg.str(); return {}; } -Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag) { +Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res, + IDiagnostics* diag) { // Make sure the first element is <manifest> with package attribute. - xml::Element* manifest_el = xml_res->root.get(); + const xml::Element* manifest_el = xml_res.root.get(); if (manifest_el == nullptr) { return {}; } @@ -323,63 +325,63 @@ Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiag AppInfo app_info; if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") { - diag->Error(DiagMessage(xml_res->file.source) << "root tag must be <manifest>"); + diag->Error(DiagMessage(xml_res.file.source) << "root tag must be <manifest>"); return {}; } - xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package"); + const xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package"); if (!package_attr) { - diag->Error(DiagMessage(xml_res->file.source) << "<manifest> must have a 'package' attribute"); + diag->Error(DiagMessage(xml_res.file.source) << "<manifest> must have a 'package' attribute"); return {}; } std::string error_msg; - Maybe<std::string> maybe_package = ExtractCompiledString(package_attr, &error_msg); + Maybe<std::string> maybe_package = ExtractCompiledString(*package_attr, &error_msg); if (!maybe_package) { - diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number)) + diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) << "invalid package name: " << error_msg); return {}; } app_info.package = maybe_package.value(); - if (xml::Attribute* version_code_attr = + if (const xml::Attribute* version_code_attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) { - Maybe<uint32_t> maybe_code = ExtractCompiledInt(version_code_attr, &error_msg); + Maybe<uint32_t> maybe_code = ExtractCompiledInt(*version_code_attr, &error_msg); if (!maybe_code) { - diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number)) + diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) << "invalid android:versionCode: " << error_msg); return {}; } app_info.version_code = maybe_code.value(); } - if (xml::Attribute* revision_code_attr = + if (const xml::Attribute* revision_code_attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) { - Maybe<uint32_t> maybe_code = ExtractCompiledInt(revision_code_attr, &error_msg); + Maybe<uint32_t> maybe_code = ExtractCompiledInt(*revision_code_attr, &error_msg); if (!maybe_code) { - diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number)) + diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) << "invalid android:revisionCode: " << error_msg); return {}; } app_info.revision_code = maybe_code.value(); } - if (xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) { - Maybe<std::string> maybe_split_name = ExtractCompiledString(split_name_attr, &error_msg); + if (const xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) { + Maybe<std::string> maybe_split_name = ExtractCompiledString(*split_name_attr, &error_msg); if (!maybe_split_name) { - diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number)) + diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) << "invalid split name: " << error_msg); return {}; } app_info.split_name = maybe_split_name.value(); } - if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) { - if (xml::Attribute* min_sdk = + if (const xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) { + if (const xml::Attribute* min_sdk = uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) { - Maybe<int> maybe_sdk = ExtractSdkVersion(min_sdk, &error_msg); + Maybe<int> maybe_sdk = ExtractSdkVersion(*min_sdk, &error_msg); if (!maybe_sdk) { - diag->Error(DiagMessage(xml_res->file.source.WithLine(uses_sdk_el->line_number)) + diag->Error(DiagMessage(xml_res.file.source.WithLine(uses_sdk_el->line_number)) << "invalid android:minSdkVersion: " << error_msg); return {}; } diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h index fd9b39c67380..7611c1526104 100644 --- a/tools/aapt2/cmd/Util.h +++ b/tools/aapt2/cmd/Util.h @@ -57,7 +57,8 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, const SplitConstraints& constraints); // Extracts relevant info from the AndroidManifest.xml. -Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag); +Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res, + IDiagnostics* diag); } // namespace aapt diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp index 73a90da6baf0..238e339c05af 100644 --- a/tools/aapt2/compile/InlineXmlFormatParser.cpp +++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp @@ -69,10 +69,10 @@ class Visitor : public xml::PackageAwareVisitor { // Use an empty string for the compilation package because we don't want to default to // the local package if the user specified name="style" or something. This should just // be the default namespace. - Maybe<xml::ExtractedPackage> maybe_pkg = TransformPackageAlias(name.package, {}); + Maybe<xml::ExtractedPackage> maybe_pkg = TransformPackageAlias(name.package); if (!maybe_pkg) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "invalid namespace prefix '" - << name.package << "'"); + context_->GetDiagnostics()->Error(DiagMessage(src) + << "invalid namespace prefix '" << name.package << "'"); error_ = true; return; } @@ -118,10 +118,11 @@ bool InlineXmlFormatParser::Consume(IAaptContext* context, xml::XmlResource* doc size_t name_suffix_counter = 0; for (const InlineDeclaration& decl : visitor.GetInlineDeclarations()) { - auto new_doc = util::make_unique<xml::XmlResource>(); - new_doc->file.config = doc->file.config; - new_doc->file.source = doc->file.source.WithLine(decl.el->line_number); - new_doc->file.name = doc->file.name; + // Create a new XmlResource with the same ResourceFile as the base XmlResource. + auto new_doc = util::make_unique<xml::XmlResource>(doc->file); + + // Attach the line number. + new_doc->file.source.line = decl.el->line_number; // Modify the new entry name. We need to suffix the entry with a number to // avoid local collisions, then mangle it with the empty package, such that it won't show up @@ -163,7 +164,7 @@ bool InlineXmlFormatParser::Consume(IAaptContext* context, xml::XmlResource* doc // Add the inline attribute to the parent. parent_el->attributes.push_back(xml::Attribute{decl.attr_namespace_uri, decl.attr_name, - "@" + new_doc->file.name.ToString()}); + "@" + new_doc->file.name.to_string()}); // Delete the subtree. for (auto iter = parent_el->children.begin(); iter != parent_el->children.end(); ++iter) { diff --git a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp index a4c602c29b86..2b4ab96d5c3a 100644 --- a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp +++ b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp @@ -54,6 +54,7 @@ TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) { </View1>)"); doc->file.name = test::ParseNameOrDie("layout/main"); + doc->file.type = ResourceFile::Type::kProtoXml; InlineXmlFormatParser parser; ASSERT_TRUE(parser.Consume(context.get(), doc.get())); @@ -81,6 +82,9 @@ TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) { // Make sure the generated reference is correct. EXPECT_THAT(extracted_doc->file.name, Eq(name_ref)); + // Make sure the ResourceFile::Type is the same. + EXPECT_THAT(extracted_doc->file.type, Eq(ResourceFile::Type::kProtoXml)); + // Verify the structure of the extracted XML. el = extracted_doc->root.get(); ASSERT_THAT(el, NotNull()); diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp index 5af91fdc71b9..bc2e6990433c 100644 --- a/tools/aapt2/compile/PngChunkFilter.cpp +++ b/tools/aapt2/compile/PngChunkFilter.cpp @@ -21,8 +21,8 @@ #include "io/Io.h" -using android::StringPiece; -using android::base::StringPrintf; +using ::android::StringPiece; +using ::android::base::StringPrintf; namespace aapt { diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp index 871ed4f01e77..36c24bc4a0fd 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp @@ -24,8 +24,8 @@ #include "compile/Pseudolocalizer.h" #include "util/Util.h" -using android::StringPiece; -using android::StringPiece16; +using ::android::StringPiece; +using ::android::StringPiece16; namespace aapt { @@ -215,7 +215,7 @@ std::unique_ptr<StyledString> PseudolocalizeStyledString(StyledString* string, namespace { -class Visitor : public RawValueVisitor { +class Visitor : public ValueVisitor { public: // Either value or item will be populated upon visiting the value. std::unique_ptr<Value> value; diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp index bdccf8bcae3a..b99240f0a40a 100644 --- a/tools/aapt2/configuration/ConfigurationParser.cpp +++ b/tools/aapt2/configuration/ConfigurationParser.cpp @@ -27,9 +27,11 @@ #include "ConfigDescription.h" #include "Diagnostics.h" +#include "ResourceUtils.h" #include "io/File.h" #include "io/FileSystem.h" -#include "io/StringInputStream.h" +#include "io/StringStream.h" +#include "util/Files.h" #include "util/Maybe.h" #include "util/Util.h" #include "xml/XmlActionExecutor.h" @@ -58,6 +60,7 @@ using ::aapt::xml::XmlActionExecutor; using ::aapt::xml::XmlActionExecutorPolicy; using ::aapt::xml::XmlNodeAction; using ::android::base::ReadFileToString; +using ::android::StringPiece; const std::unordered_map<std::string, Abi> kStringToAbiMap = { {"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a}, {"arm64-v8a", Abi::kArm64V8a}, @@ -116,56 +119,141 @@ const std::string& AbiToString(Abi abi) { * success, or false if the either the placeholder is not found in the name, or the value is not * present and the placeholder was. */ -static bool ReplacePlaceholder(const std::string& placeholder, const Maybe<std::string>& value, +static bool ReplacePlaceholder(const StringPiece& placeholder, const Maybe<StringPiece>& value, std::string* name, IDiagnostics* diag) { - size_t offset = name->find(placeholder); - if (value) { - if (offset == std::string::npos) { + size_t offset = name->find(placeholder.data()); + bool found = (offset != std::string::npos); + + // Make sure the placeholder was present if the desired value is present. + if (!found) { + if (value) { diag->Error(DiagMessage() << "Missing placeholder for artifact: " << placeholder); return false; } - name->replace(offset, placeholder.length(), value.value()); return true; } + DCHECK(found) << "Missing return path for placeholder not found"; + // Make sure the placeholder was not present if the desired value was not present. - bool result = (offset == std::string::npos); - if (!result) { + if (!value) { diag->Error(DiagMessage() << "Placeholder present but no value for artifact: " << placeholder); + return false; + } + + name->replace(offset, placeholder.length(), value.value().data()); + + // Make sure there was only one instance of the placeholder. + if (name->find(placeholder.data()) != std::string::npos) { + diag->Error(DiagMessage() << "Placeholder present multiple times: " << placeholder); + return false; + } + return true; +} + +/** + * Returns the common artifact base name from a template string. + */ +Maybe<std::string> ToBaseName(std::string result, const StringPiece& apk_name, IDiagnostics* diag) { + const StringPiece ext = file::GetExtension(apk_name); + size_t end_index = apk_name.to_string().rfind(ext.to_string()); + const std::string base_name = + (end_index != std::string::npos) ? std::string{apk_name.begin(), end_index} : ""; + + // Base name is optional. + if (result.find("${basename}") != std::string::npos) { + Maybe<StringPiece> maybe_base_name = + base_name.empty() ? Maybe<StringPiece>{} : Maybe<StringPiece>{base_name}; + if (!ReplacePlaceholder("${basename}", maybe_base_name, &result, diag)) { + return {}; + } } + + // Extension is optional. + if (result.find("${ext}") != std::string::npos) { + // Make sure we disregard the '.' in the extension when replacing the placeholder. + if (!ReplacePlaceholder("${ext}", {ext.substr(1)}, &result, diag)) { + return {}; + } + } else { + // If no extension is specified, and the name template does not end in the current extension, + // add the existing extension. + if (!util::EndsWith(result, ext)) { + result.append(ext.to_string()); + } + } + return result; } -Maybe<std::string> Artifact::ToArtifactName(const std::string& format, IDiagnostics* diag) const { - std::string result = format; +Maybe<std::string> Artifact::ToArtifactName(const StringPiece& format, const StringPiece& apk_name, + IDiagnostics* diag) const { + Maybe<std::string> base = ToBaseName(format.to_string(), apk_name, diag); + if (!base) { + return {}; + } + std::string result = std::move(base.value()); - if (!ReplacePlaceholder("{abi}", abi_group, &result, diag)) { + if (!ReplacePlaceholder("${abi}", abi_group, &result, diag)) { return {}; } - if (!ReplacePlaceholder("{density}", screen_density_group, &result, diag)) { + if (!ReplacePlaceholder("${density}", screen_density_group, &result, diag)) { return {}; } - if (!ReplacePlaceholder("{locale}", locale_group, &result, diag)) { + if (!ReplacePlaceholder("${locale}", locale_group, &result, diag)) { return {}; } - if (!ReplacePlaceholder("{sdk}", android_sdk_group, &result, diag)) { + if (!ReplacePlaceholder("${sdk}", android_sdk_group, &result, diag)) { return {}; } - if (!ReplacePlaceholder("{feature}", device_feature_group, &result, diag)) { + if (!ReplacePlaceholder("${feature}", device_feature_group, &result, diag)) { return {}; } - if (!ReplacePlaceholder("{gl}", gl_texture_group, &result, diag)) { + if (!ReplacePlaceholder("${gl}", gl_texture_group, &result, diag)) { return {}; } return result; } +Maybe<std::string> Artifact::Name(const StringPiece& apk_name, IDiagnostics* diag) const { + if (!name) { + return {}; + } + + return ToBaseName(name.value(), apk_name, diag); +} + +bool PostProcessingConfiguration::AllArtifactNames(const StringPiece& apk_name, + std::vector<std::string>* artifact_names, + IDiagnostics* diag) const { + for (const auto& artifact : artifacts) { + Maybe<std::string> name; + if (artifact.name) { + name = artifact.Name(apk_name, diag); + } else { + if (!artifact_format) { + diag->Error(DiagMessage() << "No global artifact template and an artifact name is missing"); + return false; + } + name = artifact.ToArtifactName(artifact_format.value(), apk_name, diag); + } + + if (!name) { + return false; + } + + artifact_names->push_back(std::move(name.value())); + } + + return true; +} + } // namespace configuration /** Returns a ConfigurationParser for the file located at the provided path. */ @@ -242,15 +330,32 @@ Maybe<PostProcessingConfiguration> ConfigurationParser::Parse() { // TODO: Validate all references in the configuration are valid. It should be safe to assume from // this point on that any references from one section to another will be present. + // TODO: Automatically arrange artifacts so that they match Play Store multi-APK requirements. + // see: https://developer.android.com/google/play/publishing/multiple-apks.html + // + // For now, make sure the version codes are unique. + std::vector<Artifact>& artifacts = config.artifacts; + std::sort(artifacts.begin(), artifacts.end()); + if (std::adjacent_find(artifacts.begin(), artifacts.end()) != artifacts.end()) { + diag_->Error(DiagMessage() << "Configuration has duplicate versions"); + return {}; + } + return {config}; } ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ = [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + // This will be incremented later so the first version will always be different to the base APK. + int current_version = (config->artifacts.empty()) ? 0 : config->artifacts.back().version; + Artifact artifact{}; + Maybe<int> version; for (const auto& attr : root_element->attributes) { if (attr.name == "name") { artifact.name = attr.value; + } else if (attr.name == "version") { + version = std::stoi(attr.value); } else if (attr.name == "abi-group") { artifact.abi_group = {attr.value}; } else if (attr.name == "screen-density-group") { @@ -268,6 +373,9 @@ ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ = << attr.value); } } + + artifact.version = (version) ? version.value() : current_version + 1; + config->artifacts.push_back(artifact); return true; }; @@ -333,7 +441,10 @@ ConfigurationParser::ActionHandler ConfigurationParser::screen_density_group_han if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { ConfigDescription config_descriptor; const android::StringPiece& text = TrimWhitespace(t->text); - if (ConfigDescription::Parse(text, &config_descriptor)) { + bool parsed = ConfigDescription::Parse(text, &config_descriptor); + if (parsed && + (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) == + android::ResTable_config::CONFIG_DENSITY)) { // Copy the density with the minimum SDK version stripped out. group.push_back(config_descriptor.CopyWithoutSdkVersion()); } else { @@ -366,17 +477,25 @@ ConfigurationParser::ActionHandler ConfigurationParser::locale_group_handler_ = << child->name); valid = false; } else { - Locale entry; - for (const auto& attr : child->attributes) { - if (attr.name == "lang") { - entry.lang = {attr.value}; - } else if (attr.name == "region") { - entry.region = {attr.value}; - } else { - diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value); + for (auto& node : child->children) { + xml::Text* t; + if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { + ConfigDescription config_descriptor; + const android::StringPiece& text = TrimWhitespace(t->text); + bool parsed = ConfigDescription::Parse(text, &config_descriptor); + if (parsed && + (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) == + android::ResTable_config::CONFIG_LOCALE)) { + // Copy the locale with the minimum SDK version stripped out. + group.push_back(config_descriptor.CopyWithoutSdkVersion()); + } else { + diag->Error(DiagMessage() + << "Could not parse config descriptor for screen-density: " << text); + valid = false; + } + break; } } - group.push_back(entry); } } @@ -390,8 +509,8 @@ ConfigurationParser::ActionHandler ConfigurationParser::android_sdk_group_handle return false; } - auto& group = config->android_sdk_groups[label]; bool valid = true; + bool found = false; for (auto* child : root_element->GetChildElements()) { if (child->name != "android-sdk") { @@ -401,11 +520,11 @@ ConfigurationParser::ActionHandler ConfigurationParser::android_sdk_group_handle AndroidSdk entry; for (const auto& attr : child->attributes) { if (attr.name == "minSdkVersion") { - entry.min_sdk_version = {attr.value}; + entry.min_sdk_version = ResourceUtils::ParseSdkVersion(attr.value); } else if (attr.name == "targetSdkVersion") { - entry.target_sdk_version = {attr.value}; + entry.target_sdk_version = ResourceUtils::ParseSdkVersion(attr.value); } else if (attr.name == "maxSdkVersion") { - entry.max_sdk_version = {attr.value}; + entry.max_sdk_version = ResourceUtils::ParseSdkVersion(attr.value); } else { diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value); } @@ -422,7 +541,11 @@ ConfigurationParser::ActionHandler ConfigurationParser::android_sdk_group_handle } } - group.push_back(entry); + config->android_sdk_groups[label] = entry; + if (found) { + valid = false; + } + found = true; } } diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h index 28c355e39643..c5d3284c33f4 100644 --- a/tools/aapt2/configuration/ConfigurationParser.h +++ b/tools/aapt2/configuration/ConfigurationParser.h @@ -17,6 +17,7 @@ #ifndef AAPT2_CONFIGURATION_H #define AAPT2_CONFIGURATION_H +#include <set> #include <string> #include <unordered_map> #include <vector> @@ -33,10 +34,20 @@ namespace configuration { template<class T> using Group = std::unordered_map<std::string, std::vector<T>>; +/** A mapping of group label to a single configuration item. */ +template <class T> +using Entry = std::unordered_map<std::string, T>; + /** Output artifact configuration options. */ struct Artifact { /** Name to use for output of processing foo.apk -> foo.<name>.apk. */ - std::string name; + Maybe<std::string> name; + /** + * Value to add to the base Android manifest versionCode. If it is not present in the + * configuration file, it is set to the previous artifact + 1. If the first artifact does not have + * a value, artifacts are a 1 based index. + */ + int version; /** If present, uses the ABI group with this name. */ Maybe<std::string> abi_group; /** If present, uses the screen density group with this name. */ @@ -51,7 +62,20 @@ struct Artifact { Maybe<std::string> gl_texture_group; /** Convert an artifact name template into a name string based on configuration contents. */ - Maybe<std::string> ToArtifactName(const std::string& format, IDiagnostics* diag) const; + Maybe<std::string> ToArtifactName(const android::StringPiece& format, + const android::StringPiece& apk_name, IDiagnostics* diag) const; + + /** Convert an artifact name template into a name string based on configuration contents. */ + Maybe<std::string> Name(const android::StringPiece& apk_name, IDiagnostics* diag) const; + + bool operator<(const Artifact& rhs) const { + // TODO(safarmer): Order by play store multi-APK requirements. + return version < rhs.version; + } + + bool operator==(const Artifact& rhs) const { + return version == rhs.version; + } }; /** Enumeration of currently supported ABIs. */ @@ -95,11 +119,17 @@ struct AndroidManifest { }; struct AndroidSdk { - Maybe<std::string> min_sdk_version; - Maybe<std::string> target_sdk_version; - Maybe<std::string> max_sdk_version; + Maybe<int> min_sdk_version; + Maybe<int> target_sdk_version; + Maybe<int> max_sdk_version; Maybe<AndroidManifest> manifest; + static AndroidSdk ForMinSdk(int min_sdk) { + AndroidSdk sdk; + sdk.min_sdk_version = min_sdk; + return sdk; + } + inline friend bool operator==(const AndroidSdk& lhs, const AndroidSdk& rhs) { return lhs.min_sdk_version == rhs.min_sdk_version && lhs.target_sdk_version == rhs.target_sdk_version && @@ -129,10 +159,14 @@ struct PostProcessingConfiguration { Group<Abi> abi_groups; Group<ConfigDescription> screen_density_groups; - Group<Locale> locale_groups; - Group<AndroidSdk> android_sdk_groups; + Group<ConfigDescription> locale_groups; + Entry<AndroidSdk> android_sdk_groups; Group<DeviceFeature> device_feature_groups; Group<GlTexture> gl_texture_groups; + + /** Helper method that generates a list of artifact names and returns true on success. */ + bool AllArtifactNames(const android::StringPiece& apk_name, + std::vector<std::string>* artifact_names, IDiagnostics* diag) const; }; } // namespace configuration diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp index f89773720cc5..afa155f46eb9 100644 --- a/tools/aapt2/configuration/ConfigurationParser_test.cpp +++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp @@ -24,6 +24,15 @@ #include "xml/XmlDom.h" namespace aapt { + +namespace configuration { +void PrintTo(const AndroidSdk& sdk, std::ostream* os) { + *os << "SDK: min=" << sdk.min_sdk_version.value_or_default(-1) + << ", target=" << sdk.target_sdk_version.value_or_default(-1) + << ", max=" << sdk.max_sdk_version.value_or_default(-1); +} +} // namespace configuration + namespace { using ::android::ResTable_config; @@ -64,20 +73,17 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?> <screen-density>xxxhdpi</screen-density> </screen-density-group> <locale-group label="europe"> - <locale lang="en"/> - <locale lang="es"/> - <locale lang="fr"/> - <locale lang="de"/> + <locale>en</locale> + <locale>es</locale> + <locale>fr</locale> + <locale>de</locale> </locale-group> <locale-group label="north-america"> - <locale lang="en"/> - <locale lang="es" region="MX"/> - <locale lang="fr" region="CA"/> - </locale-group> - <locale-group label="all"> - <locale/> + <locale>en</locale> + <locale>es-rMX</locale> + <locale>fr-rCA</locale> </locale-group> - <android-sdk-group label="19"> + <android-sdk-group label="v19"> <android-sdk minSdkVersion="19" targetSdkVersion="24" @@ -105,7 +111,7 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?> abi-group="arm" screen-density-group="large" locale-group="europe" - android-sdk-group="19" + android-sdk-group="v19" gl-texture-group="dxt1" device-feature-group="low-latency"/> <artifact @@ -113,7 +119,7 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?> abi-group="other" screen-density-group="alldpi" locale-group="north-america" - android-sdk-group="19" + android-sdk-group="v19" gl-texture-group="dxt1" device-feature-group="low-latency"/> </artifacts> @@ -153,13 +159,13 @@ TEST_F(ConfigurationParserTest, ValidateFile) { EXPECT_EQ(3ul, value.screen_density_groups["large"].size()); EXPECT_EQ(6ul, value.screen_density_groups["alldpi"].size()); - EXPECT_EQ(3ul, value.locale_groups.size()); + EXPECT_EQ(2ul, value.locale_groups.size()); EXPECT_EQ(4ul, value.locale_groups["europe"].size()); EXPECT_EQ(3ul, value.locale_groups["north-america"].size()); - EXPECT_EQ(1ul, value.locale_groups["all"].size()); EXPECT_EQ(1ul, value.android_sdk_groups.size()); - EXPECT_EQ(1ul, value.android_sdk_groups["19"].size()); + EXPECT_TRUE(value.android_sdk_groups["v19"].min_sdk_version); + EXPECT_EQ(19, value.android_sdk_groups["v19"].min_sdk_version.value()); EXPECT_EQ(1ul, value.gl_texture_groups.size()); EXPECT_EQ(1ul, value.gl_texture_groups["dxt1"].size()); @@ -177,55 +183,117 @@ TEST_F(ConfigurationParserTest, InvalidNamespace) { } TEST_F(ConfigurationParserTest, ArtifactAction) { - static constexpr const char* xml = R"xml( + PostProcessingConfiguration config; + { + const auto doc = test::BuildXmlDom(R"xml( + <artifact + abi-group="arm" + screen-density-group="large" + locale-group="europe" + android-sdk-group="v19" + gl-texture-group="dxt1" + device-feature-group="low-latency"/>)xml"); + + ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc->root.get()), &diag_)); + + EXPECT_EQ(1ul, config.artifacts.size()); + + auto& artifact = config.artifacts.back(); + EXPECT_FALSE(artifact.name); // TODO: make this fail. + EXPECT_EQ(1, artifact.version); + EXPECT_EQ("arm", artifact.abi_group.value()); + EXPECT_EQ("large", artifact.screen_density_group.value()); + EXPECT_EQ("europe", artifact.locale_group.value()); + EXPECT_EQ("v19", artifact.android_sdk_group.value()); + EXPECT_EQ("dxt1", artifact.gl_texture_group.value()); + EXPECT_EQ("low-latency", artifact.device_feature_group.value()); + } + + { + // Perform a second action to ensure we get 2 artifacts. + const auto doc = test::BuildXmlDom(R"xml( + <artifact + abi-group="other" + screen-density-group="large" + locale-group="europe" + android-sdk-group="v19" + gl-texture-group="dxt1" + device-feature-group="low-latency"/>)xml"); + + ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_)); + EXPECT_EQ(2ul, config.artifacts.size()); + EXPECT_EQ(2, config.artifacts.back().version); + } + + { + // Perform a third action with a set version code. + const auto doc = test::BuildXmlDom(R"xml( <artifact - abi-group="arm" + version="5" + abi-group="other" screen-density-group="large" locale-group="europe" - android-sdk-group="19" + android-sdk-group="v19" gl-texture-group="dxt1" - device-feature-group="low-latency"/>)xml"; + device-feature-group="low-latency"/>)xml"); - auto doc = test::BuildXmlDom(xml); - - PostProcessingConfiguration config; - bool ok = artifact_handler_(&config, NodeCast<Element>(doc->root.get()), &diag_); - ASSERT_TRUE(ok); + ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_)); + EXPECT_EQ(3ul, config.artifacts.size()); + EXPECT_EQ(5, config.artifacts.back().version); + } - EXPECT_EQ(1ul, config.artifacts.size()); - - auto& artifact = config.artifacts.front(); - EXPECT_EQ("", artifact.name); // TODO: make this fail. - EXPECT_EQ("arm", artifact.abi_group.value()); - EXPECT_EQ("large", artifact.screen_density_group.value()); - EXPECT_EQ("europe", artifact.locale_group.value()); - EXPECT_EQ("19", artifact.android_sdk_group.value()); - EXPECT_EQ("dxt1", artifact.gl_texture_group.value()); - EXPECT_EQ("low-latency", artifact.device_feature_group.value()); - - // Perform a second action to ensure we get 2 artifacts. - static constexpr const char* second = R"xml( + { + // Perform a fourth action to ensure the version code still increments. + const auto doc = test::BuildXmlDom(R"xml( <artifact abi-group="other" screen-density-group="large" locale-group="europe" - android-sdk-group="19" + android-sdk-group="v19" gl-texture-group="dxt1" - device-feature-group="low-latency"/>)xml"; - doc = test::BuildXmlDom(second); + device-feature-group="low-latency"/>)xml"); - ok = artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); - ASSERT_TRUE(ok); - EXPECT_EQ(2ul, config.artifacts.size()); + ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_)); + EXPECT_EQ(4ul, config.artifacts.size()); + EXPECT_EQ(6, config.artifacts.back().version); + } +} + +TEST_F(ConfigurationParserTest, DuplicateArtifactVersion) { + static constexpr const char* configuration = R"xml(<?xml version="1.0" encoding="utf-8" ?> + <pst-process xmlns="http://schemas.android.com/tools/aapt">> + <artifacts> + <artifact-format> + ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release + </artifact-format> + <artifact + name="art1" + abi-group="arm" + screen-density-group="large" + locale-group="europe" + android-sdk-group="v19" + gl-texture-group="dxt1" + device-feature-group="low-latency"/> + <artifact + name="art2" + version = "1" + abi-group="other" + screen-density-group="alldpi" + locale-group="north-america" + android-sdk-group="v19" + gl-texture-group="dxt1" + device-feature-group="low-latency"/> + </artifacts> + </post-process>)xml"; + auto result = ConfigurationParser::ForContents(configuration).Parse(); + ASSERT_FALSE(result); } TEST_F(ConfigurationParserTest, ArtifactFormatAction) { - static constexpr const char* xml = R"xml( + const auto doc = test::BuildXmlDom(R"xml( <artifact-format> ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release - </artifact-format>)xml"; - - auto doc = test::BuildXmlDom(xml); + </artifact-format>)xml"); PostProcessingConfiguration config; bool ok = artifact_format_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); @@ -295,10 +363,10 @@ TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) { TEST_F(ConfigurationParserTest, LocaleGroupAction) { static constexpr const char* xml = R"xml( <locale-group label="europe"> - <locale lang="en"/> - <locale lang="es"/> - <locale lang="fr"/> - <locale lang="de"/> + <locale>en</locale> + <locale>es</locale> + <locale>fr</locale> + <locale>de</locale> </locale-group>)xml"; auto doc = test::BuildXmlDom(xml); @@ -310,23 +378,19 @@ TEST_F(ConfigurationParserTest, LocaleGroupAction) { ASSERT_EQ(1ul, config.locale_groups.size()); ASSERT_EQ(1u, config.locale_groups.count("europe")); - auto& out = config.locale_groups["europe"]; + const auto& out = config.locale_groups["europe"]; - Locale en; - en.lang = std::string("en"); - Locale es; - es.lang = std::string("es"); - Locale fr; - fr.lang = std::string("fr"); - Locale de; - de.lang = std::string("de"); + ConfigDescription en = test::ParseConfigOrDie("en"); + ConfigDescription es = test::ParseConfigOrDie("es"); + ConfigDescription fr = test::ParseConfigOrDie("fr"); + ConfigDescription de = test::ParseConfigOrDie("de"); ASSERT_THAT(out, ElementsAre(en, es, fr, de)); } TEST_F(ConfigurationParserTest, AndroidSdkGroupAction) { static constexpr const char* xml = R"xml( - <android-sdk-group label="19"> + <android-sdk-group label="v19"> <android-sdk minSdkVersion="19" targetSdkVersion="24" @@ -344,18 +408,46 @@ TEST_F(ConfigurationParserTest, AndroidSdkGroupAction) { ASSERT_TRUE(ok); ASSERT_EQ(1ul, config.android_sdk_groups.size()); - ASSERT_EQ(1u, config.android_sdk_groups.count("19")); + ASSERT_EQ(1u, config.android_sdk_groups.count("v19")); - auto& out = config.android_sdk_groups["19"]; + auto& out = config.android_sdk_groups["v19"]; AndroidSdk sdk; - sdk.min_sdk_version = std::string("19"); - sdk.target_sdk_version = std::string("24"); - sdk.max_sdk_version = std::string("25"); + sdk.min_sdk_version = 19; + sdk.target_sdk_version = 24; + sdk.max_sdk_version = 25; sdk.manifest = AndroidManifest(); - ASSERT_EQ(1ul, out.size()); - ASSERT_EQ(sdk, out[0]); + ASSERT_EQ(sdk, out); +} + +TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_NonNumeric) { + static constexpr const char* xml = R"xml( + <android-sdk-group label="P"> + <android-sdk + minSdkVersion="M" + targetSdkVersion="P" + maxSdkVersion="P"> + </android-sdk> + </android-sdk-group>)xml"; + + auto doc = test::BuildXmlDom(xml); + + PostProcessingConfiguration config; + bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); + ASSERT_TRUE(ok); + + ASSERT_EQ(1ul, config.android_sdk_groups.size()); + ASSERT_EQ(1u, config.android_sdk_groups.count("P")); + + auto& out = config.android_sdk_groups["P"]; + + AndroidSdk sdk; + sdk.min_sdk_version = {}; // Only the latest development version is supported. + sdk.target_sdk_version = 28; + sdk.max_sdk_version = 28; + + ASSERT_EQ(sdk, out); } TEST_F(ConfigurationParserTest, GlTextureGroupAction) { @@ -415,21 +507,43 @@ TEST_F(ConfigurationParserTest, DeviceFeatureGroupAction) { ASSERT_THAT(out, ElementsAre(low_latency, pro)); } +// Artifact name parser test cases. + TEST(ArtifactTest, Simple) { StdErrDiagnostics diag; Artifact x86; x86.abi_group = {"x86"}; - auto x86_result = x86.ToArtifactName("something.{abi}.apk", &diag); + auto x86_result = x86.ToArtifactName("something.${abi}.apk", "", &diag); ASSERT_TRUE(x86_result); EXPECT_EQ(x86_result.value(), "something.x86.apk"); Artifact arm; arm.abi_group = {"armeabi-v7a"}; - auto arm_result = arm.ToArtifactName("app.{abi}.apk", &diag); - ASSERT_TRUE(arm_result); - EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); + { + auto arm_result = arm.ToArtifactName("app.${abi}.apk", "", &diag); + ASSERT_TRUE(arm_result); + EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); + } + + { + auto arm_result = arm.ToArtifactName("app.${abi}.apk", "different_name.apk", &diag); + ASSERT_TRUE(arm_result); + EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); + } + + { + auto arm_result = arm.ToArtifactName("${basename}.${abi}.apk", "app.apk", &diag); + ASSERT_TRUE(arm_result); + EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); + } + + { + auto arm_result = arm.ToArtifactName("app.${abi}.${ext}", "app.apk", &diag); + ASSERT_TRUE(arm_result); + EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); + } } TEST(ArtifactTest, Complex) { @@ -440,12 +554,42 @@ TEST(ArtifactTest, Complex) { artifact.device_feature_group = {"df1"}; artifact.gl_texture_group = {"glx1"}; artifact.locale_group = {"en-AU"}; - artifact.android_sdk_group = {"26"}; - - auto result = - artifact.ToArtifactName("app.{density}_{locale}_{feature}_{gl}.sdk{sdk}.{abi}.apk", &diag); - ASSERT_TRUE(result); - EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.sdk26.mips64.apk"); + artifact.android_sdk_group = {"v26"}; + + { + auto result = artifact.ToArtifactName( + "app.${density}_${locale}_${feature}_${gl}.${sdk}.${abi}.apk", "", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.v26.mips64.apk"); + } + + { + auto result = artifact.ToArtifactName( + "app.${density}_${locale}_${feature}_${gl}.${sdk}.${abi}.apk", "app.apk", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.v26.mips64.apk"); + } + + { + auto result = artifact.ToArtifactName( + "${basename}.${density}_${locale}_${feature}_${gl}.${sdk}.${abi}.apk", "app.apk", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.v26.mips64.apk"); + } + + { + auto result = artifact.ToArtifactName( + "app.${density}_${locale}_${feature}_${gl}.${sdk}.${abi}.${ext}", "app.apk", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.v26.mips64.apk"); + } + + { + auto result = artifact.ToArtifactName( + "${basename}.${density}_${locale}_${feature}_${gl}.${sdk}.${abi}", "app.apk", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.v26.mips64.apk"); + } } TEST(ArtifactTest, Missing) { @@ -453,16 +597,69 @@ TEST(ArtifactTest, Missing) { Artifact x86; x86.abi_group = {"x86"}; - EXPECT_FALSE(x86.ToArtifactName("something.{density}.apk", &diag)); - EXPECT_FALSE(x86.ToArtifactName("something.apk", &diag)); + EXPECT_FALSE(x86.ToArtifactName("something.${density}.apk", "", &diag)); + EXPECT_FALSE(x86.ToArtifactName("something.apk", "", &diag)); + EXPECT_FALSE(x86.ToArtifactName("something.${density}.apk", "something.apk", &diag)); + EXPECT_FALSE(x86.ToArtifactName("something.apk", "something.apk", &diag)); } TEST(ArtifactTest, Empty) { StdErrDiagnostics diag; Artifact artifact; - EXPECT_FALSE(artifact.ToArtifactName("something.{density}.apk", &diag)); - EXPECT_TRUE(artifact.ToArtifactName("something.apk", &diag)); + EXPECT_FALSE(artifact.ToArtifactName("something.${density}.apk", "", &diag)); + EXPECT_TRUE(artifact.ToArtifactName("something.apk", "", &diag)); + EXPECT_FALSE(artifact.ToArtifactName("something.${density}.apk", "something.apk", &diag)); + EXPECT_TRUE(artifact.ToArtifactName("something.apk", "something.apk", &diag)); +} + +TEST(ArtifactTest, Repeated) { + StdErrDiagnostics diag; + Artifact artifact; + artifact.screen_density_group = {"mdpi"}; + + ASSERT_TRUE(artifact.ToArtifactName("something.${density}.apk", "", &diag)); + EXPECT_FALSE(artifact.ToArtifactName("something.${density}.${density}.apk", "", &diag)); + ASSERT_TRUE(artifact.ToArtifactName("something.${density}.apk", "something.apk", &diag)); +} + +TEST(ArtifactTest, Nesting) { + StdErrDiagnostics diag; + Artifact x86; + x86.abi_group = {"x86"}; + + EXPECT_FALSE(x86.ToArtifactName("something.${abi${density}}.apk", "", &diag)); + + const Maybe<std::string>& name = x86.ToArtifactName("something.${abi${abi}}.apk", "", &diag); + ASSERT_TRUE(name); + EXPECT_EQ(name.value(), "something.${abix86}.apk"); +} + +TEST(ArtifactTest, Recursive) { + StdErrDiagnostics diag; + Artifact artifact; + artifact.device_feature_group = {"${gl}"}; + artifact.gl_texture_group = {"glx1"}; + + EXPECT_FALSE(artifact.ToArtifactName("app.${feature}.${gl}.apk", "", &diag)); + + artifact.device_feature_group = {"df1"}; + artifact.gl_texture_group = {"${feature}"}; + { + const auto& result = artifact.ToArtifactName("app.${feature}.${gl}.apk", "", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.df1.${feature}.apk"); + } + + // This is an invalid case, but should be the only possible case due to the ordering of + // replacement. + artifact.device_feature_group = {"${gl}"}; + artifact.gl_texture_group = {"glx1"}; + { + const auto& result = artifact.ToArtifactName("app.${feature}.apk", "", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.glx1.apk"); + } } } // namespace diff --git a/tools/aapt2/configuration/aapt2.xsd b/tools/aapt2/configuration/aapt2.xsd index 47bf99e28089..134153a017f8 100644 --- a/tools/aapt2/configuration/aapt2.xsd +++ b/tools/aapt2/configuration/aapt2.xsd @@ -39,6 +39,7 @@ <!-- Groups output artifacts together by dimension labels. --> <xsd:complexType name="artifact"> <xsd:attribute name="name" type="xsd:string"/> + <xsd:attribute name="version" type="xsd:integer"/> <xsd:attribute name="abi-group" type="xsd:string"/> <xsd:attribute name="android-sdk-group" type="xsd:string"/> <xsd:attribute name="device-feature-group" type="xsd:string"/> diff --git a/tools/aapt2/filter/ConfigFilter.h b/tools/aapt2/filter/ConfigFilter.h index 3f1341684912..ebb81519dceb 100644 --- a/tools/aapt2/filter/ConfigFilter.h +++ b/tools/aapt2/filter/ConfigFilter.h @@ -38,13 +38,9 @@ class IConfigFilter { }; /** - * Implements config axis matching. An axis is one component of a configuration, - * like screen - * density or locale. If an axis is specified in the filter, and the axis is - * specified in - * the configuration to match, they must be compatible. Otherwise the - * configuration to match is - * accepted. + * Implements config axis matching. An axis is one component of a configuration, like screen density + * or locale. If an axis is specified in the filter, and the axis is specified in the configuration + * to match, they must be compatible. Otherwise the configuration to match is accepted. * * Used when handling "-c" options. */ diff --git a/tools/aapt2/filter/Filter_test.cpp b/tools/aapt2/filter/Filter_test.cpp index fb75a4b4d7c1..db2e69fc90d3 100644 --- a/tools/aapt2/filter/Filter_test.cpp +++ b/tools/aapt2/filter/Filter_test.cpp @@ -25,22 +25,16 @@ namespace aapt { namespace { -TEST(FilterChainTest, EmptyChain) { +TEST(FilterTest, FilterChain) { FilterChain chain; ASSERT_TRUE(chain.Keep("some/random/path")); -} -TEST(FilterChainTest, SingleFilter) { - FilterChain chain; chain.AddFilter(util::make_unique<PrefixFilter>("keep/")); ASSERT_FALSE(chain.Keep("removed/path")); ASSERT_TRUE(chain.Keep("keep/path/1")); ASSERT_TRUE(chain.Keep("keep/path/2")); -} -TEST(FilterChainTest, MultipleFilters) { - FilterChain chain; chain.AddFilter(util::make_unique<PrefixFilter>("keep/")); chain.AddFilter(util::make_unique<PrefixFilter>("keep/really/")); diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/format/Archive.cpp index 5f8bd063f9b0..d152a9cc7e62 100644 --- a/tools/aapt2/flatten/Archive.cpp +++ b/tools/aapt2/format/Archive.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "flatten/Archive.h" +#include "format/Archive.h" #include <cstdio> #include <memory> @@ -106,9 +106,13 @@ class DirectoryWriter : public IArchiveWriter { return !in->HadError(); } - bool HadError() const override { return !error_.empty(); } + bool HadError() const override { + return !error_.empty(); + } - std::string GetError() const override { return error_; } + std::string GetError() const override { + return error_; + } private: DISALLOW_COPY_AND_ASSIGN(DirectoryWriter); @@ -221,9 +225,13 @@ class ZipFileWriter : public IArchiveWriter { } } - bool HadError() const override { return !error_.empty(); } + bool HadError() const override { + return !error_.empty(); + } - std::string GetError() const override { return error_; } + std::string GetError() const override { + return error_; + } virtual ~ZipFileWriter() { if (writer_) { diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/format/Archive.h index 4ee4ce71a5c5..4e8a39df9165 100644 --- a/tools/aapt2/flatten/Archive.h +++ b/tools/aapt2/format/Archive.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef AAPT_FLATTEN_ARCHIVE_H -#define AAPT_FLATTEN_ARCHIVE_H +#ifndef AAPT_FORMAT_ARCHIVE_H +#define AAPT_FORMAT_ARCHIVE_H #include <fstream> #include <memory> @@ -78,4 +78,4 @@ std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(IDiagnostics* diag, } // namespace aapt -#endif /* AAPT_FLATTEN_ARCHIVE_H */ +#endif /* AAPT_FORMAT_ARCHIVE_H */ diff --git a/tools/aapt2/format/Container.cpp b/tools/aapt2/format/Container.cpp new file mode 100644 index 000000000000..739555c5b15d --- /dev/null +++ b/tools/aapt2/format/Container.cpp @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2017 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 "format/Container.h" + +#include "android-base/scopeguard.h" +#include "android-base/stringprintf.h" + +using ::android::base::StringPrintf; +using ::google::protobuf::io::CodedInputStream; +using ::google::protobuf::io::CodedOutputStream; +using ::google::protobuf::io::ZeroCopyOutputStream; + +namespace aapt { + +constexpr const static uint32_t kContainerFormatMagic = 0x54504141u; +constexpr const static uint32_t kContainerFormatVersion = 1u; + +ContainerWriter::ContainerWriter(ZeroCopyOutputStream* out, size_t entry_count) + : out_(out), total_entry_count_(entry_count), current_entry_count_(0u) { + CodedOutputStream coded_out(out_); + + // Write the magic. + coded_out.WriteLittleEndian32(kContainerFormatMagic); + + // Write the version. + coded_out.WriteLittleEndian32(kContainerFormatVersion); + + // Write the total number of entries. + coded_out.WriteLittleEndian32(static_cast<uint32_t>(total_entry_count_)); + + if (coded_out.HadError()) { + error_ = "failed writing container format header"; + } +} + +inline static void WritePadding(int padding, CodedOutputStream* out) { + if (padding < 4) { + const uint32_t zero = 0u; + out->WriteRaw(&zero, padding); + } +} + +bool ContainerWriter::AddResTableEntry(const pb::ResourceTable& table) { + if (current_entry_count_ >= total_entry_count_) { + error_ = "too many entries being serialized"; + return false; + } + current_entry_count_++; + + CodedOutputStream coded_out(out_); + + // Write the type. + coded_out.WriteLittleEndian32(kResTable); + + // Write the aligned size. + const ::google::protobuf::uint64 size = table.ByteSize(); + const int padding = 4 - (size % 4); + coded_out.WriteLittleEndian64(size); + + // Write the table. + table.SerializeWithCachedSizes(&coded_out); + + // Write the padding. + WritePadding(padding, &coded_out); + + if (coded_out.HadError()) { + error_ = "failed writing to output"; + return false; + } + return true; +} + +bool ContainerWriter::AddResFileEntry(const pb::internal::CompiledFile& file, + io::KnownSizeInputStream* in) { + if (current_entry_count_ >= total_entry_count_) { + error_ = "too many entries being serialized"; + return false; + } + current_entry_count_++; + + constexpr const static int kResFileEntryHeaderSize = 12; + + CodedOutputStream coded_out(out_); + + // Write the type. + coded_out.WriteLittleEndian32(kResFile); + + // Write the aligned size. + const ::google::protobuf::uint32 header_size = file.ByteSize(); + const int header_padding = 4 - (header_size % 4); + const ::google::protobuf::uint64 data_size = in->TotalSize(); + const int data_padding = 4 - (data_size % 4); + coded_out.WriteLittleEndian64(kResFileEntryHeaderSize + header_size + header_padding + data_size + + data_padding); + + // Write the res file header size. + coded_out.WriteLittleEndian32(header_size); + + // Write the data payload size. + coded_out.WriteLittleEndian64(data_size); + + // Write the header. + file.SerializeToCodedStream(&coded_out); + + WritePadding(header_padding, &coded_out); + + // Write the data payload. We need to call Trim() since we are going to write to the underlying + // ZeroCopyOutputStream. + coded_out.Trim(); + + // Check at this point if there were any errors. + if (coded_out.HadError()) { + error_ = "failed writing to output"; + return false; + } + + if (!io::Copy(out_, in)) { + if (in->HadError()) { + std::ostringstream error; + error << "failed reading from input: " << in->GetError(); + error_ = error.str(); + } else { + error_ = "failed writing to output"; + } + return false; + } + WritePadding(data_padding, &coded_out); + + if (coded_out.HadError()) { + error_ = "failed writing to output"; + return false; + } + return true; +} + +bool ContainerWriter::HadError() const { + return !error_.empty(); +} + +std::string ContainerWriter::GetError() const { + return error_; +} + +static bool AlignRead(CodedInputStream* in) { + const int padding = 4 - (in->CurrentPosition() % 4); + if (padding < 4) { + return in->Skip(padding); + } + return true; +} + +ContainerReaderEntry::ContainerReaderEntry(ContainerReader* reader) : reader_(reader) { +} + +ContainerEntryType ContainerReaderEntry::Type() const { + return type_; +} + +bool ContainerReaderEntry::GetResTable(pb::ResourceTable* out_table) { + CHECK(type_ == ContainerEntryType::kResTable) << "reading a kResTable when the type is kResFile"; + if (length_ > std::numeric_limits<int>::max()) { + reader_->error_ = StringPrintf("entry length %zu is too large", length_); + return false; + } + + CodedInputStream& coded_in = reader_->coded_in_; + + const CodedInputStream::Limit limit = coded_in.PushLimit(static_cast<int>(length_)); + auto guard = ::android::base::make_scope_guard([&]() { coded_in.PopLimit(limit); }); + + if (!out_table->ParseFromCodedStream(&coded_in)) { + reader_->error_ = "failed to parse ResourceTable"; + return false; + } + return true; +} + +bool ContainerReaderEntry::GetResFileOffsets(pb::internal::CompiledFile* out_file, + off64_t* out_offset, size_t* out_len) { + CHECK(type_ == ContainerEntryType::kResFile) << "reading a kResFile when the type is kResTable"; + + CodedInputStream& coded_in = reader_->coded_in_; + + // Read the ResFile header. + ::google::protobuf::uint32 header_length; + if (!coded_in.ReadLittleEndian32(&header_length)) { + std::ostringstream error; + error << "failed to read header length from input: " << reader_->in_->GetError(); + reader_->error_ = error.str(); + return false; + } + + ::google::protobuf::uint64 data_length; + if (!coded_in.ReadLittleEndian64(&data_length)) { + std::ostringstream error; + error << "failed to read data length from input: " << reader_->in_->GetError(); + reader_->error_ = error.str(); + return false; + } + + if (header_length > std::numeric_limits<int>::max()) { + std::ostringstream error; + error << "header length " << header_length << " is too large"; + reader_->error_ = error.str(); + return false; + } + + if (data_length > std::numeric_limits<size_t>::max()) { + std::ostringstream error; + error << "data length " << data_length << " is too large"; + reader_->error_ = error.str(); + return false; + } + + { + const CodedInputStream::Limit limit = coded_in.PushLimit(static_cast<int>(header_length)); + auto guard = ::android::base::make_scope_guard([&]() { coded_in.PopLimit(limit); }); + + if (!out_file->ParseFromCodedStream(&coded_in)) { + reader_->error_ = "failed to parse CompiledFile header"; + return false; + } + } + + AlignRead(&coded_in); + + *out_offset = coded_in.CurrentPosition(); + *out_len = data_length; + + coded_in.Skip(static_cast<int>(data_length)); + AlignRead(&coded_in); + return true; +} + +bool ContainerReaderEntry::HadError() const { + return reader_->HadError(); +} + +std::string ContainerReaderEntry::GetError() const { + return reader_->GetError(); +} + +ContainerReader::ContainerReader(io::InputStream* in) + : in_(in), + adaptor_(in), + coded_in_(&adaptor_), + total_entry_count_(0u), + current_entry_count_(0u), + entry_(this) { + ::google::protobuf::uint32 magic; + if (!coded_in_.ReadLittleEndian32(&magic)) { + std::ostringstream error; + error << "failed to read magic from input: " << in_->GetError(); + error_ = error.str(); + return; + } + + if (magic != kContainerFormatMagic) { + error_ = "magic value doesn't match AAPT"; + return; + } + + ::google::protobuf::uint32 version; + if (!coded_in_.ReadLittleEndian32(&version)) { + std::ostringstream error; + error << "failed to read version from input: " << in_->GetError(); + error_ = error.str(); + return; + } + + if (version != kContainerFormatVersion) { + error_ = StringPrintf("container version is 0x%08x but AAPT expects version 0x%08x", version, + kContainerFormatVersion); + return; + } + + ::google::protobuf::uint32 total_entry_count; + if (!coded_in_.ReadLittleEndian32(&total_entry_count)) { + std::ostringstream error; + error << "failed to read entry count from input: " << in_->GetError(); + error_ = error.str(); + return; + } + + total_entry_count_ = total_entry_count; +} + +ContainerReaderEntry* ContainerReader::Next() { + if (current_entry_count_ >= total_entry_count_) { + return nullptr; + } + current_entry_count_++; + + // Ensure the next read is aligned. + AlignRead(&coded_in_); + + ::google::protobuf::uint32 entry_type; + if (!coded_in_.ReadLittleEndian32(&entry_type)) { + std::ostringstream error; + error << "failed reading entry type from input: " << in_->GetError(); + error_ = error.str(); + return nullptr; + } + + ::google::protobuf::uint64 entry_length; + if (!coded_in_.ReadLittleEndian64(&entry_length)) { + std::ostringstream error; + error << "failed reading entry length from input: " << in_->GetError(); + error_ = error.str(); + return nullptr; + } + + if (entry_type == ContainerEntryType::kResFile || entry_type == ContainerEntryType::kResTable) { + entry_.type_ = static_cast<ContainerEntryType>(entry_type); + } else { + error_ = StringPrintf("entry type 0x%08x is invalid", entry_type); + return nullptr; + } + + if (entry_length > std::numeric_limits<size_t>::max()) { + std::ostringstream error; + error << "entry length " << entry_length << " is too large"; + error_ = error.str(); + return nullptr; + } + + entry_.length_ = entry_length; + return &entry_; +} + +bool ContainerReader::HadError() const { + return !error_.empty(); +} + +std::string ContainerReader::GetError() const { + return error_; +} + +} // namespace aapt diff --git a/tools/aapt2/format/Container.h b/tools/aapt2/format/Container.h new file mode 100644 index 000000000000..aa5c82cd322c --- /dev/null +++ b/tools/aapt2/format/Container.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017 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 AAPT_FORMAT_CONTAINER_H +#define AAPT_FORMAT_CONTAINER_H + +#include <inttypes.h> + +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/io/zero_copy_stream.h" + +#include "Resources.pb.h" +#include "ResourcesInternal.pb.h" +#include "io/Io.h" +#include "io/Util.h" +#include "util/BigBuffer.h" + +namespace aapt { + +enum ContainerEntryType : uint8_t { + kResTable = 0x00u, + kResFile = 0x01u, +}; + +class ContainerWriter { + public: + explicit ContainerWriter(::google::protobuf::io::ZeroCopyOutputStream* out, size_t entry_count); + + bool AddResTableEntry(const pb::ResourceTable& table); + bool AddResFileEntry(const pb::internal::CompiledFile& file, io::KnownSizeInputStream* in); + bool HadError() const; + std::string GetError() const; + + private: + DISALLOW_COPY_AND_ASSIGN(ContainerWriter); + + ::google::protobuf::io::ZeroCopyOutputStream* out_; + size_t total_entry_count_; + size_t current_entry_count_; + std::string error_; +}; + +class ContainerReader; + +class ContainerReaderEntry { + public: + ContainerEntryType Type() const; + + bool GetResTable(pb::ResourceTable* out_table); + bool GetResFileOffsets(pb::internal::CompiledFile* out_file, off64_t* out_offset, + size_t* out_len); + + bool HadError() const; + std::string GetError() const; + + private: + DISALLOW_COPY_AND_ASSIGN(ContainerReaderEntry); + + friend class ContainerReader; + + explicit ContainerReaderEntry(ContainerReader* reader); + + ContainerReader* reader_; + ContainerEntryType type_ = ContainerEntryType::kResTable; + size_t length_ = 0u; +}; + +class ContainerReader { + public: + explicit ContainerReader(io::InputStream* in); + + ContainerReaderEntry* Next(); + + bool HadError() const; + std::string GetError() const; + + private: + DISALLOW_COPY_AND_ASSIGN(ContainerReader); + + friend class ContainerReaderEntry; + + io::InputStream* in_; + io::ZeroCopyInputAdaptor adaptor_; + ::google::protobuf::io::CodedInputStream coded_in_; + size_t total_entry_count_; + size_t current_entry_count_; + ContainerReaderEntry entry_; + std::string error_; +}; + +} // namespace aapt + +#endif /* AAPT_FORMAT_CONTAINER_H */ diff --git a/tools/aapt2/format/Container_test.cpp b/tools/aapt2/format/Container_test.cpp new file mode 100644 index 000000000000..dc81a3ae2219 --- /dev/null +++ b/tools/aapt2/format/Container_test.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2017 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 "format/Container.h" + +#include "google/protobuf/io/zero_copy_stream_impl_lite.h" + +#include "io/StringStream.h" +#include "test/Test.h" + +using ::google::protobuf::io::StringOutputStream; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::IsNull; +using ::testing::NotNull; +using ::testing::StrEq; + +namespace aapt { + +TEST(ContainerTest, SerializeCompiledFile) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + const std::string expected_data = "123"; + + std::string output_str; + { + StringOutputStream out_stream(&output_str); + ContainerWriter writer(&out_stream, 2u); + ASSERT_FALSE(writer.HadError()); + + pb::internal::CompiledFile pb_compiled_file; + pb_compiled_file.set_resource_name("android:layout/main.xml"); + pb_compiled_file.set_type(pb::FileReference::PROTO_XML); + pb_compiled_file.set_source_path("res/layout/main.xml"); + io::StringInputStream data(expected_data); + ASSERT_TRUE(writer.AddResFileEntry(pb_compiled_file, &data)); + + pb::ResourceTable pb_table; + pb::Package* pb_pkg = pb_table.add_package(); + pb_pkg->set_package_name("android"); + pb_pkg->mutable_package_id()->set_id(0x01u); + ASSERT_TRUE(writer.AddResTableEntry(pb_table)); + + ASSERT_FALSE(writer.HadError()); + } + + io::StringInputStream input(output_str); + ContainerReader reader(&input); + ASSERT_FALSE(reader.HadError()); + + ContainerReaderEntry* entry = reader.Next(); + ASSERT_THAT(entry, NotNull()); + ASSERT_THAT(entry->Type(), Eq(ContainerEntryType::kResFile)); + + pb::internal::CompiledFile pb_new_file; + off64_t offset; + size_t len; + ASSERT_TRUE(entry->GetResFileOffsets(&pb_new_file, &offset, &len)) << entry->GetError(); + EXPECT_THAT(offset & 0x03, Eq(0u)); + EXPECT_THAT(output_str.substr(static_cast<size_t>(offset), len), StrEq(expected_data)); + + entry = reader.Next(); + ASSERT_THAT(entry, NotNull()); + ASSERT_THAT(entry->Type(), Eq(ContainerEntryType::kResTable)); + + pb::ResourceTable pb_new_table; + ASSERT_TRUE(entry->GetResTable(&pb_new_table)); + ASSERT_THAT(pb_new_table.package_size(), Eq(1)); + EXPECT_THAT(pb_new_table.package(0).package_name(), StrEq("android")); + EXPECT_THAT(pb_new_table.package(0).package_id().id(), Eq(0x01u)); + + EXPECT_THAT(reader.Next(), IsNull()); + EXPECT_FALSE(reader.HadError()); + EXPECT_THAT(reader.GetError(), IsEmpty()); +} + +} // namespace aapt diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp index 892aee6fadcb..5078678e08a3 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.cpp +++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "unflatten/BinaryResourceParser.h" +#include "format/binary/BinaryResourceParser.h" #include <algorithm> #include <map> @@ -31,22 +31,22 @@ #include "ResourceValues.h" #include "Source.h" #include "ValueVisitor.h" -#include "unflatten/ResChunkPullParser.h" +#include "format/binary/ResChunkPullParser.h" #include "util/Util.h" -namespace aapt { - using namespace android; using ::android::base::StringPrintf; +namespace aapt { + namespace { // Visitor that converts a reference's resource ID to a resource name, given a mapping from // resource ID to resource name. -class ReferenceIdToNameVisitor : public ValueVisitor { +class ReferenceIdToNameVisitor : public DescendingValueVisitor { public: - using ValueVisitor::Visit; + using DescendingValueVisitor::Visit; explicit ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping) : mapping_(mapping) { @@ -73,29 +73,22 @@ class ReferenceIdToNameVisitor : public ValueVisitor { } // namespace -BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table, +BinaryResourceParser::BinaryResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, const void* data, size_t len, io::IFileCollection* files) - : context_(context), - table_(table), - source_(source), - data_(data), - data_len_(len), - files_(files) { + : diag_(diag), table_(table), source_(source), data_(data), data_len_(len), files_(files) { } bool BinaryResourceParser::Parse() { ResChunkPullParser parser(data_, data_len_); if (!ResChunkPullParser::IsGoodEvent(parser.Next())) { - context_->GetDiagnostics()->Error(DiagMessage(source_) - << "corrupt resources.arsc: " << parser.error()); + diag_->Error(DiagMessage(source_) << "corrupt resources.arsc: " << parser.error()); return false; } if (parser.chunk()->type != android::RES_TABLE_TYPE) { - context_->GetDiagnostics()->Error(DiagMessage(source_) - << StringPrintf("unknown chunk of type 0x%02x", + diag_->Error(DiagMessage(source_) << StringPrintf("unknown chunk of type 0x%02x", static_cast<int>(parser.chunk()->type))); return false; } @@ -106,27 +99,22 @@ bool BinaryResourceParser::Parse() { if (parser.Next() != ResChunkPullParser::Event::kEndDocument) { if (parser.event() == ResChunkPullParser::Event::kBadDocument) { - context_->GetDiagnostics()->Warn( - DiagMessage(source_) << "invalid chunk trailing RES_TABLE_TYPE: " << parser.error()); + diag_->Warn(DiagMessage(source_) + << "invalid chunk trailing RES_TABLE_TYPE: " << parser.error()); } else { - context_->GetDiagnostics()->Warn( - DiagMessage(source_) << StringPrintf( - "unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE", - static_cast<int>(parser.chunk()->type))); + diag_->Warn(DiagMessage(source_) + << StringPrintf("unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE", + static_cast<int>(parser.chunk()->type))); } } return true; } -/** - * Parses the resource table, which contains all the packages, types, and - * entries. - */ +// Parses the resource table, which contains all the packages, types, and entries. bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) { const ResTable_header* table_header = ConvertTo<ResTable_header>(chunk); if (!table_header) { - context_->GetDiagnostics()->Error(DiagMessage(source_) - << "corrupt ResTable_header chunk"); + diag_->Error(DiagMessage(source_) << "corrupt ResTable_header chunk"); return false; } @@ -136,21 +124,18 @@ bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) { switch (util::DeviceToHost16(parser.chunk()->type)) { case android::RES_STRING_POOL_TYPE: if (value_pool_.getError() == NO_INIT) { - status_t err = value_pool_.setTo( - parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); + status_t err = + value_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); if (err != NO_ERROR) { - context_->GetDiagnostics()->Error( - DiagMessage(source_) << "corrupt string pool in ResTable: " - << value_pool_.getError()); + diag_->Error(DiagMessage(source_) + << "corrupt string pool in ResTable: " << value_pool_.getError()); return false; } // Reserve some space for the strings we are going to add. - table_->string_pool.HintWillAdd(value_pool_.size(), - value_pool_.styleCount()); + table_->string_pool.HintWillAdd(value_pool_.size(), value_pool_.styleCount()); } else { - context_->GetDiagnostics()->Warn( - DiagMessage(source_) << "unexpected string pool in ResTable"); + diag_->Warn(DiagMessage(source_) << "unexpected string pool in ResTable"); } break; @@ -161,16 +146,15 @@ bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) { break; default: - context_->GetDiagnostics()->Warn( - DiagMessage(source_) << "unexpected chunk type " - << static_cast<int>(util::DeviceToHost16(parser.chunk()->type))); + diag_->Warn(DiagMessage(source_) + << "unexpected chunk type " + << static_cast<int>(util::DeviceToHost16(parser.chunk()->type))); break; } } if (parser.event() == ResChunkPullParser::Event::kBadDocument) { - context_->GetDiagnostics()->Error( - DiagMessage(source_) << "corrupt resource table: " << parser.error()); + diag_->Error(DiagMessage(source_) << "corrupt resource table: " << parser.error()); return false; } return true; @@ -181,32 +165,29 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset); const ResTable_package* package_header = ConvertTo<ResTable_package, kMinPackageSize>(chunk); if (!package_header) { - context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_package chunk"); + diag_->Error(DiagMessage(source_) << "corrupt ResTable_package chunk"); return false; } uint32_t package_id = util::DeviceToHost32(package_header->id); if (package_id > std::numeric_limits<uint8_t>::max()) { - context_->GetDiagnostics()->Error( - DiagMessage(source_) << "package ID is too big (" << package_id << ")"); + diag_->Error(DiagMessage(source_) << "package ID is too big (" << package_id << ")"); return false; } // Extract the package name. - size_t len = strnlen16((const char16_t*)package_header->name, - arraysize(package_header->name)); + size_t len = strnlen16((const char16_t*)package_header->name, arraysize(package_header->name)); std::u16string package_name; package_name.resize(len); for (size_t i = 0; i < len; i++) { package_name[i] = util::DeviceToHost16(package_header->name[i]); } - ResourceTablePackage* package = table_->CreatePackage( - util::Utf16ToUtf8(package_name), static_cast<uint8_t>(package_id)); + ResourceTablePackage* package = + table_->CreatePackage(util::Utf16ToUtf8(package_name), static_cast<uint8_t>(package_id)); if (!package) { - context_->GetDiagnostics()->Error( - DiagMessage(source_) << "incompatible package '" << package_name - << "' with ID " << package_id); + diag_->Error(DiagMessage(source_) + << "incompatible package '" << package_name << "' with ID " << package_id); return false; } @@ -221,27 +202,23 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { switch (util::DeviceToHost16(parser.chunk()->type)) { case android::RES_STRING_POOL_TYPE: if (type_pool_.getError() == NO_INIT) { - status_t err = type_pool_.setTo( - parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); + status_t err = + type_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); if (err != NO_ERROR) { - context_->GetDiagnostics()->Error(DiagMessage(source_) - << "corrupt type string pool in " - << "ResTable_package: " - << type_pool_.getError()); + diag_->Error(DiagMessage(source_) << "corrupt type string pool in " + << "ResTable_package: " << type_pool_.getError()); return false; } } else if (key_pool_.getError() == NO_INIT) { - status_t err = key_pool_.setTo( - parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); + status_t err = + key_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); if (err != NO_ERROR) { - context_->GetDiagnostics()->Error(DiagMessage(source_) - << "corrupt key string pool in " - << "ResTable_package: " - << key_pool_.getError()); + diag_->Error(DiagMessage(source_) << "corrupt key string pool in " + << "ResTable_package: " << key_pool_.getError()); return false; } } else { - context_->GetDiagnostics()->Warn(DiagMessage(source_) << "unexpected string pool"); + diag_->Warn(DiagMessage(source_) << "unexpected string pool"); } break; @@ -264,16 +241,15 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { break; default: - context_->GetDiagnostics()->Warn( - DiagMessage(source_) << "unexpected chunk type " - << static_cast<int>(util::DeviceToHost16(parser.chunk()->type))); + diag_->Warn(DiagMessage(source_) + << "unexpected chunk type " + << static_cast<int>(util::DeviceToHost16(parser.chunk()->type))); break; } } if (parser.event() == ResChunkPullParser::Event::kBadDocument) { - context_->GetDiagnostics()->Error( - DiagMessage(source_) << "corrupt ResTable_package: " << parser.error()); + diag_->Error(DiagMessage(source_) << "corrupt ResTable_package: " << parser.error()); return false; } @@ -286,22 +262,18 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) { if (type_pool_.getError() != NO_ERROR) { - context_->GetDiagnostics()->Error(DiagMessage(source_) - << "missing type string pool"); + diag_->Error(DiagMessage(source_) << "missing type string pool"); return false; } const ResTable_typeSpec* type_spec = ConvertTo<ResTable_typeSpec>(chunk); if (!type_spec) { - context_->GetDiagnostics()->Error(DiagMessage(source_) - << "corrupt ResTable_typeSpec chunk"); + diag_->Error(DiagMessage(source_) << "corrupt ResTable_typeSpec chunk"); return false; } if (type_spec->id == 0) { - context_->GetDiagnostics()->Error(DiagMessage(source_) - << "ResTable_typeSpec has invalid id: " - << type_spec->id); + diag_->Error(DiagMessage(source_) << "ResTable_typeSpec has invalid id: " << type_spec->id); return false; } return true; @@ -310,14 +282,12 @@ bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) { bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, const ResChunk_header* chunk) { if (type_pool_.getError() != NO_ERROR) { - context_->GetDiagnostics()->Error(DiagMessage(source_) - << "missing type string pool"); + diag_->Error(DiagMessage(source_) << "missing type string pool"); return false; } if (key_pool_.getError() != NO_ERROR) { - context_->GetDiagnostics()->Error(DiagMessage(source_) - << "missing key string pool"); + diag_->Error(DiagMessage(source_) << "missing key string pool"); return false; } @@ -325,15 +295,12 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, // a lot and has its own code to handle variable size. const ResTable_type* type = ConvertTo<ResTable_type, kResTableTypeMinSize>(chunk); if (!type) { - context_->GetDiagnostics()->Error(DiagMessage(source_) - << "corrupt ResTable_type chunk"); + diag_->Error(DiagMessage(source_) << "corrupt ResTable_type chunk"); return false; } if (type->id == 0) { - context_->GetDiagnostics()->Error(DiagMessage(source_) - << "ResTable_type has invalid id: " - << (int)type->id); + diag_->Error(DiagMessage(source_) << "ResTable_type has invalid id: " << (int)type->id); return false; } @@ -344,9 +311,8 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, const ResourceType* parsed_type = ParseResourceType(type_str); if (!parsed_type) { - context_->GetDiagnostics()->Error( - DiagMessage(source_) << "invalid type name '" << type_str - << "' for type with ID " << (int)type->id); + diag_->Error(DiagMessage(source_) + << "invalid type name '" << type_str << "' for type with ID " << (int)type->id); return false; } @@ -357,12 +323,10 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, continue; } - const ResourceName name( - package->name, *parsed_type, - util::GetString(key_pool_, util::DeviceToHost32(entry->key.index))); + const ResourceName name(package->name, *parsed_type, + util::GetString(key_pool_, util::DeviceToHost32(entry->key.index))); - const ResourceId res_id(package->id.value(), type->id, - static_cast<uint16_t>(it.index())); + const ResourceId res_id(package->id.value(), type->id, static_cast<uint16_t>(it.index())); std::unique_ptr<Value> resource_value; if (entry->flags & ResTable_entry::FLAG_COMPLEX) { @@ -377,15 +341,13 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, } if (!resource_value) { - context_->GetDiagnostics()->Error( - DiagMessage(source_) << "failed to parse value for resource " << name - << " (" << res_id << ") with configuration '" - << config << "'"); + diag_->Error(DiagMessage(source_) << "failed to parse value for resource " << name << " (" + << res_id << ") with configuration '" << config << "'"); return false; } if (!table_->AddResourceAllowMangled(name, res_id, config, {}, std::move(resource_value), - context_->GetDiagnostics())) { + diag_)) { return false; } @@ -393,7 +355,7 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, Symbol symbol; symbol.state = SymbolState::kPublic; symbol.source = source_.WithLine(0); - if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, context_->GetDiagnostics())) { + if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, diag_)) { return false; } } @@ -428,29 +390,28 @@ std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& na const android::Res_value& value) { std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(name.type, config, value_pool_, value, &table_->string_pool); - if (files_ != nullptr && item != nullptr) { + if (files_ != nullptr) { FileReference* file_ref = ValueCast<FileReference>(item.get()); if (file_ref != nullptr) { file_ref->file = files_->FindFile(*file_ref->path); if (file_ref->file == nullptr) { - context_->GetDiagnostics()->Warn(DiagMessage() << "resource " << name << " for config '" - << config << "' is a file reference to '" - << *file_ref->path - << "' but no such path exists"); + diag_->Warn(DiagMessage() << "resource " << name << " for config '" << config + << "' is a file reference to '" << *file_ref->path + << "' but no such path exists"); } } } return item; } -std::unique_ptr<Value> BinaryResourceParser::ParseMapEntry( - const ResourceNameRef& name, const ConfigDescription& config, - const ResTable_map_entry* map) { +std::unique_ptr<Value> BinaryResourceParser::ParseMapEntry(const ResourceNameRef& name, + const ConfigDescription& config, + const ResTable_map_entry* map) { switch (name.type) { case ResourceType::kStyle: return ParseStyle(name, config, map); case ResourceType::kAttrPrivate: - // fallthrough + // fallthrough case ResourceType::kAttr: return ParseAttr(name, config, map); case ResourceType::kArray: @@ -463,16 +424,16 @@ std::unique_ptr<Value> BinaryResourceParser::ParseMapEntry( // We can ignore the value here. return util::make_unique<Id>(); default: - context_->GetDiagnostics()->Error(DiagMessage() << "illegal map type '" << ToString(name.type) - << "' (" << (int)name.type << ")"); + diag_->Error(DiagMessage() << "illegal map type '" << to_string(name.type) << "' (" + << (int)name.type << ")"); break; } return {}; } -std::unique_ptr<Style> BinaryResourceParser::ParseStyle( - const ResourceNameRef& name, const ConfigDescription& config, - const ResTable_map_entry* map) { +std::unique_ptr<Style> BinaryResourceParser::ParseStyle(const ResourceNameRef& name, + const ConfigDescription& config, + const ResTable_map_entry* map) { std::unique_ptr<Style> style = util::make_unique<Style>(); if (util::DeviceToHost32(map->parent.ident) != 0) { // The parent is a regular reference to a resource. @@ -495,19 +456,16 @@ std::unique_ptr<Style> BinaryResourceParser::ParseStyle( return style; } -std::unique_ptr<Attribute> BinaryResourceParser::ParseAttr( - const ResourceNameRef& name, const ConfigDescription& config, - const ResTable_map_entry* map) { - const bool is_weak = - (util::DeviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0; +std::unique_ptr<Attribute> BinaryResourceParser::ParseAttr(const ResourceNameRef& name, + const ConfigDescription& config, + const ResTable_map_entry* map) { + const bool is_weak = (util::DeviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0; std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(is_weak); // First we must discover what type of attribute this is. Find the type mask. - auto type_mask_iter = - std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool { - return util::DeviceToHost32(entry.name.ident) == - ResTable_map::ATTR_TYPE; - }); + auto type_mask_iter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool { + return util::DeviceToHost32(entry.name.ident) == ResTable_map::ATTR_TYPE; + }); if (type_mask_iter != end(map)) { attr->type_mask = util::DeviceToHost32(type_mask_iter->value.data); @@ -526,8 +484,7 @@ std::unique_ptr<Attribute> BinaryResourceParser::ParseAttr( continue; } - if (attr->type_mask & - (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) { + if (attr->type_mask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) { Attribute::Symbol symbol; symbol.value = util::DeviceToHost32(map_entry.value.data); symbol.symbol = Reference(util::DeviceToHost32(map_entry.name.ident)); @@ -539,9 +496,9 @@ std::unique_ptr<Attribute> BinaryResourceParser::ParseAttr( return attr; } -std::unique_ptr<Array> BinaryResourceParser::ParseArray( - const ResourceNameRef& name, const ConfigDescription& config, - const ResTable_map_entry* map) { +std::unique_ptr<Array> BinaryResourceParser::ParseArray(const ResourceNameRef& name, + const ConfigDescription& config, + const ResTable_map_entry* map) { std::unique_ptr<Array> array = util::make_unique<Array>(); for (const ResTable_map& map_entry : map) { array->elements.push_back(ParseValue(name, config, map_entry.value)); @@ -549,9 +506,9 @@ std::unique_ptr<Array> BinaryResourceParser::ParseArray( return array; } -std::unique_ptr<Plural> BinaryResourceParser::ParsePlural( - const ResourceNameRef& name, const ConfigDescription& config, - const ResTable_map_entry* map) { +std::unique_ptr<Plural> BinaryResourceParser::ParsePlural(const ResourceNameRef& name, + const ConfigDescription& config, + const ResTable_map_entry* map) { std::unique_ptr<Plural> plural = util::make_unique<Plural>(); for (const ResTable_map& map_entry : map) { std::unique_ptr<Item> item = ParseValue(name, config, map_entry.value); diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h index c41ada09e551..052f806e3b95 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.h +++ b/tools/aapt2/format/binary/BinaryResourceParser.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef AAPT_BINARY_RESOURCE_PARSER_H -#define AAPT_BINARY_RESOURCE_PARSER_H +#ifndef AAPT_FORMAT_BINARY_RESOURCEPARSER_H +#define AAPT_FORMAT_BINARY_RESOURCEPARSER_H #include <string> @@ -32,25 +32,17 @@ namespace aapt { struct SymbolTable_entry; -/* - * Parses a binary resource table (resources.arsc) and adds the entries - * to a ResourceTable. This is different than the libandroidfw ResTable - * in that it scans the table from top to bottom and doesn't require - * support for random access. It is also able to parse non-runtime - * chunks and types. - */ +// Parses a binary resource table (resources.arsc) and adds the entries to a ResourceTable. +// This is different than the libandroidfw ResTable in that it scans the table from top to bottom +// and doesn't require support for random access. class BinaryResourceParser { public: - /* - * Creates a parser, which will read `len` bytes from `data`, and - * add any resources parsed to `table`. `source` is for logging purposes. - */ - BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source, + // Creates a parser, which will read `len` bytes from `data`, and add any resources parsed to + // `table`. `source` is for logging purposes. + BinaryResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, const void* data, size_t data_len, io::IFileCollection* files = nullptr); - /* - * Parses the binary resource table and returns true if successful. - */ + // Parses the binary resource table and returns true if successful. bool Parse(); private: @@ -59,31 +51,25 @@ class BinaryResourceParser { bool ParseTable(const android::ResChunk_header* chunk); bool ParsePackage(const android::ResChunk_header* chunk); bool ParseTypeSpec(const android::ResChunk_header* chunk); - bool ParseType(const ResourceTablePackage* package, - const android::ResChunk_header* chunk); + bool ParseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk); bool ParseLibrary(const android::ResChunk_header* chunk); std::unique_ptr<Item> ParseValue(const ResourceNameRef& name, const ConfigDescription& config, const android::Res_value& value); - std::unique_ptr<Value> ParseMapEntry(const ResourceNameRef& name, - const ConfigDescription& config, + std::unique_ptr<Value> ParseMapEntry(const ResourceNameRef& name, const ConfigDescription& config, const android::ResTable_map_entry* map); - std::unique_ptr<Style> ParseStyle(const ResourceNameRef& name, - const ConfigDescription& config, + std::unique_ptr<Style> ParseStyle(const ResourceNameRef& name, const ConfigDescription& config, const android::ResTable_map_entry* map); - std::unique_ptr<Attribute> ParseAttr(const ResourceNameRef& name, - const ConfigDescription& config, + std::unique_ptr<Attribute> ParseAttr(const ResourceNameRef& name, const ConfigDescription& config, const android::ResTable_map_entry* map); - std::unique_ptr<Array> ParseArray(const ResourceNameRef& name, - const ConfigDescription& config, + std::unique_ptr<Array> ParseArray(const ResourceNameRef& name, const ConfigDescription& config, const android::ResTable_map_entry* map); - std::unique_ptr<Plural> ParsePlural(const ResourceNameRef& name, - const ConfigDescription& config, + std::unique_ptr<Plural> ParsePlural(const ResourceNameRef& name, const ConfigDescription& config, const android::ResTable_map_entry* map); /** @@ -94,7 +80,7 @@ class BinaryResourceParser { */ bool CollectMetaData(const android::ResTable_map& map_entry, Value* value); - IAaptContext* context_; + IDiagnostics* diag_; ResourceTable* table_; const Source source_; @@ -125,13 +111,10 @@ class BinaryResourceParser { namespace android { -/** - * Iterator functionality for ResTable_map_entry. - */ +// Iterator functionality for ResTable_map_entry. inline const ResTable_map* begin(const ResTable_map_entry* map) { - return (const ResTable_map*)((const uint8_t*)map + - aapt::util::DeviceToHost32(map->size)); + return (const ResTable_map*)((const uint8_t*)map + ::aapt::util::DeviceToHost32(map->size)); } inline const ResTable_map* end(const ResTable_map_entry* map) { @@ -140,4 +123,4 @@ inline const ResTable_map* end(const ResTable_map_entry* map) { } // namespace android -#endif // AAPT_BINARY_RESOURCE_PARSER_H +#endif // AAPT_FORMAT_BINARY_RESOURCEPARSER_H diff --git a/tools/aapt2/flatten/ChunkWriter.h b/tools/aapt2/format/binary/ChunkWriter.h index 968d3eef48ec..1892a295dcf5 100644 --- a/tools/aapt2/flatten/ChunkWriter.h +++ b/tools/aapt2/format/binary/ChunkWriter.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef AAPT_FLATTEN_CHUNKWRITER_H -#define AAPT_FLATTEN_CHUNKWRITER_H +#ifndef AAPT_FORMAT_BINARY_CHUNKWRITER_H +#define AAPT_FORMAT_BINARY_CHUNKWRITER_H #include "android-base/macros.h" #include "androidfw/ResourceTypes.h" @@ -27,7 +27,8 @@ namespace aapt { class ChunkWriter { public: - explicit inline ChunkWriter(BigBuffer* buffer) : buffer_(buffer) {} + explicit inline ChunkWriter(BigBuffer* buffer) : buffer_(buffer) { + } ChunkWriter(ChunkWriter&&) = default; ChunkWriter& operator=(ChunkWriter&&) = default; @@ -46,11 +47,17 @@ class ChunkWriter { return buffer_->NextBlock<T>(count); } - inline BigBuffer* buffer() { return buffer_; } + inline BigBuffer* buffer() { + return buffer_; + } - inline android::ResChunk_header* chunk_header() { return header_; } + inline android::ResChunk_header* chunk_header() { + return header_; + } - inline size_t size() { return buffer_->size() - start_size_; } + inline size_t size() { + return buffer_->size() - start_size_; + } inline android::ResChunk_header* Finish() { buffer_->Align4(); @@ -77,4 +84,4 @@ inline android::ResChunk_header* ChunkWriter::StartChunk(uint16_t type) { } // namespace aapt -#endif /* AAPT_FLATTEN_CHUNKWRITER_H */ +#endif /* AAPT_FORMAT_BINARY_CHUNKWRITER_H */ diff --git a/tools/aapt2/unflatten/ResChunkPullParser.cpp b/tools/aapt2/format/binary/ResChunkPullParser.cpp index 8d92bd94b0ae..fd6919d1de60 100644 --- a/tools/aapt2/unflatten/ResChunkPullParser.cpp +++ b/tools/aapt2/format/binary/ResChunkPullParser.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "unflatten/ResChunkPullParser.h" +#include "format/binary/ResChunkPullParser.h" #include <inttypes.h> #include <cstddef> @@ -44,9 +44,8 @@ ResChunkPullParser::Event ResChunkPullParser::Next() { if (event_ == Event::kStartDocument) { current_chunk_ = data_; } else { - current_chunk_ = - (const ResChunk_header*)(((const char*)current_chunk_) + - util::DeviceToHost32(current_chunk_->size)); + current_chunk_ = (const ResChunk_header*)(((const char*)current_chunk_) + + util::DeviceToHost32(current_chunk_->size)); } const std::ptrdiff_t diff = (const char*)current_chunk_ - (const char*)data_; diff --git a/tools/aapt2/unflatten/ResChunkPullParser.h b/tools/aapt2/format/binary/ResChunkPullParser.h index 58277531034c..5ff13598a31d 100644 --- a/tools/aapt2/unflatten/ResChunkPullParser.h +++ b/tools/aapt2/format/binary/ResChunkPullParser.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef AAPT_RES_CHUNK_PULL_PARSER_H -#define AAPT_RES_CHUNK_PULL_PARSER_H +#ifndef AAPT_FORMAT_BINARY_RESCHUNKPULLPARSER_H +#define AAPT_FORMAT_BINARY_RESCHUNKPULLPARSER_H #include <string> @@ -26,18 +26,13 @@ namespace aapt { -/** - * A pull parser, modeled after XmlPullParser, that reads - * android::ResChunk_header structs from a block of data. - * - * An android::ResChunk_header specifies a type, headerSize, - * and size. The pull parser will verify that the chunk's size - * doesn't extend beyond the available data, and will iterate - * over each chunk in the given block of data. - * - * Processing nested chunks is done by creating a new ResChunkPullParser - * pointing to the data portion of a chunk. - */ +// A pull parser, modeled after XmlPullParser, that reads android::ResChunk_header structs from a +// block of data. +// An android::ResChunk_header specifies a type, headerSize, and size. The pull parser will verify +// that the chunk's size doesn't extend beyond the available data, and will iterate over each chunk +// in the given block of data. +// Processing nested chunks is done by creating a new ResChunkPullParser pointing to the data +// portion of a chunk. class ResChunkPullParser { public: enum class Event { @@ -48,24 +43,18 @@ class ResChunkPullParser { kChunk, }; - /** - * Returns false if the event is EndDocument or BadDocument. - */ + // Returns false if the event is EndDocument or BadDocument. static bool IsGoodEvent(Event event); - /** - * Create a ResChunkPullParser to read android::ResChunk_headers - * from the memory pointed to by data, of len bytes. - */ + // Create a ResChunkPullParser to read android::ResChunk_headers from the memory pointed to by + // data, of len bytes. ResChunkPullParser(const void* data, size_t len); Event event() const; const std::string& error() const; const android::ResChunk_header* chunk() const; - /** - * Move to the next android::ResChunk_header. - */ + // Move to the next android::ResChunk_header. Event Next(); private: @@ -86,15 +75,12 @@ inline static const T* ConvertTo(const android::ResChunk_header* chunk) { return reinterpret_cast<const T*>(chunk); } -inline static const uint8_t* GetChunkData( - const android::ResChunk_header* chunk) { - return reinterpret_cast<const uint8_t*>(chunk) + - util::DeviceToHost16(chunk->headerSize); +inline static const uint8_t* GetChunkData(const android::ResChunk_header* chunk) { + return reinterpret_cast<const uint8_t*>(chunk) + util::DeviceToHost16(chunk->headerSize); } inline static uint32_t GetChunkDataLen(const android::ResChunk_header* chunk) { - return util::DeviceToHost32(chunk->size) - - util::DeviceToHost16(chunk->headerSize); + return util::DeviceToHost32(chunk->size) - util::DeviceToHost16(chunk->headerSize); } // @@ -109,13 +95,16 @@ inline ResChunkPullParser::ResChunkPullParser(const void* data, size_t len) : event_(Event::kStartDocument), data_(reinterpret_cast<const android::ResChunk_header*>(data)), len_(len), - current_chunk_(nullptr) {} + current_chunk_(nullptr) { +} inline ResChunkPullParser::Event ResChunkPullParser::event() const { return event_; } -inline const std::string& ResChunkPullParser::error() const { return error_; } +inline const std::string& ResChunkPullParser::error() const { + return error_; +} inline const android::ResChunk_header* ResChunkPullParser::chunk() const { return current_chunk_; @@ -123,4 +112,4 @@ inline const android::ResChunk_header* ResChunkPullParser::chunk() const { } // namespace aapt -#endif // AAPT_RES_CHUNK_PULL_PARSER_H +#endif // AAPT_FORMAT_BINARY_RESCHUNKPULLPARSER_H diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/format/binary/ResourceTypeExtensions.h index 6359b4143f1e..7f58df808cc7 100644 --- a/tools/aapt2/flatten/ResourceTypeExtensions.h +++ b/tools/aapt2/format/binary/ResourceTypeExtensions.h @@ -14,18 +14,14 @@ * limitations under the License. */ -#ifndef AAPT_RESOURCE_TYPE_EXTENSIONS_H -#define AAPT_RESOURCE_TYPE_EXTENSIONS_H +#ifndef AAPT_FORMAT_BINARY_RESOURCETYPEEXTENSIONS_H +#define AAPT_FORMAT_BINARY_RESOURCETYPEEXTENSIONS_H #include "androidfw/ResourceTypes.h" namespace aapt { -/** - * An alternative struct to use instead of ResTable_map_entry. This one is a - * standard_layout - * struct. - */ +// An alternative struct to use instead of ResTable_map_entry. This one is a standard_layout struct. struct ResTable_entry_ext { android::ResTable_entry entry; android::ResTable_ref parent; @@ -34,4 +30,4 @@ struct ResTable_entry_ext { } // namespace aapt -#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H +#endif // AAPT_FORMAT_BINARY_RESOURCETYPEEXTENSIONS_H diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 32924215ab4e..a3034df91d82 100644 --- a/tools/aapt2/flatten/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "flatten/TableFlattener.h" +#include "format/binary/TableFlattener.h" #include <algorithm> #include <numeric> @@ -29,8 +29,8 @@ #include "ResourceValues.h" #include "SdkConstants.h" #include "ValueVisitor.h" -#include "flatten/ChunkWriter.h" -#include "flatten/ResourceTypeExtensions.h" +#include "format/binary/ChunkWriter.h" +#include "format/binary/ResourceTypeExtensions.h" #include "util/BigBuffer.h" using namespace android; @@ -77,12 +77,13 @@ struct FlatEntry { uint32_t entry_key; }; -class MapFlattenVisitor : public RawValueVisitor { +class MapFlattenVisitor : public ValueVisitor { public: - using RawValueVisitor::Visit; + using ValueVisitor::Visit; MapFlattenVisitor(ResTable_entry_ext* out_entry, BigBuffer* buffer) - : out_entry_(out_entry), buffer_(buffer) {} + : out_entry_(out_entry), buffer_(buffer) { + } void Visit(Attribute* attr) override { { @@ -93,15 +94,13 @@ class MapFlattenVisitor : public RawValueVisitor { if (attr->min_int != std::numeric_limits<int32_t>::min()) { Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN)); - BinaryPrimitive val(Res_value::TYPE_INT_DEC, - static_cast<uint32_t>(attr->min_int)); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->min_int)); FlattenEntry(&key, &val); } if (attr->max_int != std::numeric_limits<int32_t>::max()) { Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX)); - BinaryPrimitive val(Res_value::TYPE_INT_DEC, - static_cast<uint32_t>(attr->max_int)); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->max_int)); FlattenEntry(&key, &val); } @@ -189,7 +188,9 @@ class MapFlattenVisitor : public RawValueVisitor { * Call this after visiting a Value. This will finish any work that * needs to be done to prepare the entry. */ - void Finish() { out_entry_->count = util::HostToDevice32(entry_count_); } + void Finish() { + out_entry_->count = util::HostToDevice32(entry_count_); + } private: DISALLOW_COPY_AND_ASSIGN(MapFlattenVisitor); @@ -219,12 +220,16 @@ class MapFlattenVisitor : public RawValueVisitor { class PackageFlattener { public: PackageFlattener(IAaptContext* context, ResourceTablePackage* package, - const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries) + const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries, + bool collapse_key_stringpool, const std::set<std::string>& whitelisted_resources) : context_(context), diag_(context->GetDiagnostics()), package_(package), shared_libs_(shared_libs), - use_sparse_entries_(use_sparse_entries) {} + use_sparse_entries_(use_sparse_entries), + collapse_key_stringpool_(collapse_key_stringpool), + whitelisted_resources_(whitelisted_resources) { + } bool FlattenPackage(BigBuffer* buffer) { ChunkWriter pkg_writer(buffer); @@ -272,9 +277,9 @@ class PackageFlattener { template <typename T, bool IsItem> T* WriteEntry(FlatEntry* entry, BigBuffer* buffer) { - static_assert(std::is_same<ResTable_entry, T>::value || - std::is_same<ResTable_entry_ext, T>::value, - "T must be ResTable_entry or ResTable_entry_ext"); + static_assert( + std::is_same<ResTable_entry, T>::value || std::is_same<ResTable_entry_ext, T>::value, + "T must be ResTable_entry or ResTable_entry_ext"); T* result = buffer->NextBlock<T>(); ResTable_entry* out_entry = (ResTable_entry*)result; @@ -303,8 +308,7 @@ class PackageFlattener { CHECK(item->Flatten(outValue)) << "flatten failed"; outValue->size = util::HostToDevice16(sizeof(*outValue)); } else { - ResTable_entry_ext* out_entry = - WriteEntry<ResTable_entry_ext, false>(entry, buffer); + ResTable_entry_ext* out_entry = WriteEntry<ResTable_entry_ext, false>(entry, buffer); MapFlattenVisitor visitor(out_entry, buffer); entry->value->Accept(&visitor); visitor.Finish(); @@ -319,8 +323,7 @@ class PackageFlattener { CHECK(num_total_entries <= std::numeric_limits<uint16_t>::max()); ChunkWriter type_writer(buffer); - ResTable_type* type_header = - type_writer.StartChunk<ResTable_type>(RES_TABLE_TYPE_TYPE); + ResTable_type* type_header = type_writer.StartChunk<ResTable_type>(RES_TABLE_TYPE_TYPE); type_header->id = type->id.value(); type_header->config = config; type_header->config.swapHtoD(); @@ -396,8 +399,7 @@ class PackageFlattener { sorted_types.push_back(type.get()); } - std::sort(sorted_types.begin(), sorted_types.end(), - cmp_ids<ResourceTableType>); + std::sort(sorted_types.begin(), sorted_types.end(), cmp_ids<ResourceTableType>); return sorted_types; } @@ -412,13 +414,11 @@ class PackageFlattener { return sorted_entries; } - bool FlattenTypeSpec(ResourceTableType* type, - std::vector<ResourceEntry*>* sorted_entries, + bool FlattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sorted_entries, BigBuffer* buffer) { ChunkWriter type_spec_writer(buffer); ResTable_typeSpec* spec_header = - type_spec_writer.StartChunk<ResTable_typeSpec>( - RES_TABLE_TYPE_SPEC_TYPE); + type_spec_writer.StartChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE); spec_header->id = type->id.value(); if (sorted_entries->empty()) { @@ -444,8 +444,7 @@ class PackageFlattener { // Populate the config masks for this entry. if (entry->symbol_status.state == SymbolState::kPublic) { - config_masks[entry->id.value()] |= - util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); + config_masks[entry->id.value()] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); } const size_t config_count = entry->values.size(); @@ -477,7 +476,7 @@ class PackageFlattener { expected_type_id++; } expected_type_id++; - type_pool_.MakeRef(ToString(type->type)); + type_pool_.MakeRef(to_string(type->type)); std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type); if (sorted_entries.empty()) { @@ -498,13 +497,23 @@ class PackageFlattener { // configuration available. Here we reverse this to match the binary // table. std::map<ConfigDescription, std::vector<FlatEntry>> config_to_entry_list_map; - for (ResourceEntry* entry : sorted_entries) { - const uint32_t key_index = (uint32_t)key_pool_.MakeRef(entry->name).index(); + // hardcoded string uses characters which make it an invalid resource name + const std::string obfuscated_resource_name = "0_resource_name_obfuscated"; + + for (ResourceEntry* entry : sorted_entries) { + uint32_t local_key_index; + if (!collapse_key_stringpool_ || + whitelisted_resources_.find(entry->name) != whitelisted_resources_.end()) { + local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index(); + } else { + // resource isn't whitelisted, add it as obfuscated value + local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index(); + } // Group values by configuration. for (auto& config_value : entry->values) { config_to_entry_list_map[config_value->config].push_back( - FlatEntry{entry, config_value->value.get(), key_index}); + FlatEntry{entry, config_value->value.get(), local_key_index}); } } @@ -553,6 +562,8 @@ class PackageFlattener { bool use_sparse_entries_; StringPool type_pool_; StringPool key_pool_; + bool collapse_key_stringpool_; + const std::set<std::string>& whitelisted_resources_; }; } // namespace @@ -597,7 +608,8 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { } PackageFlattener flattener(context, package.get(), &table->included_packages_, - options_.use_sparse_entries); + options_.use_sparse_entries, options_.collapse_key_stringpool, + options_.whitelisted_resources); if (!flattener.FlattenPackage(&package_buffer)) { return false; } diff --git a/tools/aapt2/flatten/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h index 223aef8119c0..c2e1d4b4ce8c 100644 --- a/tools/aapt2/flatten/TableFlattener.h +++ b/tools/aapt2/format/binary/TableFlattener.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef AAPT_FLATTEN_TABLEFLATTENER_H -#define AAPT_FLATTEN_TABLEFLATTENER_H +#ifndef AAPT_FORMAT_BINARY_TABLEFLATTENER_H +#define AAPT_FORMAT_BINARY_TABLEFLATTENER_H #include "android-base/macros.h" @@ -35,12 +35,21 @@ struct TableFlattenerOptions { // This is only available on platforms O+ and will only be respected when // minSdk is O+. bool use_sparse_entries = false; + + // When true, the key string pool in the final ResTable + // is collapsed to a single entry. All resource entries + // have name indices that point to this single value + bool collapse_key_stringpool = false; + + // Set of whitelisted resource names to avoid altering in key stringpool + std::set<std::string> whitelisted_resources; }; class TableFlattener : public IResourceTableConsumer { public: explicit TableFlattener(const TableFlattenerOptions& options, BigBuffer* buffer) - : options_(options), buffer_(buffer) {} + : options_(options), buffer_(buffer) { + } bool Consume(IAaptContext* context, ResourceTable* table) override; @@ -53,4 +62,4 @@ class TableFlattener : public IResourceTableConsumer { } // namespace aapt -#endif /* AAPT_FLATTEN_TABLEFLATTENER_H */ +#endif /* AAPT_FORMAT_BINARY_TABLEFLATTENER_H */ diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index 4fdb2eced59b..f0b80d22a9b4 100644 --- a/tools/aapt2/flatten/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -14,14 +14,14 @@ * limitations under the License. */ -#include "flatten/TableFlattener.h" +#include "format/binary/TableFlattener.h" #include "android-base/stringprintf.h" #include "ResourceUtils.h" #include "SdkConstants.h" +#include "format/binary/BinaryResourceParser.h" #include "test/Test.h" -#include "unflatten/BinaryResourceParser.h" #include "util/Util.h" using namespace android; @@ -34,10 +34,8 @@ namespace aapt { class TableFlattenerTest : public ::testing::Test { public: void SetUp() override { - context_ = test::ContextBuilder() - .SetCompilationPackage("com.app.test") - .SetPackageId(0x7f) - .Build(); + context_ = + test::ContextBuilder().SetCompilationPackage("com.app.test").SetPackageId(0x7f).Build(); } ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options, @@ -73,19 +71,18 @@ class TableFlattenerTest : public ::testing::Test { return result; } - BinaryResourceParser parser(context, out_table, {}, content.data(), content.size()); + BinaryResourceParser parser(context->GetDiagnostics(), out_table, {}, content.data(), + content.size()); if (!parser.Parse()) { return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; } return ::testing::AssertionSuccess(); } - ::testing::AssertionResult Exists(ResTable* table, - const StringPiece& expected_name, + ::testing::AssertionResult Exists(ResTable* table, const StringPiece& expected_name, const ResourceId& expected_id, const ConfigDescription& expected_config, - const uint8_t expected_data_type, - const uint32_t expected_data, + const uint8_t expected_data_type, const uint32_t expected_data, const uint32_t expected_spec_flags) { const ResourceName expected_res_name = test::ParseNameOrDie(expected_name); @@ -94,28 +91,26 @@ class TableFlattenerTest : public ::testing::Test { ResTable_config config; Res_value val; uint32_t spec_flags; - if (table->getResource(expected_id.id, &val, false, 0, &spec_flags, - &config) < 0) { + if (table->getResource(expected_id.id, &val, false, 0, &spec_flags, &config) < 0) { return ::testing::AssertionFailure() << "could not find resource with"; } if (expected_data_type != val.dataType) { return ::testing::AssertionFailure() << "expected data type " << std::hex << (int)expected_data_type - << " but got data type " << (int)val.dataType << std::dec - << " instead"; + << " but got data type " << (int)val.dataType << std::dec << " instead"; } if (expected_data != val.data) { return ::testing::AssertionFailure() - << "expected data " << std::hex << expected_data - << " but got data " << val.data << std::dec << " instead"; + << "expected data " << std::hex << expected_data << " but got data " << val.data + << std::dec << " instead"; } if (expected_spec_flags != spec_flags) { return ::testing::AssertionFailure() - << "expected specFlags " << std::hex << expected_spec_flags - << " but got specFlags " << spec_flags << std::dec << " instead"; + << "expected specFlags " << std::hex << expected_spec_flags << " but got specFlags " + << spec_flags << std::dec << " instead"; } ResTable::resource_name actual_name; @@ -127,16 +122,23 @@ class TableFlattenerTest : public ::testing::Test { if (!resName) { return ::testing::AssertionFailure() << "expected name '" << expected_res_name << "' but got '" - << StringPiece16(actual_name.package, actual_name.packageLen) - << ":" << StringPiece16(actual_name.type, actual_name.typeLen) - << "/" << StringPiece16(actual_name.name, actual_name.nameLen) - << "'"; + << StringPiece16(actual_name.package, actual_name.packageLen) << ":" + << StringPiece16(actual_name.type, actual_name.typeLen) << "/" + << StringPiece16(actual_name.name, actual_name.nameLen) << "'"; + } + + ResourceName actual_res_name(resName.value()); + + if (expected_res_name.entry != actual_res_name.entry || + expected_res_name.package != actual_res_name.package || + expected_res_name.type != actual_res_name.type) { + return ::testing::AssertionFailure() << "expected resource '" << expected_res_name.to_string() + << "' but got '" << actual_res_name.to_string() << "'"; } if (expected_config != config) { - return ::testing::AssertionFailure() << "expected config '" - << expected_config << "' but got '" - << ConfigDescription(config) << "'"; + return ::testing::AssertionFailure() << "expected config '" << expected_config + << "' but got '" << ConfigDescription(config) << "'"; } return ::testing::AssertionSuccess(); } @@ -152,57 +154,46 @@ TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) { .AddSimple("com.app.test:id/one", ResourceId(0x7f020000)) .AddSimple("com.app.test:id/two", ResourceId(0x7f020001)) .AddValue("com.app.test:id/three", ResourceId(0x7f020002), - test::BuildReference("com.app.test:id/one", - ResourceId(0x7f020000))) + test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000))) .AddValue("com.app.test:integer/one", ResourceId(0x7f030000), - util::make_unique<BinaryPrimitive>( - uint8_t(Res_value::TYPE_INT_DEC), 1u)) + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)) .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"), ResourceId(0x7f030000), - util::make_unique<BinaryPrimitive>( - uint8_t(Res_value::TYPE_INT_DEC), 2u)) + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)) .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo") - .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), - "res/layout/bar.xml") + .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml") .Build(); ResTable res_table; ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table)); - EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020000), - {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020000), {}, + Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); - EXPECT_TRUE(Exists(&res_table, "com.app.test:id/two", ResourceId(0x7f020001), - {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/two", ResourceId(0x7f020001), {}, + Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); - EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", - ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, - 0x7f020000u, 0u)); + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {}, + Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); - EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", - ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u, - ResTable_config::CONFIG_VERSION)); + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", ResourceId(0x7f030000), {}, + Res_value::TYPE_INT_DEC, 1u, ResTable_config::CONFIG_VERSION)); - EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", - ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), - Res_value::TYPE_INT_DEC, 2u, + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", ResourceId(0x7f030000), + test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u, ResTable_config::CONFIG_VERSION)); std::u16string foo_str = u"foo"; - ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), - foo_str.size()); + ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size()); ASSERT_GE(idx, 0); - EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", - ResourceId(0x7f040000), {}, Res_value::TYPE_STRING, - (uint32_t)idx, 0u)); + EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", ResourceId(0x7f040000), {}, + Res_value::TYPE_STRING, (uint32_t)idx, 0u)); std::u16string bar_path = u"res/layout/bar.xml"; - idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), - bar_path.size()); + idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size()); ASSERT_GE(idx, 0); - EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/bar", - ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, - (uint32_t)idx, 0u)); + EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/bar", ResourceId(0x7f050000), {}, + Res_value::TYPE_STRING, (uint32_t)idx, 0u)); } TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) { @@ -216,11 +207,10 @@ TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) { ResTable res_table; ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table)); - EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020001), - {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); - EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", - ResourceId(0x7f020003), {}, Res_value::TYPE_INT_BOOLEAN, - 0u, 0u)); + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020001), {}, + Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020003), {}, + Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); } TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) { @@ -231,8 +221,7 @@ TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() .SetPackageId("android", 0x01) - .AddValue("android:attr/foo", ResourceId(0x01010000), - util::make_unique<Attribute>(attr)) + .AddValue("android:attr/foo", ResourceId(0x01010000), util::make_unique<Attribute>(attr)) .Build(); ResourceTable result; @@ -299,7 +288,7 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) { // Attempt to parse the sparse contents. ResourceTable sparse_table; - BinaryResourceParser parser(context.get(), &sparse_table, Source("test.arsc"), + BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"), sparse_contents.data(), sparse_contents.size()); ASSERT_TRUE(parser.Parse()); @@ -308,9 +297,12 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) { ASSERT_THAT(value, NotNull()); EXPECT_EQ(0u, value->value.data); - ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1", sparse_config), IsNull()); + ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1", + sparse_config), + IsNull()); - value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4", sparse_config); + value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4", + sparse_config); ASSERT_THAT(value, NotNull()); EXPECT_EQ(4u, value->value.data); } @@ -467,4 +459,113 @@ TEST_F(TableFlattenerTest, LongSharedLibraryPackageNameIsIllegal) { ASSERT_FALSE(Flatten(context.get(), {}, table.get(), &result)); } +TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoWhitelistSucceeds) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddSimple("com.app.test:id/one", ResourceId(0x7f020000)) + .AddSimple("com.app.test:id/two", ResourceId(0x7f020001)) + .AddValue("com.app.test:id/three", ResourceId(0x7f020002), + test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000))) + .AddValue("com.app.test:integer/one", ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)) + .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"), + ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)) + .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo") + .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml") + .Build(); + + TableFlattenerOptions options; + options.collapse_key_stringpool = true; + + ResTable res_table; + + ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", + ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u, + ResTable_config::CONFIG_VERSION)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", + ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, + 2u, ResTable_config::CONFIG_VERSION)); + + std::u16string foo_str = u"foo"; + ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated", + ResourceId(0x7f040000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u)); + + std::u16string bar_path = u"res/layout/bar.xml"; + idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated", + ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u)); +} + +TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddSimple("com.app.test:id/one", ResourceId(0x7f020000)) + .AddSimple("com.app.test:id/two", ResourceId(0x7f020001)) + .AddValue("com.app.test:id/three", ResourceId(0x7f020002), + test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000))) + .AddValue("com.app.test:integer/one", ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)) + .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"), + ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)) + .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo") + .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml") + .Build(); + + TableFlattenerOptions options; + options.collapse_key_stringpool = true; + options.whitelisted_resources.insert("test"); + options.whitelisted_resources.insert("three"); + ResTable res_table; + + ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {}, + Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", + ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u, + ResTable_config::CONFIG_VERSION)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", + ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, + 2u, ResTable_config::CONFIG_VERSION)); + + std::u16string foo_str = u"foo"; + ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", ResourceId(0x7f040000), {}, + Res_value::TYPE_STRING, (uint32_t)idx, 0u)); + + std::u16string bar_path = u"res/layout/bar.xml"; + idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated", + ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u)); +} + } // namespace aapt diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/format/binary/XmlFlattener.cpp index 6214d70a7579..345cc95cfb29 100644 --- a/tools/aapt2/flatten/XmlFlattener.cpp +++ b/tools/aapt2/format/binary/XmlFlattener.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "flatten/XmlFlattener.h" +#include "format/binary/XmlFlattener.h" #include <algorithm> #include <map> @@ -26,8 +26,8 @@ #include "utils/misc.h" #include "SdkConstants.h" -#include "flatten/ChunkWriter.h" -#include "flatten/ResourceTypeExtensions.h" +#include "format/binary/ChunkWriter.h" +#include "format/binary/ResourceTypeExtensions.h" #include "xml/XmlDom.h" using namespace android; @@ -56,9 +56,9 @@ static bool cmp_xml_attribute_by_id(const xml::Attribute* a, const xml::Attribut return false; } -class XmlFlattenerVisitor : public xml::Visitor { +class XmlFlattenerVisitor : public xml::ConstVisitor { public: - using xml::Visitor::Visit; + using xml::ConstVisitor::Visit; StringPool pool; std::map<uint8_t, StringPool> package_pools; @@ -71,9 +71,10 @@ class XmlFlattenerVisitor : public xml::Visitor { std::vector<StringFlattenDest> string_refs; XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) - : buffer_(buffer), options_(options) {} + : buffer_(buffer), options_(options) { + } - void Visit(xml::Text* node) override { + void Visit(const xml::Text* node) override { if (util::TrimWhitespace(node->text).empty()) { // Skip whitespace only text nodes. return; @@ -94,7 +95,7 @@ class XmlFlattenerVisitor : public xml::Visitor { writer.Finish(); } - void Visit(xml::Element* node) override { + void Visit(const xml::Element* node) override { for (const xml::NamespaceDecl& decl : node->namespace_decls) { // Skip dedicated tools namespace. if (decl.uri != xml::kSchemaTools) { @@ -124,7 +125,7 @@ class XmlFlattenerVisitor : public xml::Visitor { start_writer.Finish(); } - xml::Visitor::Visit(node); + xml::ConstVisitor::Visit(node); { ChunkWriter end_writer(buffer_); @@ -152,15 +153,14 @@ class XmlFlattenerVisitor : public xml::Visitor { private: DISALLOW_COPY_AND_ASSIGN(XmlFlattenerVisitor); - void AddString(const StringPiece& str, uint32_t priority, - android::ResStringPool_ref* dest, + void AddString(const StringPiece& str, uint32_t priority, android::ResStringPool_ref* dest, bool treat_empty_string_as_null = false) { if (str.empty() && treat_empty_string_as_null) { // Some parts of the runtime treat null differently than empty string. dest->index = util::DeviceToHost32(-1); } else { - string_refs.push_back(StringFlattenDest{ - pool.MakeRef(str, StringPool::Context(priority)), dest}); + string_refs.push_back( + StringFlattenDest{pool.MakeRef(str, StringPool::Context(priority)), dest}); } } @@ -182,12 +182,13 @@ class XmlFlattenerVisitor : public xml::Visitor { writer.Finish(); } - void WriteAttributes(xml::Element* node, ResXMLTree_attrExt* flat_elem, ChunkWriter* writer) { + void WriteAttributes(const xml::Element* node, ResXMLTree_attrExt* flat_elem, + ChunkWriter* writer) { filtered_attrs_.clear(); filtered_attrs_.reserve(node->attributes.size()); // Filter the attributes. - for (xml::Attribute& attr : node->attributes) { + for (const xml::Attribute& attr : node->attributes) { if (attr.namespace_uri != xml::kSchemaTools) { filtered_attrs_.push_back(&attr); } @@ -208,8 +209,7 @@ class XmlFlattenerVisitor : public xml::Visitor { uint16_t attribute_index = 1; for (const xml::Attribute* xml_attr : filtered_attrs_) { // Assign the indices for specific attributes. - if (xml_attr->compiled_attribute && - xml_attr->compiled_attribute.value().id && + if (xml_attr->compiled_attribute && xml_attr->compiled_attribute.value().id && xml_attr->compiled_attribute.value().id.value() == kIdAttr) { flat_elem->idIndex = util::HostToDevice16(attribute_index); } else if (xml_attr->namespace_uri.empty()) { @@ -241,9 +241,8 @@ class XmlFlattenerVisitor : public xml::Visitor { // Lookup the StringPool for this package and make the reference there. const xml::AaptAttribute& aapt_attr = xml_attr->compiled_attribute.value(); - StringPool::Ref name_ref = - package_pools[aapt_attr.id.value().package_id()].MakeRef( - xml_attr->name, StringPool::Context(aapt_attr.id.value().id)); + StringPool::Ref name_ref = package_pools[aapt_attr.id.value().package_id()].MakeRef( + xml_attr->name, StringPool::Context(aapt_attr.id.value().id)); // Add it to the list of strings to flatten. AddString(name_ref, &flat_attr->name); @@ -272,7 +271,7 @@ class XmlFlattenerVisitor : public xml::Visitor { flat_attr->typedValue.dataType = android::Res_value::TYPE_STRING; AddString(str_builder.ToString(), kLowPriority, - (ResStringPool_ref*) &flat_attr->typedValue.data); + (ResStringPool_ref*)&flat_attr->typedValue.data); } flat_attr->typedValue.size = util::HostToDevice16(sizeof(flat_attr->typedValue)); @@ -284,12 +283,12 @@ class XmlFlattenerVisitor : public xml::Visitor { XmlFlattenerOptions options_; // Scratch vector to filter attributes. We avoid allocations making this a member. - std::vector<xml::Attribute*> filtered_attrs_; + std::vector<const xml::Attribute*> filtered_attrs_; }; } // namespace -bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) { +bool XmlFlattener::Flatten(IAaptContext* context, const xml::Node* node) { BigBuffer node_buffer(1024); XmlFlattenerVisitor visitor(&node_buffer, options_); node->Accept(&visitor); @@ -343,7 +342,7 @@ bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) { return true; } -bool XmlFlattener::Consume(IAaptContext* context, xml::XmlResource* resource) { +bool XmlFlattener::Consume(IAaptContext* context, const xml::XmlResource* resource) { if (!resource->root) { return false; } diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/format/binary/XmlFlattener.h index 9e436acfa36a..1f9e777f7a1a 100644 --- a/tools/aapt2/flatten/XmlFlattener.h +++ b/tools/aapt2/format/binary/XmlFlattener.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef AAPT_FLATTEN_XMLFLATTENER_H -#define AAPT_FLATTEN_XMLFLATTENER_H +#ifndef AAPT_FORMAT_BINARY_XMLFLATTENER_H +#define AAPT_FORMAT_BINARY_XMLFLATTENER_H #include "android-base/macros.h" @@ -26,9 +26,7 @@ namespace aapt { struct XmlFlattenerOptions { - /** - * Keep attribute raw string values along with typed values. - */ + // Keep attribute raw string values along with typed values. bool keep_raw_values = false; // Encode the strings in UTF-16. Only needed for AndroidManifest.xml to avoid a bug in @@ -36,17 +34,18 @@ struct XmlFlattenerOptions { bool use_utf16 = false; }; -class XmlFlattener : public IXmlResourceConsumer { +class XmlFlattener { public: XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) - : buffer_(buffer), options_(options) {} + : buffer_(buffer), options_(options) { + } - bool Consume(IAaptContext* context, xml::XmlResource* resource) override; + bool Consume(IAaptContext* context, const xml::XmlResource* resource); private: DISALLOW_COPY_AND_ASSIGN(XmlFlattener); - bool Flatten(IAaptContext* context, xml::Node* node); + bool Flatten(IAaptContext* context, const xml::Node* node); BigBuffer* buffer_; XmlFlattenerOptions options_; @@ -54,4 +53,4 @@ class XmlFlattener : public IXmlResourceConsumer { } // namespace aapt -#endif /* AAPT_FLATTEN_XMLFLATTENER_H */ +#endif /* AAPT_FORMAT_BINARY_XMLFLATTENER_H */ diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/format/binary/XmlFlattener_test.cpp index a57e3178accd..0450f6c16de5 100644 --- a/tools/aapt2/flatten/XmlFlattener_test.cpp +++ b/tools/aapt2/format/binary/XmlFlattener_test.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "flatten/XmlFlattener.h" +#include "format/binary/XmlFlattener.h" #include "androidfw/ResourceTypes.h" @@ -55,8 +55,7 @@ class XmlFlattenerTest : public ::testing::Test { .Build(); } - ::testing::AssertionResult Flatten(xml::XmlResource* doc, - android::ResXMLTree* out_tree, + ::testing::AssertionResult Flatten(xml::XmlResource* doc, android::ResXMLTree* out_tree, const XmlFlattenerOptions& options = {}) { using namespace android; // For NO_ERROR on windows because it is a macro. diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp new file mode 100644 index 000000000000..0f0bce8bf5a7 --- /dev/null +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -0,0 +1,869 @@ +/* + * Copyright (C) 2016 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 "format/proto/ProtoDeserialize.h" + +#include "android-base/logging.h" +#include "android-base/macros.h" +#include "androidfw/ResourceTypes.h" + +#include "Locale.h" +#include "ResourceTable.h" +#include "ResourceUtils.h" +#include "ValueVisitor.h" + +using ::android::ResStringPool; + +namespace aapt { + +namespace { + +class ReferenceIdToNameVisitor : public DescendingValueVisitor { + public: + using DescendingValueVisitor::Visit; + + explicit ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceNameRef>* mapping) + : mapping_(mapping) { + CHECK(mapping_ != nullptr); + } + + void Visit(Reference* reference) override { + if (!reference->id || !reference->id.value().is_valid()) { + return; + } + + ResourceId id = reference->id.value(); + auto cache_iter = mapping_->find(id); + if (cache_iter != mapping_->end()) { + reference->name = cache_iter->second.ToResourceName(); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(ReferenceIdToNameVisitor); + + const std::map<ResourceId, ResourceNameRef>* mapping_; +}; + +} // namespace + +bool DeserializeConfigFromPb(const pb::Configuration& pb_config, ConfigDescription* out_config, + std::string* out_error) { + out_config->mcc = static_cast<uint16_t>(pb_config.mcc()); + out_config->mnc = static_cast<uint16_t>(pb_config.mnc()); + + if (!pb_config.locale().empty()) { + LocaleValue lv; + if (!lv.InitFromBcp47Tag(pb_config.locale())) { + std::ostringstream error; + error << "configuration has invalid locale '" << pb_config.locale() << "'"; + *out_error = error.str(); + return false; + } + lv.WriteTo(out_config); + } + + switch (pb_config.layout_direction()) { + case pb::Configuration_LayoutDirection_LAYOUT_DIRECTION_LTR: + out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_LAYOUTDIR) | + ConfigDescription::LAYOUTDIR_LTR; + break; + + case pb::Configuration_LayoutDirection_LAYOUT_DIRECTION_RTL: + out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_LAYOUTDIR) | + ConfigDescription::LAYOUTDIR_RTL; + break; + + default: + break; + } + + out_config->smallestScreenWidthDp = static_cast<uint16_t>(pb_config.smallest_screen_width_dp()); + out_config->screenWidthDp = static_cast<uint16_t>(pb_config.screen_width_dp()); + out_config->screenHeightDp = static_cast<uint16_t>(pb_config.screen_height_dp()); + + switch (pb_config.screen_layout_size()) { + case pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_SMALL: + out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_SCREENSIZE) | + ConfigDescription::SCREENSIZE_SMALL; + break; + + case pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_NORMAL: + out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_SCREENSIZE) | + ConfigDescription::SCREENSIZE_NORMAL; + break; + + case pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_LARGE: + out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_SCREENSIZE) | + ConfigDescription::SCREENSIZE_LARGE; + break; + + case pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_XLARGE: + out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_SCREENSIZE) | + ConfigDescription::SCREENSIZE_XLARGE; + break; + + default: + break; + } + + switch (pb_config.screen_layout_long()) { + case pb::Configuration_ScreenLayoutLong_SCREEN_LAYOUT_LONG_LONG: + out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_SCREENLONG) | + ConfigDescription::SCREENLONG_YES; + break; + + case pb::Configuration_ScreenLayoutLong_SCREEN_LAYOUT_LONG_NOTLONG: + out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_SCREENLONG) | + ConfigDescription::SCREENLONG_NO; + break; + + default: + break; + } + + switch (pb_config.screen_round()) { + case pb::Configuration_ScreenRound_SCREEN_ROUND_ROUND: + out_config->screenLayout2 = + (out_config->screenLayout2 & ~ConfigDescription::MASK_SCREENROUND) | + ConfigDescription::SCREENROUND_YES; + break; + + case pb::Configuration_ScreenRound_SCREEN_ROUND_NOTROUND: + out_config->screenLayout2 = + (out_config->screenLayout2 & ~ConfigDescription::MASK_SCREENROUND) | + ConfigDescription::SCREENROUND_NO; + break; + + default: + break; + } + + switch (pb_config.wide_color_gamut()) { + case pb::Configuration_WideColorGamut_WIDE_COLOR_GAMUT_WIDECG: + out_config->colorMode = (out_config->colorMode & ~ConfigDescription::MASK_WIDE_COLOR_GAMUT) | + ConfigDescription::WIDE_COLOR_GAMUT_YES; + break; + + case pb::Configuration_WideColorGamut_WIDE_COLOR_GAMUT_NOWIDECG: + out_config->colorMode = (out_config->colorMode & ~ConfigDescription::MASK_WIDE_COLOR_GAMUT) | + ConfigDescription::WIDE_COLOR_GAMUT_NO; + break; + + default: + break; + } + + switch (pb_config.hdr()) { + case pb::Configuration_Hdr_HDR_HIGHDR: + out_config->colorMode = + (out_config->colorMode & ~ConfigDescription::MASK_HDR) | ConfigDescription::HDR_YES; + break; + + case pb::Configuration_Hdr_HDR_LOWDR: + out_config->colorMode = + (out_config->colorMode & ~ConfigDescription::MASK_HDR) | ConfigDescription::HDR_NO; + break; + + default: + break; + } + + switch (pb_config.orientation()) { + case pb::Configuration_Orientation_ORIENTATION_PORT: + out_config->orientation = ConfigDescription::ORIENTATION_PORT; + break; + + case pb::Configuration_Orientation_ORIENTATION_LAND: + out_config->orientation = ConfigDescription::ORIENTATION_LAND; + break; + + case pb::Configuration_Orientation_ORIENTATION_SQUARE: + out_config->orientation = ConfigDescription::ORIENTATION_SQUARE; + break; + + default: + break; + } + + switch (pb_config.ui_mode_type()) { + case pb::Configuration_UiModeType_UI_MODE_TYPE_NORMAL: + out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_TYPE) | + ConfigDescription::UI_MODE_TYPE_NORMAL; + break; + + case pb::Configuration_UiModeType_UI_MODE_TYPE_DESK: + out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_TYPE) | + ConfigDescription::UI_MODE_TYPE_DESK; + break; + + case pb::Configuration_UiModeType_UI_MODE_TYPE_CAR: + out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_TYPE) | + ConfigDescription::UI_MODE_TYPE_CAR; + break; + + case pb::Configuration_UiModeType_UI_MODE_TYPE_TELEVISION: + out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_TYPE) | + ConfigDescription::UI_MODE_TYPE_TELEVISION; + break; + + case pb::Configuration_UiModeType_UI_MODE_TYPE_APPLIANCE: + out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_TYPE) | + ConfigDescription::UI_MODE_TYPE_APPLIANCE; + break; + + case pb::Configuration_UiModeType_UI_MODE_TYPE_WATCH: + out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_TYPE) | + ConfigDescription::UI_MODE_TYPE_WATCH; + break; + + case pb::Configuration_UiModeType_UI_MODE_TYPE_VRHEADSET: + out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_TYPE) | + ConfigDescription::UI_MODE_TYPE_VR_HEADSET; + break; + + default: + break; + } + + switch (pb_config.ui_mode_night()) { + case pb::Configuration_UiModeNight_UI_MODE_NIGHT_NIGHT: + out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_NIGHT) | + ConfigDescription::UI_MODE_NIGHT_YES; + break; + + case pb::Configuration_UiModeNight_UI_MODE_NIGHT_NOTNIGHT: + out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_NIGHT) | + ConfigDescription::UI_MODE_NIGHT_NO; + break; + + default: + break; + } + + out_config->density = static_cast<uint16_t>(pb_config.density()); + + switch (pb_config.touchscreen()) { + case pb::Configuration_Touchscreen_TOUCHSCREEN_NOTOUCH: + out_config->touchscreen = ConfigDescription::TOUCHSCREEN_NOTOUCH; + break; + + case pb::Configuration_Touchscreen_TOUCHSCREEN_STYLUS: + out_config->touchscreen = ConfigDescription::TOUCHSCREEN_STYLUS; + break; + + case pb::Configuration_Touchscreen_TOUCHSCREEN_FINGER: + out_config->touchscreen = ConfigDescription::TOUCHSCREEN_FINGER; + break; + + default: + break; + } + + switch (pb_config.keys_hidden()) { + case pb::Configuration_KeysHidden_KEYS_HIDDEN_KEYSEXPOSED: + out_config->inputFlags = (out_config->inputFlags & ~ConfigDescription::MASK_KEYSHIDDEN) | + ConfigDescription::KEYSHIDDEN_NO; + break; + + case pb::Configuration_KeysHidden_KEYS_HIDDEN_KEYSHIDDEN: + out_config->inputFlags = (out_config->inputFlags & ~ConfigDescription::MASK_KEYSHIDDEN) | + ConfigDescription::KEYSHIDDEN_YES; + break; + + case pb::Configuration_KeysHidden_KEYS_HIDDEN_KEYSSOFT: + out_config->inputFlags = (out_config->inputFlags & ~ConfigDescription::MASK_KEYSHIDDEN) | + ConfigDescription::KEYSHIDDEN_SOFT; + break; + + default: + break; + } + + switch (pb_config.keyboard()) { + case pb::Configuration_Keyboard_KEYBOARD_NOKEYS: + out_config->keyboard = ConfigDescription::KEYBOARD_NOKEYS; + break; + + case pb::Configuration_Keyboard_KEYBOARD_QWERTY: + out_config->keyboard = ConfigDescription::KEYBOARD_QWERTY; + break; + + case pb::Configuration_Keyboard_KEYBOARD_TWELVEKEY: + out_config->keyboard = ConfigDescription::KEYBOARD_12KEY; + break; + + default: + break; + } + + switch (pb_config.nav_hidden()) { + case pb::Configuration_NavHidden_NAV_HIDDEN_NAVEXPOSED: + out_config->inputFlags = (out_config->inputFlags & ~ConfigDescription::MASK_NAVHIDDEN) | + ConfigDescription::NAVHIDDEN_NO; + break; + + case pb::Configuration_NavHidden_NAV_HIDDEN_NAVHIDDEN: + out_config->inputFlags = (out_config->inputFlags & ~ConfigDescription::MASK_NAVHIDDEN) | + ConfigDescription::NAVHIDDEN_YES; + break; + + default: + break; + } + + switch (pb_config.navigation()) { + case pb::Configuration_Navigation_NAVIGATION_NONAV: + out_config->navigation = ConfigDescription::NAVIGATION_NONAV; + break; + + case pb::Configuration_Navigation_NAVIGATION_DPAD: + out_config->navigation = ConfigDescription::NAVIGATION_DPAD; + break; + + case pb::Configuration_Navigation_NAVIGATION_TRACKBALL: + out_config->navigation = ConfigDescription::NAVIGATION_TRACKBALL; + break; + + case pb::Configuration_Navigation_NAVIGATION_WHEEL: + out_config->navigation = ConfigDescription::NAVIGATION_WHEEL; + break; + + default: + break; + } + + out_config->screenWidth = static_cast<uint16_t>(pb_config.screen_width()); + out_config->screenHeight = static_cast<uint16_t>(pb_config.screen_height()); + out_config->sdkVersion = static_cast<uint16_t>(pb_config.sdk_version()); + return true; +} + +static void DeserializeSourceFromPb(const pb::Source& pb_source, const ResStringPool& src_pool, + Source* out_source) { + out_source->path = util::GetString(src_pool, pb_source.path_idx()); + out_source->line = static_cast<size_t>(pb_source.position().line_number()); +} + +static SymbolState DeserializeVisibilityFromPb(const pb::SymbolStatus_Visibility& pb_visibility) { + switch (pb_visibility) { + case pb::SymbolStatus_Visibility_PRIVATE: + return SymbolState::kPrivate; + case pb::SymbolStatus_Visibility_PUBLIC: + return SymbolState::kPublic; + default: + break; + } + return SymbolState::kUndefined; +} + +static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStringPool& src_pool, + io::IFileCollection* files, ResourceTable* out_table, + std::string* out_error) { + Maybe<uint8_t> id; + if (pb_package.has_package_id()) { + id = static_cast<uint8_t>(pb_package.package_id().id()); + } + + std::map<ResourceId, ResourceNameRef> id_index; + + ResourceTablePackage* pkg = out_table->CreatePackage(pb_package.package_name(), id); + for (const pb::Type& pb_type : pb_package.type()) { + const ResourceType* res_type = ParseResourceType(pb_type.name()); + if (res_type == nullptr) { + std::ostringstream error; + error << "unknown type '" << pb_type.name() << "'"; + *out_error = error.str(); + return false; + } + + ResourceTableType* type = pkg->FindOrCreateType(*res_type); + if (pb_type.has_type_id()) { + type->id = static_cast<uint8_t>(pb_type.type_id().id()); + } + + for (const pb::Entry& pb_entry : pb_type.entry()) { + ResourceEntry* entry = type->FindOrCreateEntry(pb_entry.name()); + if (pb_entry.has_entry_id()) { + entry->id = static_cast<uint16_t>(pb_entry.entry_id().id()); + } + + // Deserialize the symbol status (public/private with source and comments). + if (pb_entry.has_symbol_status()) { + const pb::SymbolStatus& pb_status = pb_entry.symbol_status(); + if (pb_status.has_source()) { + DeserializeSourceFromPb(pb_status.source(), src_pool, &entry->symbol_status.source); + } + + entry->symbol_status.comment = pb_status.comment(); + entry->symbol_status.allow_new = pb_status.allow_new(); + + const SymbolState visibility = DeserializeVisibilityFromPb(pb_status.visibility()); + entry->symbol_status.state = visibility; + if (visibility == SymbolState::kPublic) { + // Propagate the public visibility up to the Type. + type->symbol_status.state = SymbolState::kPublic; + } else if (visibility == SymbolState::kPrivate) { + // Only propagate if no previous state was assigned. + if (type->symbol_status.state == SymbolState::kUndefined) { + type->symbol_status.state = SymbolState::kPrivate; + } + } + } + + ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(), + pb_entry.entry_id().id()); + if (resid.is_valid()) { + id_index[resid] = ResourceNameRef(pkg->name, type->type, entry->name); + } + + for (const pb::ConfigValue& pb_config_value : pb_entry.config_value()) { + const pb::Configuration& pb_config = pb_config_value.config(); + + ConfigDescription config; + if (!DeserializeConfigFromPb(pb_config, &config, out_error)) { + return false; + } + + ResourceConfigValue* config_value = entry->FindOrCreateValue(config, pb_config.product()); + if (config_value->value != nullptr) { + *out_error = "duplicate configuration in resource table"; + return false; + } + + config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config, + &out_table->string_pool, files, out_error); + if (config_value->value == nullptr) { + return false; + } + } + } + } + + ReferenceIdToNameVisitor visitor(&id_index); + VisitAllValuesInPackage(pkg, &visitor); + return true; +} + +bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, io::IFileCollection* files, + ResourceTable* out_table, std::string* out_error) { + // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which + // causes errors when qualifying it with android:: + using namespace android; + + ResStringPool source_pool; + if (pb_table.has_source_pool()) { + status_t result = source_pool.setTo(pb_table.source_pool().data().data(), + pb_table.source_pool().data().size()); + if (result != NO_ERROR) { + *out_error = "invalid source pool"; + return false; + } + } + + for (const pb::Package& pb_package : pb_table.package()) { + if (!DeserializePackageFromPb(pb_package, source_pool, files, out_table, out_error)) { + return false; + } + } + return true; +} + +static ResourceFile::Type DeserializeFileReferenceTypeFromPb(const pb::FileReference::Type& type) { + switch (type) { + case pb::FileReference::BINARY_XML: + return ResourceFile::Type::kBinaryXml; + case pb::FileReference::PROTO_XML: + return ResourceFile::Type::kProtoXml; + case pb::FileReference::PNG: + return ResourceFile::Type::kPng; + default: + return ResourceFile::Type::kUnknown; + } +} + +bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file, + ResourceFile* out_file, std::string* out_error) { + ResourceNameRef name_ref; + if (!ResourceUtils::ParseResourceName(pb_file.resource_name(), &name_ref)) { + std::ostringstream error; + error << "invalid resource name in compiled file header: " << pb_file.resource_name(); + *out_error = error.str(); + return false; + } + + out_file->name = name_ref.ToResourceName(); + out_file->source.path = pb_file.source_path(); + out_file->type = DeserializeFileReferenceTypeFromPb(pb_file.type()); + + std::string config_error; + if (!DeserializeConfigFromPb(pb_file.config(), &out_file->config, &config_error)) { + std::ostringstream error; + error << "invalid resource configuration in compiled file header: " << config_error; + *out_error = error.str(); + return false; + } + + for (const pb::internal::CompiledFile_Symbol& pb_symbol : pb_file.exported_symbol()) { + if (!ResourceUtils::ParseResourceName(pb_symbol.resource_name(), &name_ref)) { + std::ostringstream error; + error << "invalid resource name for exported symbol in compiled file header: " + << pb_file.resource_name(); + *out_error = error.str(); + return false; + } + + size_t line = 0u; + if (pb_symbol.has_source()) { + line = pb_symbol.source().line_number(); + } + out_file->exported_symbols.push_back(SourcedResourceName{name_ref.ToResourceName(), line}); + } + return true; +} + +static Reference::Type DeserializeReferenceTypeFromPb(const pb::Reference_Type& pb_type) { + switch (pb_type) { + case pb::Reference_Type_REFERENCE: + return Reference::Type::kResource; + case pb::Reference_Type_ATTRIBUTE: + return Reference::Type::kAttribute; + default: + break; + } + return Reference::Type::kResource; +} + +static bool DeserializeReferenceFromPb(const pb::Reference& pb_ref, Reference* out_ref, + std::string* out_error) { + out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type()); + out_ref->private_reference = pb_ref.private_(); + + if (pb_ref.id() != 0) { + out_ref->id = ResourceId(pb_ref.id()); + } + + if (!pb_ref.name().empty()) { + ResourceNameRef name_ref; + if (!ResourceUtils::ParseResourceName(pb_ref.name(), &name_ref, nullptr)) { + std::ostringstream error; + error << "reference has invalid resource name '" << pb_ref.name() << "'"; + *out_error = error.str(); + return false; + } + out_ref->name = name_ref.ToResourceName(); + } + return true; +} + +template <typename T> +static void DeserializeItemMetaDataFromPb(const T& pb_item, const android::ResStringPool& src_pool, + Value* out_value) { + if (pb_item.has_source()) { + Source source; + DeserializeSourceFromPb(pb_item.source(), src_pool, &source); + out_value->SetSource(std::move(source)); + } + out_value->SetComment(pb_item.comment()); +} + +static size_t DeserializePluralEnumFromPb(const pb::Plural_Arity& arity) { + switch (arity) { + case pb::Plural_Arity_ZERO: + return Plural::Zero; + case pb::Plural_Arity_ONE: + return Plural::One; + case pb::Plural_Arity_TWO: + return Plural::Two; + case pb::Plural_Arity_FEW: + return Plural::Few; + case pb::Plural_Arity_MANY: + return Plural::Many; + default: + break; + } + return Plural::Other; +} + +std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value, + const android::ResStringPool& src_pool, + const ConfigDescription& config, + StringPool* value_pool, io::IFileCollection* files, + std::string* out_error) { + std::unique_ptr<Value> value; + if (pb_value.has_item()) { + value = DeserializeItemFromPb(pb_value.item(), src_pool, config, value_pool, files, out_error); + if (value == nullptr) { + return {}; + } + + } else if (pb_value.has_compound_value()) { + const pb::CompoundValue& pb_compound_value = pb_value.compound_value(); + switch (pb_compound_value.value_case()) { + case pb::CompoundValue::kAttr: { + const pb::Attribute& pb_attr = pb_compound_value.attr(); + std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(); + attr->type_mask = pb_attr.format_flags(); + attr->min_int = pb_attr.min_int(); + attr->max_int = pb_attr.max_int(); + for (const pb::Attribute_Symbol& pb_symbol : pb_attr.symbol()) { + Attribute::Symbol symbol; + DeserializeItemMetaDataFromPb(pb_symbol, src_pool, &symbol.symbol); + if (!DeserializeReferenceFromPb(pb_symbol.name(), &symbol.symbol, out_error)) { + return {}; + } + symbol.value = pb_symbol.value(); + attr->symbols.push_back(std::move(symbol)); + } + value = std::move(attr); + } break; + + case pb::CompoundValue::kStyle: { + const pb::Style& pb_style = pb_compound_value.style(); + std::unique_ptr<Style> style = util::make_unique<Style>(); + if (pb_style.has_parent()) { + style->parent = Reference(); + if (!DeserializeReferenceFromPb(pb_style.parent(), &style->parent.value(), out_error)) { + return {}; + } + + if (pb_style.has_parent_source()) { + Source parent_source; + DeserializeSourceFromPb(pb_style.parent_source(), src_pool, &parent_source); + style->parent.value().SetSource(std::move(parent_source)); + } + } + + for (const pb::Style_Entry& pb_entry : pb_style.entry()) { + Style::Entry entry; + if (!DeserializeReferenceFromPb(pb_entry.key(), &entry.key, out_error)) { + return {}; + } + DeserializeItemMetaDataFromPb(pb_entry, src_pool, &entry.key); + entry.value = DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, files, + out_error); + if (entry.value == nullptr) { + return {}; + } + + // Copy the meta-data into the value as well. + DeserializeItemMetaDataFromPb(pb_entry, src_pool, entry.value.get()); + style->entries.push_back(std::move(entry)); + } + value = std::move(style); + } break; + + case pb::CompoundValue::kStyleable: { + const pb::Styleable& pb_styleable = pb_compound_value.styleable(); + std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); + for (const pb::Styleable_Entry& pb_entry : pb_styleable.entry()) { + Reference attr_ref; + DeserializeItemMetaDataFromPb(pb_entry, src_pool, &attr_ref); + DeserializeReferenceFromPb(pb_entry.attr(), &attr_ref, out_error); + styleable->entries.push_back(std::move(attr_ref)); + } + value = std::move(styleable); + } break; + + case pb::CompoundValue::kArray: { + const pb::Array& pb_array = pb_compound_value.array(); + std::unique_ptr<Array> array = util::make_unique<Array>(); + for (const pb::Array_Element& pb_entry : pb_array.element()) { + std::unique_ptr<Item> item = DeserializeItemFromPb(pb_entry.item(), src_pool, config, + value_pool, files, out_error); + if (item == nullptr) { + return {}; + } + + DeserializeItemMetaDataFromPb(pb_entry, src_pool, item.get()); + array->elements.push_back(std::move(item)); + } + value = std::move(array); + } break; + + case pb::CompoundValue::kPlural: { + const pb::Plural& pb_plural = pb_compound_value.plural(); + std::unique_ptr<Plural> plural = util::make_unique<Plural>(); + for (const pb::Plural_Entry& pb_entry : pb_plural.entry()) { + size_t plural_idx = DeserializePluralEnumFromPb(pb_entry.arity()); + plural->values[plural_idx] = DeserializeItemFromPb(pb_entry.item(), src_pool, config, + value_pool, files, out_error); + if (!plural->values[plural_idx]) { + return {}; + } + + DeserializeItemMetaDataFromPb(pb_entry, src_pool, plural->values[plural_idx].get()); + } + value = std::move(plural); + } break; + + default: + LOG(FATAL) << "unknown compound value: " << (int)pb_compound_value.value_case(); + break; + } + } else { + LOG(FATAL) << "unknown value: " << (int)pb_value.value_case(); + return {}; + } + + CHECK(value) << "forgot to set value"; + + value->SetWeak(pb_value.weak()); + DeserializeItemMetaDataFromPb(pb_value, src_pool, value.get()); + return value; +} + +std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, + const android::ResStringPool& src_pool, + const ConfigDescription& config, StringPool* value_pool, + io::IFileCollection* files, std::string* out_error) { + switch (pb_item.value_case()) { + case pb::Item::kRef: { + const pb::Reference& pb_ref = pb_item.ref(); + std::unique_ptr<Reference> ref = util::make_unique<Reference>(); + if (!DeserializeReferenceFromPb(pb_ref, ref.get(), out_error)) { + return {}; + } + return std::move(ref); + } break; + + case pb::Item::kPrim: { + const pb::Primitive& pb_prim = pb_item.prim(); + return util::make_unique<BinaryPrimitive>(static_cast<uint8_t>(pb_prim.type()), + pb_prim.data()); + } break; + + case pb::Item::kId: { + return util::make_unique<Id>(); + } break; + + case pb::Item::kStr: { + return util::make_unique<String>( + value_pool->MakeRef(pb_item.str().value(), StringPool::Context(config))); + } break; + + case pb::Item::kRawStr: { + return util::make_unique<RawString>( + value_pool->MakeRef(pb_item.raw_str().value(), StringPool::Context(config))); + } break; + + case pb::Item::kStyledStr: { + const pb::StyledString& pb_str = pb_item.styled_str(); + StyleString style_str{pb_str.value()}; + for (const pb::StyledString::Span& pb_span : pb_str.span()) { + style_str.spans.push_back(Span{pb_span.tag(), pb_span.first_char(), pb_span.last_char()}); + } + return util::make_unique<StyledString>(value_pool->MakeRef( + style_str, StringPool::Context(StringPool::Context::kNormalPriority, config))); + } break; + + case pb::Item::kFile: { + const pb::FileReference& pb_file = pb_item.file(); + std::unique_ptr<FileReference> file_ref = + util::make_unique<FileReference>(value_pool->MakeRef( + pb_file.path(), StringPool::Context(StringPool::Context::kHighPriority, config))); + file_ref->type = DeserializeFileReferenceTypeFromPb(pb_file.type()); + if (files != nullptr) { + file_ref->file = files->FindFile(*file_ref->path); + } + return std::move(file_ref); + } break; + + default: + LOG(FATAL) << "unknown item: " << (int)pb_item.value_case(); + break; + } + return {}; +} + +std::unique_ptr<xml::XmlResource> DeserializeXmlResourceFromPb(const pb::XmlNode& pb_node, + std::string* out_error) { + if (!pb_node.has_element()) { + return {}; + } + + std::unique_ptr<xml::XmlResource> resource = util::make_unique<xml::XmlResource>(); + resource->root = util::make_unique<xml::Element>(); + if (!DeserializeXmlFromPb(pb_node, resource->root.get(), &resource->string_pool, out_error)) { + return {}; + } + return resource; +} + +bool DeserializeXmlFromPb(const pb::XmlNode& pb_node, xml::Element* out_el, StringPool* value_pool, + std::string* out_error) { + const pb::XmlElement& pb_el = pb_node.element(); + out_el->name = pb_el.name(); + out_el->namespace_uri = pb_el.namespace_uri(); + out_el->line_number = pb_node.source().line_number(); + out_el->column_number = pb_node.source().column_number(); + + for (const pb::XmlNamespace& pb_ns : pb_el.namespace_declaration()) { + xml::NamespaceDecl decl; + decl.uri = pb_ns.uri(); + decl.prefix = pb_ns.prefix(); + decl.line_number = pb_ns.source().line_number(); + decl.column_number = pb_ns.source().column_number(); + out_el->namespace_decls.push_back(std::move(decl)); + } + + for (const pb::XmlAttribute& pb_attr : pb_el.attribute()) { + xml::Attribute attr; + attr.name = pb_attr.name(); + attr.namespace_uri = pb_attr.namespace_uri(); + attr.value = pb_attr.value(); + if (pb_attr.resource_id() != 0u) { + attr.compiled_attribute = xml::AaptAttribute{Attribute(), ResourceId(pb_attr.resource_id())}; + } + if (pb_attr.has_compiled_item()) { + attr.compiled_value = + DeserializeItemFromPb(pb_attr.compiled_item(), {}, {}, value_pool, nullptr, out_error); + if (attr.compiled_value == nullptr) { + return {}; + } + attr.compiled_value->SetSource(Source().WithLine(pb_attr.source().line_number())); + } + out_el->attributes.push_back(std::move(attr)); + } + + // Deserialize the children. + for (const pb::XmlNode& pb_child : pb_el.child()) { + switch (pb_child.node_case()) { + case pb::XmlNode::NodeCase::kText: { + std::unique_ptr<xml::Text> text = util::make_unique<xml::Text>(); + text->line_number = pb_child.source().line_number(); + text->column_number = pb_child.source().column_number(); + text->text = pb_child.text(); + out_el->AppendChild(std::move(text)); + } break; + + case pb::XmlNode::NodeCase::kElement: { + std::unique_ptr<xml::Element> child_el = util::make_unique<xml::Element>(); + if (!DeserializeXmlFromPb(pb_child, child_el.get(), value_pool, out_error)) { + return false; + } + out_el->AppendChild(std::move(child_el)); + } break; + + default: + LOG(FATAL) << "unknown XmlNode " << (int)pb_child.node_case(); + break; + } + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/format/proto/ProtoDeserialize.h b/tools/aapt2/format/proto/ProtoDeserialize.h new file mode 100644 index 000000000000..0c581a17fa2e --- /dev/null +++ b/tools/aapt2/format/proto/ProtoDeserialize.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 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 AAPT_FORMAT_PROTO_PROTODESERIALIZE_H +#define AAPT_FORMAT_PROTO_PROTODESERIALIZE_H + +#include "android-base/macros.h" +#include "androidfw/ResourceTypes.h" + +#include "ConfigDescription.h" +#include "Configuration.pb.h" +#include "ResourceTable.h" +#include "ResourceValues.h" +#include "Resources.pb.h" +#include "ResourcesInternal.pb.h" +#include "StringPool.h" +#include "io/File.h" +#include "xml/XmlDom.h" + +namespace aapt { + +std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value, + const android::ResStringPool& src_pool, + const ConfigDescription& config, + StringPool* value_pool, io::IFileCollection* files, + std::string* out_error); + +std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, + const android::ResStringPool& src_pool, + const ConfigDescription& config, StringPool* value_pool, + io::IFileCollection* files, std::string* out_error); + +std::unique_ptr<xml::XmlResource> DeserializeXmlResourceFromPb(const pb::XmlNode& pb_node, + std::string* out_error); + +bool DeserializeXmlFromPb(const pb::XmlNode& pb_node, xml::Element* out_el, StringPool* value_pool, + std::string* out_error); + +bool DeserializeConfigFromPb(const pb::Configuration& pb_config, ConfigDescription* out_config, + std::string* out_error); + +// Optional io::IFileCollection used to lookup references to files in the ResourceTable. +bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, io::IFileCollection* files, + ResourceTable* out_table, std::string* out_error); + +bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file, + ResourceFile* out_file, std::string* out_error); + +} // namespace aapt + +#endif /* AAPT_FORMAT_PROTO_PROTODESERIALIZE_H */ diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp new file mode 100644 index 000000000000..97ce01a9de13 --- /dev/null +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -0,0 +1,593 @@ +/* + * Copyright (C) 2016 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 "format/proto/ProtoSerialize.h" + +#include "ValueVisitor.h" +#include "util/BigBuffer.h" + +namespace aapt { + +void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool) { + BigBuffer buffer(1024); + StringPool::FlattenUtf8(&buffer, pool); + + std::string* data = out_pb_pool->mutable_data(); + data->reserve(buffer.size()); + + size_t offset = 0; + for (const BigBuffer::Block& block : buffer) { + data->insert(data->begin() + offset, block.buffer.get(), block.buffer.get() + block.size); + offset += block.size; + } +} + +void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source* out_pb_source) { + StringPool::Ref ref = src_pool->MakeRef(source.path); + out_pb_source->set_path_idx(static_cast<uint32_t>(ref.index())); + if (source.line) { + out_pb_source->mutable_position()->set_line_number(static_cast<uint32_t>(source.line.value())); + } +} + +static pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state) { + switch (state) { + case SymbolState::kPrivate: + return pb::SymbolStatus_Visibility_PRIVATE; + case SymbolState::kPublic: + return pb::SymbolStatus_Visibility_PUBLIC; + default: + break; + } + return pb::SymbolStatus_Visibility_UNKNOWN; +} + +void SerializeConfig(const ConfigDescription& config, pb::Configuration* out_pb_config) { + out_pb_config->set_mcc(config.mcc); + out_pb_config->set_mnc(config.mnc); + out_pb_config->set_locale(config.GetBcp47LanguageTag()); + + switch (config.screenLayout & ConfigDescription::MASK_LAYOUTDIR) { + case ConfigDescription::LAYOUTDIR_LTR: + out_pb_config->set_layout_direction(pb::Configuration_LayoutDirection_LAYOUT_DIRECTION_LTR); + break; + + case ConfigDescription::LAYOUTDIR_RTL: + out_pb_config->set_layout_direction(pb::Configuration_LayoutDirection_LAYOUT_DIRECTION_RTL); + break; + } + + out_pb_config->set_screen_width(config.screenWidth); + out_pb_config->set_screen_height(config.screenHeight); + out_pb_config->set_screen_width_dp(config.screenWidthDp); + out_pb_config->set_screen_height_dp(config.screenHeightDp); + out_pb_config->set_smallest_screen_width_dp(config.smallestScreenWidthDp); + + switch (config.screenLayout & ConfigDescription::MASK_SCREENSIZE) { + case ConfigDescription::SCREENSIZE_SMALL: + out_pb_config->set_screen_layout_size( + pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_SMALL); + break; + + case ConfigDescription::SCREENSIZE_NORMAL: + out_pb_config->set_screen_layout_size( + pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_NORMAL); + break; + + case ConfigDescription::SCREENSIZE_LARGE: + out_pb_config->set_screen_layout_size( + pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_LARGE); + break; + + case ConfigDescription::SCREENSIZE_XLARGE: + out_pb_config->set_screen_layout_size( + pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_XLARGE); + break; + } + + switch (config.screenLayout & ConfigDescription::MASK_SCREENLONG) { + case ConfigDescription::SCREENLONG_YES: + out_pb_config->set_screen_layout_long( + pb::Configuration_ScreenLayoutLong_SCREEN_LAYOUT_LONG_LONG); + break; + + case ConfigDescription::SCREENLONG_NO: + out_pb_config->set_screen_layout_long( + pb::Configuration_ScreenLayoutLong_SCREEN_LAYOUT_LONG_NOTLONG); + break; + } + + switch (config.screenLayout2 & ConfigDescription::MASK_SCREENROUND) { + case ConfigDescription::SCREENROUND_YES: + out_pb_config->set_screen_round(pb::Configuration_ScreenRound_SCREEN_ROUND_ROUND); + break; + + case ConfigDescription::SCREENROUND_NO: + out_pb_config->set_screen_round(pb::Configuration_ScreenRound_SCREEN_ROUND_NOTROUND); + break; + } + + switch (config.colorMode & ConfigDescription::MASK_WIDE_COLOR_GAMUT) { + case ConfigDescription::WIDE_COLOR_GAMUT_YES: + out_pb_config->set_wide_color_gamut(pb::Configuration_WideColorGamut_WIDE_COLOR_GAMUT_WIDECG); + break; + + case ConfigDescription::WIDE_COLOR_GAMUT_NO: + out_pb_config->set_wide_color_gamut( + pb::Configuration_WideColorGamut_WIDE_COLOR_GAMUT_NOWIDECG); + break; + } + + switch (config.colorMode & ConfigDescription::MASK_HDR) { + case ConfigDescription::HDR_YES: + out_pb_config->set_hdr(pb::Configuration_Hdr_HDR_HIGHDR); + break; + + case ConfigDescription::HDR_NO: + out_pb_config->set_hdr(pb::Configuration_Hdr_HDR_LOWDR); + break; + } + + switch (config.orientation) { + case ConfigDescription::ORIENTATION_PORT: + out_pb_config->set_orientation(pb::Configuration_Orientation_ORIENTATION_PORT); + break; + + case ConfigDescription::ORIENTATION_LAND: + out_pb_config->set_orientation(pb::Configuration_Orientation_ORIENTATION_LAND); + break; + + case ConfigDescription::ORIENTATION_SQUARE: + out_pb_config->set_orientation(pb::Configuration_Orientation_ORIENTATION_SQUARE); + break; + } + + switch (config.uiMode & ConfigDescription::MASK_UI_MODE_TYPE) { + case ConfigDescription::UI_MODE_TYPE_NORMAL: + out_pb_config->set_ui_mode_type(pb::Configuration_UiModeType_UI_MODE_TYPE_NORMAL); + break; + + case ConfigDescription::UI_MODE_TYPE_DESK: + out_pb_config->set_ui_mode_type(pb::Configuration_UiModeType_UI_MODE_TYPE_DESK); + break; + + case ConfigDescription::UI_MODE_TYPE_CAR: + out_pb_config->set_ui_mode_type(pb::Configuration_UiModeType_UI_MODE_TYPE_CAR); + break; + + case ConfigDescription::UI_MODE_TYPE_TELEVISION: + out_pb_config->set_ui_mode_type(pb::Configuration_UiModeType_UI_MODE_TYPE_TELEVISION); + break; + + case ConfigDescription::UI_MODE_TYPE_APPLIANCE: + out_pb_config->set_ui_mode_type(pb::Configuration_UiModeType_UI_MODE_TYPE_APPLIANCE); + break; + + case ConfigDescription::UI_MODE_TYPE_WATCH: + out_pb_config->set_ui_mode_type(pb::Configuration_UiModeType_UI_MODE_TYPE_WATCH); + break; + + case ConfigDescription::UI_MODE_TYPE_VR_HEADSET: + out_pb_config->set_ui_mode_type(pb::Configuration_UiModeType_UI_MODE_TYPE_VRHEADSET); + break; + } + + switch (config.uiMode & ConfigDescription::MASK_UI_MODE_NIGHT) { + case ConfigDescription::UI_MODE_NIGHT_YES: + out_pb_config->set_ui_mode_night(pb::Configuration_UiModeNight_UI_MODE_NIGHT_NIGHT); + break; + + case ConfigDescription::UI_MODE_NIGHT_NO: + out_pb_config->set_ui_mode_night(pb::Configuration_UiModeNight_UI_MODE_NIGHT_NOTNIGHT); + break; + } + + out_pb_config->set_density(config.density); + + switch (config.touchscreen) { + case ConfigDescription::TOUCHSCREEN_NOTOUCH: + out_pb_config->set_touchscreen(pb::Configuration_Touchscreen_TOUCHSCREEN_NOTOUCH); + break; + + case ConfigDescription::TOUCHSCREEN_STYLUS: + out_pb_config->set_touchscreen(pb::Configuration_Touchscreen_TOUCHSCREEN_STYLUS); + break; + + case ConfigDescription::TOUCHSCREEN_FINGER: + out_pb_config->set_touchscreen(pb::Configuration_Touchscreen_TOUCHSCREEN_FINGER); + break; + } + + switch (config.inputFlags & ConfigDescription::MASK_KEYSHIDDEN) { + case ConfigDescription::KEYSHIDDEN_NO: + out_pb_config->set_keys_hidden(pb::Configuration_KeysHidden_KEYS_HIDDEN_KEYSEXPOSED); + break; + + case ConfigDescription::KEYSHIDDEN_YES: + out_pb_config->set_keys_hidden(pb::Configuration_KeysHidden_KEYS_HIDDEN_KEYSHIDDEN); + break; + + case ConfigDescription::KEYSHIDDEN_SOFT: + out_pb_config->set_keys_hidden(pb::Configuration_KeysHidden_KEYS_HIDDEN_KEYSSOFT); + break; + } + + switch (config.keyboard) { + case ConfigDescription::KEYBOARD_NOKEYS: + out_pb_config->set_keyboard(pb::Configuration_Keyboard_KEYBOARD_NOKEYS); + break; + + case ConfigDescription::KEYBOARD_QWERTY: + out_pb_config->set_keyboard(pb::Configuration_Keyboard_KEYBOARD_QWERTY); + break; + + case ConfigDescription::KEYBOARD_12KEY: + out_pb_config->set_keyboard(pb::Configuration_Keyboard_KEYBOARD_TWELVEKEY); + break; + } + + switch (config.inputFlags & ConfigDescription::MASK_NAVHIDDEN) { + case ConfigDescription::NAVHIDDEN_NO: + out_pb_config->set_nav_hidden(pb::Configuration_NavHidden_NAV_HIDDEN_NAVEXPOSED); + break; + + case ConfigDescription::NAVHIDDEN_YES: + out_pb_config->set_nav_hidden(pb::Configuration_NavHidden_NAV_HIDDEN_NAVHIDDEN); + break; + } + + switch (config.navigation) { + case ConfigDescription::NAVIGATION_NONAV: + out_pb_config->set_navigation(pb::Configuration_Navigation_NAVIGATION_NONAV); + break; + + case ConfigDescription::NAVIGATION_DPAD: + out_pb_config->set_navigation(pb::Configuration_Navigation_NAVIGATION_DPAD); + break; + + case ConfigDescription::NAVIGATION_TRACKBALL: + out_pb_config->set_navigation(pb::Configuration_Navigation_NAVIGATION_TRACKBALL); + break; + + case ConfigDescription::NAVIGATION_WHEEL: + out_pb_config->set_navigation(pb::Configuration_Navigation_NAVIGATION_WHEEL); + break; + } + + out_pb_config->set_sdk_version(config.sdkVersion); +} + +void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table) { + StringPool source_pool; + for (const std::unique_ptr<ResourceTablePackage>& package : table.packages) { + pb::Package* pb_package = out_table->add_package(); + if (package->id) { + pb_package->mutable_package_id()->set_id(package->id.value()); + } + pb_package->set_package_name(package->name); + + for (const std::unique_ptr<ResourceTableType>& type : package->types) { + pb::Type* pb_type = pb_package->add_type(); + if (type->id) { + pb_type->mutable_type_id()->set_id(type->id.value()); + } + pb_type->set_name(to_string(type->type).to_string()); + + for (const std::unique_ptr<ResourceEntry>& entry : type->entries) { + pb::Entry* pb_entry = pb_type->add_entry(); + if (entry->id) { + pb_entry->mutable_entry_id()->set_id(entry->id.value()); + } + pb_entry->set_name(entry->name); + + // Write the SymbolStatus struct. + pb::SymbolStatus* pb_status = pb_entry->mutable_symbol_status(); + pb_status->set_visibility(SerializeVisibilityToPb(entry->symbol_status.state)); + SerializeSourceToPb(entry->symbol_status.source, &source_pool, pb_status->mutable_source()); + pb_status->set_comment(entry->symbol_status.comment); + pb_status->set_allow_new(entry->symbol_status.allow_new); + + for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { + pb::ConfigValue* pb_config_value = pb_entry->add_config_value(); + SerializeConfig(config_value->config, pb_config_value->mutable_config()); + pb_config_value->mutable_config()->set_product(config_value->product); + SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(), &source_pool); + } + } + } + } + SerializeStringPoolToPb(source_pool, out_table->mutable_source_pool()); +} + +static pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type) { + switch (type) { + case Reference::Type::kResource: + return pb::Reference_Type_REFERENCE; + case Reference::Type::kAttribute: + return pb::Reference_Type_ATTRIBUTE; + default: + break; + } + return pb::Reference_Type_REFERENCE; +} + +static void SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref) { + pb_ref->set_id(ref.id.value_or_default(ResourceId(0x0)).id); + + if (ref.name) { + pb_ref->set_name(ref.name.value().to_string()); + } + + pb_ref->set_private_(ref.private_reference); + pb_ref->set_type(SerializeReferenceTypeToPb(ref.reference_type)); +} + +template <typename T> +static void SerializeItemMetaDataToPb(const Item& item, T* pb_item, StringPool* src_pool) { + if (src_pool != nullptr) { + SerializeSourceToPb(item.GetSource(), src_pool, pb_item->mutable_source()); + } + pb_item->set_comment(item.GetComment()); +} + +static pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx) { + switch (plural_idx) { + case Plural::Zero: + return pb::Plural_Arity_ZERO; + case Plural::One: + return pb::Plural_Arity_ONE; + case Plural::Two: + return pb::Plural_Arity_TWO; + case Plural::Few: + return pb::Plural_Arity_FEW; + case Plural::Many: + return pb::Plural_Arity_MANY; + default: + break; + } + return pb::Plural_Arity_OTHER; +} + +static pb::FileReference::Type SerializeFileReferenceTypeToPb(const ResourceFile::Type& type) { + switch (type) { + case ResourceFile::Type::kBinaryXml: + return pb::FileReference::BINARY_XML; + case ResourceFile::Type::kProtoXml: + return pb::FileReference::PROTO_XML; + case ResourceFile::Type::kPng: + return pb::FileReference::PNG; + default: + return pb::FileReference::UNKNOWN; + } +} + +namespace { + +class ValueSerializer : public ConstValueVisitor { + public: + using ConstValueVisitor::Visit; + + ValueSerializer(pb::Value* out_value, StringPool* src_pool) + : out_value_(out_value), src_pool_(src_pool) { + } + + void Visit(const Reference* ref) override { + SerializeReferenceToPb(*ref, out_value_->mutable_item()->mutable_ref()); + } + + void Visit(const String* str) override { + out_value_->mutable_item()->mutable_str()->set_value(*str->value); + } + + void Visit(const RawString* str) override { + out_value_->mutable_item()->mutable_raw_str()->set_value(*str->value); + } + + void Visit(const StyledString* str) override { + pb::StyledString* pb_str = out_value_->mutable_item()->mutable_styled_str(); + pb_str->set_value(str->value->value); + for (const StringPool::Span& span : str->value->spans) { + pb::StyledString::Span* pb_span = pb_str->add_span(); + pb_span->set_tag(*span.name); + pb_span->set_first_char(span.first_char); + pb_span->set_last_char(span.last_char); + } + } + + void Visit(const FileReference* file) override { + pb::FileReference* pb_file = out_value_->mutable_item()->mutable_file(); + pb_file->set_path(*file->path); + pb_file->set_type(SerializeFileReferenceTypeToPb(file->type)); + } + + void Visit(const Id* /*id*/) override { + out_value_->mutable_item()->mutable_id(); + } + + void Visit(const BinaryPrimitive* prim) override { + android::Res_value val = {}; + prim->Flatten(&val); + + pb::Primitive* pb_prim = out_value_->mutable_item()->mutable_prim(); + pb_prim->set_type(val.dataType); + pb_prim->set_data(val.data); + } + + void Visit(const Attribute* attr) override { + pb::Attribute* pb_attr = out_value_->mutable_compound_value()->mutable_attr(); + pb_attr->set_format_flags(attr->type_mask); + pb_attr->set_min_int(attr->min_int); + pb_attr->set_max_int(attr->max_int); + + for (auto& symbol : attr->symbols) { + pb::Attribute_Symbol* pb_symbol = pb_attr->add_symbol(); + SerializeItemMetaDataToPb(symbol.symbol, pb_symbol, src_pool_); + SerializeReferenceToPb(symbol.symbol, pb_symbol->mutable_name()); + pb_symbol->set_value(symbol.value); + } + } + + void Visit(const Style* style) override { + pb::Style* pb_style = out_value_->mutable_compound_value()->mutable_style(); + if (style->parent) { + const Reference& parent = style->parent.value(); + SerializeReferenceToPb(parent, pb_style->mutable_parent()); + if (src_pool_ != nullptr) { + SerializeSourceToPb(parent.GetSource(), src_pool_, pb_style->mutable_parent_source()); + } + } + + for (const Style::Entry& entry : style->entries) { + pb::Style_Entry* pb_entry = pb_style->add_entry(); + SerializeReferenceToPb(entry.key, pb_entry->mutable_key()); + SerializeItemMetaDataToPb(entry.key, pb_entry, src_pool_); + SerializeItemToPb(*entry.value, pb_entry->mutable_item()); + } + } + + void Visit(const Styleable* styleable) override { + pb::Styleable* pb_styleable = out_value_->mutable_compound_value()->mutable_styleable(); + for (const Reference& entry : styleable->entries) { + pb::Styleable_Entry* pb_entry = pb_styleable->add_entry(); + SerializeItemMetaDataToPb(entry, pb_entry, src_pool_); + SerializeReferenceToPb(entry, pb_entry->mutable_attr()); + } + } + + void Visit(const Array* array) override { + pb::Array* pb_array = out_value_->mutable_compound_value()->mutable_array(); + for (const std::unique_ptr<Item>& element : array->elements) { + pb::Array_Element* pb_element = pb_array->add_element(); + SerializeItemMetaDataToPb(*element, pb_element, src_pool_); + SerializeItemToPb(*element, pb_element->mutable_item()); + } + } + + void Visit(const Plural* plural) override { + pb::Plural* pb_plural = out_value_->mutable_compound_value()->mutable_plural(); + const size_t count = plural->values.size(); + for (size_t i = 0; i < count; i++) { + if (!plural->values[i]) { + // No plural value set here. + continue; + } + + pb::Plural_Entry* pb_entry = pb_plural->add_entry(); + pb_entry->set_arity(SerializePluralEnumToPb(i)); + SerializeItemMetaDataToPb(*plural->values[i], pb_entry, src_pool_); + SerializeItemToPb(*plural->values[i], pb_entry->mutable_item()); + } + } + + void VisitAny(const Value* unknown) override { + LOG(FATAL) << "unimplemented value: " << *unknown; + } + + private: + pb::Value* out_value_; + StringPool* src_pool_; +}; + +} // namespace + +void SerializeValueToPb(const Value& value, pb::Value* out_value, StringPool* src_pool) { + ValueSerializer serializer(out_value, src_pool); + value.Accept(&serializer); + + // Serialize the meta-data of the Value. + out_value->set_comment(value.GetComment()); + out_value->set_weak(value.IsWeak()); + if (src_pool != nullptr) { + SerializeSourceToPb(value.GetSource(), src_pool, out_value->mutable_source()); + } +} + +void SerializeItemToPb(const Item& item, pb::Item* out_item) { + pb::Value value; + ValueSerializer serializer(&value, nullptr); + item.Accept(&serializer); + out_item->MergeFrom(value.item()); +} + +void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file) { + out_file->set_resource_name(file.name.to_string()); + out_file->set_source_path(file.source.path); + out_file->set_type(SerializeFileReferenceTypeToPb(file.type)); + SerializeConfig(file.config, out_file->mutable_config()); + + for (const SourcedResourceName& exported : file.exported_symbols) { + pb::internal::CompiledFile_Symbol* pb_symbol = out_file->add_exported_symbol(); + pb_symbol->set_resource_name(exported.name.to_string()); + pb_symbol->mutable_source()->set_line_number(exported.line); + } +} + +static void SerializeXmlCommon(const xml::Node& node, pb::XmlNode* out_node) { + pb::SourcePosition* pb_src = out_node->mutable_source(); + pb_src->set_line_number(node.line_number); + pb_src->set_column_number(node.column_number); +} + +void SerializeXmlToPb(const xml::Element& el, pb::XmlNode* out_node) { + SerializeXmlCommon(el, out_node); + + pb::XmlElement* pb_element = out_node->mutable_element(); + pb_element->set_name(el.name); + pb_element->set_namespace_uri(el.namespace_uri); + + for (const xml::NamespaceDecl& ns : el.namespace_decls) { + pb::XmlNamespace* pb_ns = pb_element->add_namespace_declaration(); + pb_ns->set_prefix(ns.prefix); + pb_ns->set_uri(ns.uri); + pb::SourcePosition* pb_src = pb_ns->mutable_source(); + pb_src->set_line_number(ns.line_number); + pb_src->set_column_number(ns.column_number); + } + + for (const xml::Attribute& attr : el.attributes) { + pb::XmlAttribute* pb_attr = pb_element->add_attribute(); + pb_attr->set_name(attr.name); + pb_attr->set_namespace_uri(attr.namespace_uri); + pb_attr->set_value(attr.value); + if (attr.compiled_attribute) { + const ResourceId attr_id = attr.compiled_attribute.value().id.value_or_default({}); + pb_attr->set_resource_id(attr_id.id); + } + if (attr.compiled_value != nullptr) { + SerializeItemToPb(*attr.compiled_value, pb_attr->mutable_compiled_item()); + pb::SourcePosition* pb_src = pb_attr->mutable_source(); + pb_src->set_line_number(attr.compiled_value->GetSource().line.value_or_default(0)); + } + } + + for (const std::unique_ptr<xml::Node>& child : el.children) { + if (const xml::Element* child_el = xml::NodeCast<xml::Element>(child.get())) { + SerializeXmlToPb(*child_el, pb_element->add_child()); + } else if (const xml::Text* text_el = xml::NodeCast<xml::Text>(child.get())) { + pb::XmlNode* pb_child_node = pb_element->add_child(); + SerializeXmlCommon(*text_el, pb_child_node); + pb_child_node->set_text(text_el->text); + } else { + LOG(FATAL) << "unhandled XmlNode type"; + } + } +} + +void SerializeXmlResourceToPb(const xml::XmlResource& resource, pb::XmlNode* out_node) { + SerializeXmlToPb(*resource.root, out_node); +} + +} // namespace aapt diff --git a/tools/aapt2/format/proto/ProtoSerialize.h b/tools/aapt2/format/proto/ProtoSerialize.h new file mode 100644 index 000000000000..95dd413c1713 --- /dev/null +++ b/tools/aapt2/format/proto/ProtoSerialize.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 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 AAPT_FORMAT_PROTO_PROTOSERIALIZE_H +#define AAPT_FORMAT_PROTO_PROTOSERIALIZE_H + +#include "android-base/macros.h" + +#include "ConfigDescription.h" +#include "Configuration.pb.h" +#include "ResourceTable.h" +#include "ResourceValues.h" +#include "Resources.pb.h" +#include "ResourcesInternal.pb.h" +#include "StringPool.h" +#include "xml/XmlDom.h" + +namespace aapt { + +// Serializes a Value to its protobuf representation. An optional StringPool will hold the +// source path string. +void SerializeValueToPb(const Value& value, pb::Value* out_value, StringPool* src_pool = nullptr); + +// Serialize an Item into its protobuf representation. pb::Item does not store the source path nor +// comments of an Item. +void SerializeItemToPb(const Item& item, pb::Item* out_item); + +// Serializes an XML element into its protobuf representation. +void SerializeXmlToPb(const xml::Element& el, pb::XmlNode* out_node); + +// Serializes an XmlResource into its protobuf representation. The ResourceFile is NOT serialized. +void SerializeXmlResourceToPb(const xml::XmlResource& resource, pb::XmlNode* out_node); + +// Serializes a StringPool into its protobuf representation, which is really just the binary +// ResStringPool representation stuffed into a bytes field. +void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool); + +// Serializes a ConfigDescription into its protobuf representation. +void SerializeConfig(const ConfigDescription& config, pb::Configuration* out_pb_config); + +// Serializes a ResourceTable into its protobuf representation. +void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table); + +// Serializes a ResourceFile into its protobuf representation. +void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file); + +} // namespace aapt + +#endif /* AAPT_FORMAT_PROTO_PROTOSERIALIZE_H */ diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp new file mode 100644 index 000000000000..9649a4de2fe4 --- /dev/null +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2016 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 "format/proto/ProtoSerialize.h" + +#include "ResourceUtils.h" +#include "format/proto/ProtoDeserialize.h" +#include "test/Test.h" + +using ::android::StringPiece; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::NotNull; +using ::testing::SizeIs; +using ::testing::StrEq; + +namespace aapt { + +class MockFileCollection : public io::IFileCollection { + public: + MOCK_METHOD1(FindFile, io::IFile*(const StringPiece& path)); + MOCK_METHOD0(Iterator, std::unique_ptr<io::IFileCollectionIterator>()); +}; + +TEST(ProtoSerializeTest, SerializeSinglePackage) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.a", 0x7f) + .AddFileReference("com.app.a:layout/main", ResourceId(0x7f020000), "res/layout/main.xml") + .AddReference("com.app.a:layout/other", ResourceId(0x7f020001), "com.app.a:layout/main") + .AddString("com.app.a:string/text", {}, "hi") + .AddValue("com.app.a:id/foo", {}, util::make_unique<Id>()) + .SetSymbolState("com.app.a:bool/foo", {}, SymbolState::kUndefined, true /*allow_new*/) + .Build(); + + Symbol public_symbol; + public_symbol.state = SymbolState::kPublic; + ASSERT_TRUE(table->SetSymbolState(test::ParseNameOrDie("com.app.a:layout/main"), + ResourceId(0x7f020000), public_symbol, + context->GetDiagnostics())); + + Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo"); + ASSERT_THAT(id, NotNull()); + + // Make a plural. + std::unique_ptr<Plural> plural = util::make_unique<Plural>(); + plural->values[Plural::One] = util::make_unique<String>(table->string_pool.MakeRef("one")); + ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:plurals/hey"), ConfigDescription{}, + {}, std::move(plural), context->GetDiagnostics())); + + // Make a styled string. + StyleString style_string; + style_string.str = "hello"; + style_string.spans.push_back(Span{"b", 0u, 4u}); + ASSERT_TRUE( + table->AddResource(test::ParseNameOrDie("com.app.a:string/styled"), ConfigDescription{}, {}, + util::make_unique<StyledString>(table->string_pool.MakeRef(style_string)), + context->GetDiagnostics())); + + // Make a resource with different products. + ASSERT_TRUE(table->AddResource( + test::ParseNameOrDie("com.app.a:integer/one"), test::ParseConfigOrDie("land"), {}, + test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 123u), context->GetDiagnostics())); + ASSERT_TRUE(table->AddResource( + test::ParseNameOrDie("com.app.a:integer/one"), test::ParseConfigOrDie("land"), "tablet", + test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 321u), context->GetDiagnostics())); + + // Make a reference with both resource name and resource ID. + // The reference should point to a resource outside of this table to test that both name and id + // get serialized. + Reference expected_ref; + expected_ref.name = test::ParseNameOrDie("android:layout/main"); + expected_ref.id = ResourceId(0x01020000); + ASSERT_TRUE(table->AddResource( + test::ParseNameOrDie("com.app.a:layout/abc"), ConfigDescription::DefaultConfig(), {}, + util::make_unique<Reference>(expected_ref), context->GetDiagnostics())); + + pb::ResourceTable pb_table; + SerializeTableToPb(*table, &pb_table); + + test::TestFile file_a("res/layout/main.xml"); + MockFileCollection files; + EXPECT_CALL(files, FindFile(Eq("res/layout/main.xml"))) + .WillRepeatedly(::testing::Return(&file_a)); + + ResourceTable new_table; + std::string error; + ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error)); + EXPECT_THAT(error, IsEmpty()); + + Id* new_id = test::GetValue<Id>(&new_table, "com.app.a:id/foo"); + ASSERT_THAT(new_id, NotNull()); + EXPECT_THAT(new_id->IsWeak(), Eq(id->IsWeak())); + + Maybe<ResourceTable::SearchResult> result = + new_table.FindResource(test::ParseNameOrDie("com.app.a:layout/main")); + ASSERT_TRUE(result); + + EXPECT_THAT(result.value().type->symbol_status.state, Eq(SymbolState::kPublic)); + EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic)); + + result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo")); + ASSERT_TRUE(result); + EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kUndefined)); + EXPECT_TRUE(result.value().entry->symbol_status.allow_new); + + // Find the product-dependent values + BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( + &new_table, "com.app.a:integer/one", test::ParseConfigOrDie("land"), ""); + ASSERT_THAT(prim, NotNull()); + EXPECT_THAT(prim->value.data, Eq(123u)); + + prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( + &new_table, "com.app.a:integer/one", test::ParseConfigOrDie("land"), "tablet"); + ASSERT_THAT(prim, NotNull()); + EXPECT_THAT(prim->value.data, Eq(321u)); + + Reference* actual_ref = test::GetValue<Reference>(&new_table, "com.app.a:layout/abc"); + ASSERT_THAT(actual_ref, NotNull()); + ASSERT_TRUE(actual_ref->name); + ASSERT_TRUE(actual_ref->id); + EXPECT_THAT(*actual_ref, Eq(expected_ref)); + + FileReference* actual_file_ref = + test::GetValue<FileReference>(&new_table, "com.app.a:layout/main"); + ASSERT_THAT(actual_file_ref, NotNull()); + EXPECT_THAT(actual_file_ref->file, Eq(&file_a)); + + StyledString* actual_styled_str = + test::GetValue<StyledString>(&new_table, "com.app.a:string/styled"); + ASSERT_THAT(actual_styled_str, NotNull()); + EXPECT_THAT(actual_styled_str->value->value, Eq("hello")); + ASSERT_THAT(actual_styled_str->value->spans, SizeIs(1u)); + EXPECT_THAT(*actual_styled_str->value->spans[0].name, Eq("b")); + EXPECT_THAT(actual_styled_str->value->spans[0].first_char, Eq(0u)); + EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u)); +} + +TEST(ProtoSerializeTest, SerializeAndDeserializeXml) { + xml::Element element; + element.line_number = 22; + element.column_number = 23; + element.name = "element"; + element.namespace_uri = "uri://"; + + xml::NamespaceDecl decl; + decl.prefix = "android"; + decl.uri = xml::kSchemaAndroid; + decl.line_number = 21; + decl.column_number = 24; + + element.namespace_decls.push_back(decl); + + xml::Attribute attr; + attr.name = "name"; + attr.namespace_uri = xml::kSchemaAndroid; + attr.value = "23dp"; + attr.compiled_attribute = xml::AaptAttribute({}, ResourceId(0x01010000)); + attr.compiled_value = + ResourceUtils::TryParseItemForAttribute(attr.value, android::ResTable_map::TYPE_DIMENSION); + attr.compiled_value->SetSource(Source().WithLine(25)); + element.attributes.push_back(std::move(attr)); + + std::unique_ptr<xml::Text> text = util::make_unique<xml::Text>(); + text->line_number = 25; + text->column_number = 3; + text->text = "hey there"; + element.AppendChild(std::move(text)); + + std::unique_ptr<xml::Element> child = util::make_unique<xml::Element>(); + child->name = "child"; + + text = util::make_unique<xml::Text>(); + text->text = "woah there"; + child->AppendChild(std::move(text)); + + element.AppendChild(std::move(child)); + + pb::XmlNode pb_xml; + SerializeXmlToPb(element, &pb_xml); + + StringPool pool; + xml::Element actual_el; + std::string error; + ASSERT_TRUE(DeserializeXmlFromPb(pb_xml, &actual_el, &pool, &error)); + ASSERT_THAT(error, IsEmpty()); + + EXPECT_THAT(actual_el.name, StrEq("element")); + EXPECT_THAT(actual_el.namespace_uri, StrEq("uri://")); + EXPECT_THAT(actual_el.line_number, Eq(22u)); + EXPECT_THAT(actual_el.column_number, Eq(23u)); + + ASSERT_THAT(actual_el.namespace_decls, SizeIs(1u)); + const xml::NamespaceDecl& actual_decl = actual_el.namespace_decls[0]; + EXPECT_THAT(actual_decl.prefix, StrEq("android")); + EXPECT_THAT(actual_decl.uri, StrEq(xml::kSchemaAndroid)); + EXPECT_THAT(actual_decl.line_number, Eq(21u)); + EXPECT_THAT(actual_decl.column_number, Eq(24u)); + + ASSERT_THAT(actual_el.attributes, SizeIs(1u)); + const xml::Attribute& actual_attr = actual_el.attributes[0]; + EXPECT_THAT(actual_attr.name, StrEq("name")); + EXPECT_THAT(actual_attr.namespace_uri, StrEq(xml::kSchemaAndroid)); + EXPECT_THAT(actual_attr.value, StrEq("23dp")); + + ASSERT_THAT(actual_attr.compiled_value, NotNull()); + const BinaryPrimitive* prim = ValueCast<BinaryPrimitive>(actual_attr.compiled_value.get()); + ASSERT_THAT(prim, NotNull()); + EXPECT_THAT(prim->value.dataType, Eq(android::Res_value::TYPE_DIMENSION)); + + ASSERT_TRUE(actual_attr.compiled_attribute); + ASSERT_TRUE(actual_attr.compiled_attribute.value().id); + + ASSERT_THAT(actual_el.children, SizeIs(2u)); + const xml::Text* child_text = xml::NodeCast<xml::Text>(actual_el.children[0].get()); + ASSERT_THAT(child_text, NotNull()); + const xml::Element* child_el = xml::NodeCast<xml::Element>(actual_el.children[1].get()); + ASSERT_THAT(child_el, NotNull()); + + EXPECT_THAT(child_text->line_number, Eq(25u)); + EXPECT_THAT(child_text->column_number, Eq(3u)); + EXPECT_THAT(child_text->text, StrEq("hey there")); + + EXPECT_THAT(child_el->name, StrEq("child")); + ASSERT_THAT(child_el->children, SizeIs(1u)); + + child_text = xml::NodeCast<xml::Text>(child_el->children[0].get()); + ASSERT_THAT(child_text, NotNull()); + EXPECT_THAT(child_text->text, StrEq("woah there")); +} + +static void ExpectConfigSerializes(const StringPiece& config_str) { + const ConfigDescription expected_config = test::ParseConfigOrDie(config_str); + pb::Configuration pb_config; + SerializeConfig(expected_config, &pb_config); + + ConfigDescription actual_config; + std::string error; + ASSERT_TRUE(DeserializeConfigFromPb(pb_config, &actual_config, &error)); + ASSERT_THAT(error, IsEmpty()); + EXPECT_EQ(expected_config, actual_config); +} + +TEST(ProtoSerializeTest, SerializeDeserializeConfiguration) { + ExpectConfigSerializes(""); + + ExpectConfigSerializes("mcc123"); + + ExpectConfigSerializes("mnc123"); + + ExpectConfigSerializes("en"); + ExpectConfigSerializes("en-rGB"); + ExpectConfigSerializes("b+en+GB"); + + ExpectConfigSerializes("ldltr"); + ExpectConfigSerializes("ldrtl"); + + ExpectConfigSerializes("sw3600dp"); + + ExpectConfigSerializes("w300dp"); + + ExpectConfigSerializes("h400dp"); + + ExpectConfigSerializes("small"); + ExpectConfigSerializes("normal"); + ExpectConfigSerializes("large"); + ExpectConfigSerializes("xlarge"); + + ExpectConfigSerializes("long"); + ExpectConfigSerializes("notlong"); + + ExpectConfigSerializes("round"); + ExpectConfigSerializes("notround"); + + ExpectConfigSerializes("widecg"); + ExpectConfigSerializes("nowidecg"); + + ExpectConfigSerializes("highdr"); + ExpectConfigSerializes("lowdr"); + + ExpectConfigSerializes("port"); + ExpectConfigSerializes("land"); + ExpectConfigSerializes("square"); + + ExpectConfigSerializes("desk"); + ExpectConfigSerializes("car"); + ExpectConfigSerializes("television"); + ExpectConfigSerializes("appliance"); + ExpectConfigSerializes("watch"); + ExpectConfigSerializes("vrheadset"); + + ExpectConfigSerializes("night"); + ExpectConfigSerializes("notnight"); + + ExpectConfigSerializes("300dpi"); + ExpectConfigSerializes("hdpi"); + + ExpectConfigSerializes("notouch"); + ExpectConfigSerializes("stylus"); + ExpectConfigSerializes("finger"); + + ExpectConfigSerializes("keysexposed"); + ExpectConfigSerializes("keyshidden"); + ExpectConfigSerializes("keyssoft"); + + ExpectConfigSerializes("nokeys"); + ExpectConfigSerializes("qwerty"); + ExpectConfigSerializes("12key"); + + ExpectConfigSerializes("navhidden"); + ExpectConfigSerializes("navexposed"); + + ExpectConfigSerializes("nonav"); + ExpectConfigSerializes("dpad"); + ExpectConfigSerializes("trackball"); + ExpectConfigSerializes("wheel"); + + ExpectConfigSerializes("300x200"); + + ExpectConfigSerializes("v8"); + + ExpectConfigSerializes( + "mcc123-mnc456-b+en+GB-ldltr-sw300dp-w300dp-h400dp-large-long-round-widecg-highdr-land-car-" + "night-xhdpi-stylus-keysexposed-qwerty-navhidden-dpad-300x200-v23"); +} + +} // namespace aapt diff --git a/tools/aapt2/formats.md b/tools/aapt2/formats.md new file mode 100644 index 000000000000..bb31a005ef42 --- /dev/null +++ b/tools/aapt2/formats.md @@ -0,0 +1,44 @@ +# AAPT2 On-Disk Formats +- AAPT2 Container Format (extension `.apc`) +- AAPT2 Static Library Format (extension `.sapk`) + +## AAPT2 Container Format (extension `.apc`) +The APC format (AAPT2 Container Format) is generated by AAPT2 during the compile phase and +consumed by the AAPT2 link phase. It is a simple container format for storing compiled PNGs, +binary and protobuf XML, and intermediate protobuf resource tables. It also stores all associated +meta-data from the compile phase. + +### Format +The file starts with a simple header. All multi-byte fields are little-endian. + +| Size (in bytes) | Field | Description | +|:----------------|:--------------|:-----------------------------------------------------| +| `4` | `magic` | The magic bytes must equal `'AAPT'` or `0x54504141`. | +| `4` | `version` | The version of the container format. | +| `4` | `entry_count` | The number of entries in this container. | + +This is followed by `entry_count` of the following data structure. It must be aligned on a 32-bit +boundary, so if a previous entry ends unaligned, padding must be inserted. + +| Size (in bytes) | Field | Description | +|:----------------|:---------------|:----------------------------------------------------------------------------------------------------------| +| `4` | `entry_type` | The type of the entry. This can be one of two types: `RES_TABLE (0x00000000)` or `RES_FILE (0x00000001)`. | +| `8` | `entry_length` | The length of the data that follows. | +| `entry_length` | `data` | The payload. The contents of this varies based on the `entry_type`. | + +If the `entry_type` is equal to `RES_TABLE (0x00000000)`, the `data` field contains a serialized +[aapt.pb.ResourceTable](Resources.proto). + +If the `entry_type` is equal to `RES_FILE (0x00000001)`, the `data` field contains the following: + + +| Size (in bytes) | Field | Description | +|:----------------|:---------------|:----------------------------------------------------------------------------------------------------------| +| `4` | `header_size` | The size of the `header` field. | +| `8` | `data_size` | The size of the `data` field. | +| `header_size` | `header` | The serialized Protobuf message [aapt.pb.internal.CompiledFile](ResourcesInternal.proto). | +| `x` | `padding` | Up to 4 bytes of zeros, if padding is necessary to align the `data` field on a 32-bit boundary. | +| `data_size` | `data` | The payload, which is determined by the `type` field in the `aapt.pb.internal.CompiledFile`. This can be a PNG file, binary XML, or [aapt.pb.XmlNode](Resources.proto). | + +## AAPT2 Static Library Format (extension `.sapk`) + diff --git a/tools/aapt2/integration-tests/NamespaceTest/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/Android.mk new file mode 100644 index 000000000000..6361f9b8ae7d --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/Android.mk @@ -0,0 +1,2 @@ +LOCAL_PATH := $(call my-dir) +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk new file mode 100644 index 000000000000..94686c095bd8 --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk @@ -0,0 +1,28 @@ +# +# Copyright (C) 2017 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. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true +LOCAL_AAPT_NAMESPACES := true +LOCAL_PACKAGE_NAME := AaptTestNamespace_App +LOCAL_MODULE_TAGS := tests +LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_STATIC_ANDROID_LIBRARIES := \ + AaptTestNamespace_LibOne \ + AaptTestNamespace_LibTwo +include $(BUILD_PACKAGE) diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/AndroidManifest.xml b/tools/aapt2/integration-tests/NamespaceTest/App/AndroidManifest.xml new file mode 100644 index 000000000000..6398a83ea1d2 --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/App/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.aapt.namespace.app"> + + <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/> + + <application android:theme="@style/AppTheme" android:label="@string/app_name"> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/res/layout/activity_main.xml b/tools/aapt2/integration-tests/NamespaceTest/App/res/layout/activity_main.xml new file mode 100644 index 000000000000..19bfd942a5bd --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/App/res/layout/activity_main.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:libone="http://schemas.android.com/apk/res/com.android.aapt.namespace.libone" + xmlns:libtwo="http://schemas.android.com/apk/res/com.android.aapt.namespace.libtwo" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <com.android.aapt.namespace.libtwo.TextView + android:id="@+id/textview" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerInParent="@bool/always_true" + android:text="@libone:string/textview_text" + libtwo:textview_attr="?libone:theme_attr" /> +</RelativeLayout>
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/res/values/values.xml b/tools/aapt2/integration-tests/NamespaceTest/App/res/values/values.xml new file mode 100644 index 000000000000..1b80d9542881 --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/App/res/values/values.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> + +<resources> + <string name="app_name">Namespace App</string> + <bool name="always_true">true</bool> + + <style name="AppTheme" parent="com.android.aapt.namespace.libone:style/Theme"> + <item name="android:colorPrimary">#3F51B5</item> + <item name="android:colorPrimaryDark">#303F9F</item> + <item name="android:colorAccent">#FF4081</item> + <item name="com.android.aapt.namespace.libone:theme_attr"> + @com.android.aapt.namespace.libtwo:string/public_string + </item> + </style> +</resources>
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/src/com/android/aapt/namespace/app/MainActivity.java b/tools/aapt2/integration-tests/NamespaceTest/App/src/com/android/aapt/namespace/app/MainActivity.java new file mode 100644 index 000000000000..fcb4c3c12f81 --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/App/src/com/android/aapt/namespace/app/MainActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017 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. + */ +package com.android.aapt.namespace.app; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.Toast; + +public class MainActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + com.android.aapt.namespace.libtwo.TextView tv = findViewById(R.id.textview); + + + + Toast.makeText(this, tv.getTextViewAttr(), Toast.LENGTH_LONG).show(); + } +} diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk new file mode 100644 index 000000000000..b1cac68dae7a --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk @@ -0,0 +1,28 @@ +# +# Copyright (C) 2017 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. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true +LOCAL_AAPT_NAMESPACES := true +LOCAL_MODULE := AaptTestNamespace_LibOne +LOCAL_MODULE_TAGS := tests +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +# We need this to retain the R.java generated for this library. +LOCAL_JAR_EXCLUDE_FILES := none +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibOne/AndroidManifest.xml b/tools/aapt2/integration-tests/NamespaceTest/LibOne/AndroidManifest.xml new file mode 100644 index 000000000000..70b4b226624b --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/LibOne/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.aapt.namespace.libone"> + + <uses-sdk android:minSdkVersion="21" /> +</manifest> diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibOne/res/values/values.xml b/tools/aapt2/integration-tests/NamespaceTest/LibOne/res/values/values.xml new file mode 100644 index 000000000000..d2dcea0c0b1e --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/LibOne/res/values/values.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> + +<resources> + <public type="string" name="textview_text" /> + <string name="textview_text">LibOne\'s textview_text string!</string> + + <public type="attr" name="theme_attr" /> + <attr name="theme_attr" format="string" /> + + <public type="style" name="Theme" /> + <style name="Theme" parent="android:Theme.Material.Light.DarkActionBar"> + <item name="theme_attr">[Please override with your own value]</item> + </style> +</resources>
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk new file mode 100644 index 000000000000..dc16d1bbb420 --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk @@ -0,0 +1,29 @@ +# +# Copyright (C) 2017 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. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true +LOCAL_AAPT_NAMESPACES := true +LOCAL_MODULE := AaptTestNamespace_LibTwo +LOCAL_MODULE_TAGS := tests +LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +# We need this to retain the R.java generated for this library. +LOCAL_JAR_EXCLUDE_FILES := none +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/AndroidManifest.xml b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/AndroidManifest.xml new file mode 100644 index 000000000000..32944a9b8f0b --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.aapt.namespace.libtwo"> + + <uses-sdk android:minSdkVersion="21" /> +</manifest> diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/res/values/values.xml b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/res/values/values.xml new file mode 100644 index 000000000000..0c5f5d8d55e0 --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/res/values/values.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> + +<resources> + <public type="string" name="public_string" /> + <string name="public_string">LibTwo\'s public string!</string> + + <public type="attr" name="textview_attr" /> + <attr name="textview_attr" format="string" /> + + <declare-styleable name="TextView"> + <attr name="textview_attr" /> + </declare-styleable> +</resources>
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/src/com/android/aapt/namespace/libtwo/TextView.java b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/src/com/android/aapt/namespace/libtwo/TextView.java new file mode 100644 index 000000000000..0f8024e58797 --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/src/com/android/aapt/namespace/libtwo/TextView.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 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. + */ +package com.android.aapt.namespace.libtwo; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +public class TextView extends android.widget.TextView { + + private String mTextViewAttr; + + public TextView(Context context) { + this(context, null); + } + + public TextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TextView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public TextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.TextView, + 0, 0); + try { + mTextViewAttr = ta.getString(R.styleable.TextView_textview_attr); + } finally { + ta.recycle(); + } + } + + public String getTextViewAttr() { + return mTextViewAttr; + } +} diff --git a/tools/aapt2/integration-tests/StaticLibTest/Android.mk b/tools/aapt2/integration-tests/StaticLibTest/Android.mk new file mode 100644 index 000000000000..6361f9b8ae7d --- /dev/null +++ b/tools/aapt2/integration-tests/StaticLibTest/Android.mk @@ -0,0 +1,2 @@ +LOCAL_PATH := $(call my-dir) +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/aapt2/integration-tests/AppOne/Android.mk b/tools/aapt2/integration-tests/StaticLibTest/App/Android.mk index 38bd5b5e3275..4d0c01d565a5 100644 --- a/tools/aapt2/integration-tests/AppOne/Android.mk +++ b/tools/aapt2/integration-tests/StaticLibTest/App/Android.mk @@ -18,12 +18,12 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_USE_AAPT2 := true -LOCAL_PACKAGE_NAME := AaptTestAppOne +LOCAL_PACKAGE_NAME := AaptTestStaticLib_App LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets $(LOCAL_PATH)/assets2 LOCAL_STATIC_ANDROID_LIBRARIES := \ - AaptTestStaticLibOne \ - AaptTestStaticLibTwo + AaptTestStaticLib_LibOne \ + AaptTestStaticLib_LibTwo LOCAL_AAPT_FLAGS := --no-version-vectors --no-version-transitions include $(BUILD_PACKAGE) diff --git a/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml b/tools/aapt2/integration-tests/StaticLibTest/App/AndroidManifest.xml index a5f202dd22fc..c8f4bda9c508 100644 --- a/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/AndroidManifest.xml @@ -21,7 +21,7 @@ <uses-permission-sdk-23 android:name="android.permission.TEST" android:maxSdkVersion="22" /> <application> - <activity android:name=".MyActivity"> + <activity android:name=".MyActivity" android:id="@+id/sample_generated_id"> <layout android:defaultHeight="500dp" android:defaultWidth="600dp" /> </activity> diff --git a/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt b/tools/aapt2/integration-tests/StaticLibTest/App/assets/subdir/subsubdir/test.txt index 125194943ec8..125194943ec8 100644 --- a/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt +++ b/tools/aapt2/integration-tests/StaticLibTest/App/assets/subdir/subsubdir/test.txt diff --git a/tools/aapt2/integration-tests/AppOne/assets/test.txt b/tools/aapt2/integration-tests/StaticLibTest/App/assets/test.txt index 88266de2b4d4..88266de2b4d4 100644 --- a/tools/aapt2/integration-tests/AppOne/assets/test.txt +++ b/tools/aapt2/integration-tests/StaticLibTest/App/assets/test.txt diff --git a/tools/aapt2/integration-tests/AppOne/assets2/new.txt b/tools/aapt2/integration-tests/StaticLibTest/App/assets2/new.txt index f4963a95503a..f4963a95503a 100644 --- a/tools/aapt2/integration-tests/AppOne/assets2/new.txt +++ b/tools/aapt2/integration-tests/StaticLibTest/App/assets2/new.txt diff --git a/tools/aapt2/integration-tests/AppOne/assets2/test.txt b/tools/aapt2/integration-tests/StaticLibTest/App/assets2/test.txt index 5d8b36c0d52d..5d8b36c0d52d 100644 --- a/tools/aapt2/integration-tests/AppOne/assets2/test.txt +++ b/tools/aapt2/integration-tests/StaticLibTest/App/assets2/test.txt diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/cheap_transparency.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/cheap_transparency.png Binary files differindex 0522a9979db9..0522a9979db9 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/cheap_transparency.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/cheap_transparency.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/complex.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/complex.9.png Binary files differindex baf9fff13ab5..baf9fff13ab5 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/complex.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/complex.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/icon.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/icon.png Binary files differindex 4bff9b900643..4bff9b900643 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/icon.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/icon.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/image.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/image.xml index 6132a75d85d0..6132a75d85d0 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/image.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/image.xml diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/outline_8x8.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/outline_8x8.9.png Binary files differindex 7b331e16fcbd..7b331e16fcbd 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/outline_8x8.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/outline_8x8.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_off_center_outline_32x16.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/round_rect_off_center_outline_32x16.9.png Binary files differindex 0ec6c70a2b9f..0ec6c70a2b9f 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_off_center_outline_32x16.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/round_rect_off_center_outline_32x16.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_outline_32x16.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/round_rect_outline_32x16.9.png Binary files differindex e05708a089a3..e05708a089a3 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_outline_32x16.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/round_rect_outline_32x16.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/test.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/test.9.png Binary files differindex 33daa117ea9d..33daa117ea9d 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/test.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/test.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_3x3.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/transparent_3x3.9.png Binary files differindex a11377a0d670..a11377a0d670 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_3x3.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/transparent_3x3.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_optical_bounds_3x3.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/transparent_optical_bounds_3x3.9.png Binary files differindex 6803e4243484..6803e4243484 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_optical_bounds_3x3.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/transparent_optical_bounds_3x3.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/white_3x3.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/white_3x3.9.png Binary files differindex 1a3731bbc8b8..1a3731bbc8b8 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/white_3x3.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/white_3x3.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/white_optical_bounds_3x3.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/white_optical_bounds_3x3.9.png Binary files differindex 489ace292e1f..489ace292e1f 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/white_optical_bounds_3x3.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/white_optical_bounds_3x3.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/font/myfont-italic.ttf b/tools/aapt2/integration-tests/StaticLibTest/App/res/font/myfont-italic.ttf index e69de29bb2d1..e69de29bb2d1 100644 --- a/tools/aapt2/integration-tests/AppOne/res/font/myfont-italic.ttf +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/font/myfont-italic.ttf diff --git a/tools/aapt2/integration-tests/AppOne/res/font/myfont-normal.ttf b/tools/aapt2/integration-tests/StaticLibTest/App/res/font/myfont-normal.ttf index e69de29bb2d1..e69de29bb2d1 100644 --- a/tools/aapt2/integration-tests/AppOne/res/font/myfont-normal.ttf +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/font/myfont-normal.ttf diff --git a/tools/aapt2/integration-tests/AppOne/res/font/myfont.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/font/myfont.xml index 1fb67914894e..1fb67914894e 100644 --- a/tools/aapt2/integration-tests/AppOne/res/font/myfont.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/font/myfont.xml diff --git a/tools/aapt2/integration-tests/AppOne/res/layout-v21/main.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/layout-v21/main.xml index 9f5a4a85cbcf..724bfe4a9536 100644 --- a/tools/aapt2/integration-tests/AppOne/res/layout-v21/main.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/layout-v21/main.xml @@ -15,7 +15,6 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:support="http://schemas.android.com/apk/res/android.appcompat" android:id="@+id/view" android:layout_width="match_parent" android:layout_height="wrap_content"> diff --git a/tools/aapt2/integration-tests/AppOne/res/layout/main.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/layout/main.xml index ab1a251a7d56..5a734296698f 100644 --- a/tools/aapt2/integration-tests/AppOne/res/layout/main.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/layout/main.xml @@ -15,12 +15,11 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:support="http://schemas.android.com/apk/res/android.appcompat" android:id="@+id/view" android:layout_width="match_parent" android:layout_height="wrap_content"> - <fragment class="android.test.sample.App$Inner" /> + <fragment class="android.test.sample.App$Inner" android:id="@id/sample_generated_id"/> <variable name="user" type="com.android.User" /> diff --git a/tools/aapt2/integration-tests/AppOne/res/layout/special.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/layout/special.xml index 28c85ca92019..28c85ca92019 100644 --- a/tools/aapt2/integration-tests/AppOne/res/layout/special.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/layout/special.xml diff --git a/tools/aapt2/integration-tests/AppOne/res/navigation/home.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/navigation/home.xml index ade271d60ab6..ade271d60ab6 100644 --- a/tools/aapt2/integration-tests/AppOne/res/navigation/home.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/navigation/home.xml diff --git a/tools/aapt2/integration-tests/AppOne/res/raw/test.txt b/tools/aapt2/integration-tests/StaticLibTest/App/res/raw/test.txt index b14df6442ea5..b14df6442ea5 100644 --- a/tools/aapt2/integration-tests/AppOne/res/raw/test.txt +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/raw/test.txt diff --git a/tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/transition/transition_set.xml index e10e6c2f53da..e10e6c2f53da 100644 --- a/tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/transition/transition_set.xml diff --git a/tools/aapt2/integration-tests/AppOne/res/values-v4/styles.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/values-v4/styles.xml index d8c11e210eda..d8c11e210eda 100644 --- a/tools/aapt2/integration-tests/AppOne/res/values-v4/styles.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/values-v4/styles.xml diff --git a/tools/aapt2/integration-tests/AppOne/res/values/colors.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/values/colors.xml index 4df5077051d2..4df5077051d2 100644 --- a/tools/aapt2/integration-tests/AppOne/res/values/colors.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/values/colors.xml diff --git a/tools/aapt2/integration-tests/AppOne/res/values/styles.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/values/styles.xml index 19d96c0809db..a088e5d0e1a2 100644 --- a/tools/aapt2/integration-tests/AppOne/res/values/styles.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/values/styles.xml @@ -14,7 +14,7 @@ limitations under the License. --> -<resources xmlns:lib="http://schemas.android.com/apk/res/android.appcompat"> +<resources> <style name="App"> <item name="android:background">@color/primary</item> <item name="android:colorPrimary">@color/primary</item> diff --git a/tools/aapt2/integration-tests/AppOne/res/values/test.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/values/test.xml index 2c9e8b877565..2c9e8b877565 100644 --- a/tools/aapt2/integration-tests/AppOne/res/values/test.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/values/test.xml diff --git a/tools/aapt2/integration-tests/AppOne/src/com/android/aapt/app/one/AppOne.java b/tools/aapt2/integration-tests/StaticLibTest/App/src/com/android/aapt/app/one/AppOne.java index 472b35a781fe..472b35a781fe 100644 --- a/tools/aapt2/integration-tests/AppOne/src/com/android/aapt/app/one/AppOne.java +++ b/tools/aapt2/integration-tests/StaticLibTest/App/src/com/android/aapt/app/one/AppOne.java diff --git a/tools/aapt2/integration-tests/StaticLibOne/Android.mk b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.mk index 0b7129aa0d38..0c828b80b3b3 100644 --- a/tools/aapt2/integration-tests/StaticLibOne/Android.mk +++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.mk @@ -18,11 +18,11 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_USE_AAPT2 := true -LOCAL_MODULE := AaptTestStaticLibOne +LOCAL_MODULE := AaptTestStaticLib_LibOne LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -# We need this to compile the Java sources of AaptTestStaticLibTwo using javac. +# We need this to compile the Java sources of AaptTestStaticLib_LibTwo using javac. LOCAL_JAR_EXCLUDE_FILES := none include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/tools/aapt2/integration-tests/StaticLibOne/AndroidManifest.xml b/tools/aapt2/integration-tests/StaticLibTest/LibOne/AndroidManifest.xml index 705047e71300..705047e71300 100644 --- a/tools/aapt2/integration-tests/StaticLibOne/AndroidManifest.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/AndroidManifest.xml diff --git a/tools/aapt2/integration-tests/StaticLibOne/res/layout/layout.xml b/tools/aapt2/integration-tests/StaticLibTest/LibOne/res/layout/layout.xml index 683c91cd9cf5..683c91cd9cf5 100644 --- a/tools/aapt2/integration-tests/StaticLibOne/res/layout/layout.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/res/layout/layout.xml diff --git a/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml b/tools/aapt2/integration-tests/StaticLibTest/LibOne/res/values/values.xml index b4dc90b3e640..b4dc90b3e640 100644 --- a/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/res/values/values.xml diff --git a/tools/aapt2/integration-tests/StaticLibOne/src/com/android/aapt/staticlib/one/StaticLibOne.java b/tools/aapt2/integration-tests/StaticLibTest/LibOne/src/com/android/aapt/staticlib/one/StaticLibOne.java index cf48f67056cf..cf48f67056cf 100644 --- a/tools/aapt2/integration-tests/StaticLibOne/src/com/android/aapt/staticlib/one/StaticLibOne.java +++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/src/com/android/aapt/staticlib/one/StaticLibOne.java diff --git a/tools/aapt2/integration-tests/StaticLibTwo/Android.mk b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.mk index 8b6eb41b08cd..538d5251b203 100644 --- a/tools/aapt2/integration-tests/StaticLibTwo/Android.mk +++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.mk @@ -18,10 +18,10 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_USE_AAPT2 := true -LOCAL_MODULE := AaptTestStaticLibTwo +LOCAL_MODULE := AaptTestStaticLib_LibTwo LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -LOCAL_SHARED_ANDROID_LIBRARIES := AaptTestStaticLibOne +LOCAL_SHARED_ANDROID_LIBRARIES := AaptTestStaticLib_LibOne include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/tools/aapt2/integration-tests/StaticLibTwo/AndroidManifest.xml b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/AndroidManifest.xml index 28f069932452..28f069932452 100644 --- a/tools/aapt2/integration-tests/StaticLibTwo/AndroidManifest.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/AndroidManifest.xml diff --git a/tools/aapt2/integration-tests/StaticLibTwo/res/drawable/vector.xml b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/res/drawable/vector.xml index dd5979f7e838..dd5979f7e838 100644 --- a/tools/aapt2/integration-tests/StaticLibTwo/res/drawable/vector.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/res/drawable/vector.xml diff --git a/tools/aapt2/integration-tests/StaticLibTwo/res/layout/layout_two.xml b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/res/layout/layout_two.xml index ba9830708eb0..ba9830708eb0 100644 --- a/tools/aapt2/integration-tests/StaticLibTwo/res/layout/layout_two.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/res/layout/layout_two.xml diff --git a/tools/aapt2/integration-tests/StaticLibTwo/res/values/values.xml b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/res/values/values.xml index 97bb2a53d9f6..97bb2a53d9f6 100644 --- a/tools/aapt2/integration-tests/StaticLibTwo/res/values/values.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/res/values/values.xml diff --git a/tools/aapt2/integration-tests/StaticLibTwo/src/com/android/aapt/staticlib/two/StaticLibTwo.java b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/src/com/android/aapt/staticlib/two/StaticLibTwo.java index 7110dcdd017a..7110dcdd017a 100644 --- a/tools/aapt2/integration-tests/StaticLibTwo/src/com/android/aapt/staticlib/two/StaticLibTwo.java +++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/src/com/android/aapt/staticlib/two/StaticLibTwo.java diff --git a/tools/aapt2/io/BigBufferStreams.cpp b/tools/aapt2/io/BigBufferStream.cpp index eb99033e1cbe..9704caae4719 100644 --- a/tools/aapt2/io/BigBufferStreams.cpp +++ b/tools/aapt2/io/BigBufferStream.cpp @@ -14,8 +14,7 @@ * limitations under the License. */ -#include "io/BigBufferInputStream.h" -#include "io/BigBufferOutputStream.h" +#include "io/BigBufferStream.h" namespace aapt { namespace io { @@ -54,7 +53,9 @@ void BigBufferInputStream::BackUp(size_t count) { } } -bool BigBufferInputStream::CanRewind() const { return true; } +bool BigBufferInputStream::CanRewind() const { + return true; +} bool BigBufferInputStream::Rewind() { iter_ = buffer_->begin(); @@ -63,9 +64,17 @@ bool BigBufferInputStream::Rewind() { return true; } -size_t BigBufferInputStream::ByteCount() const { return bytes_read_; } +size_t BigBufferInputStream::ByteCount() const { + return bytes_read_; +} -bool BigBufferInputStream::HadError() const { return false; } +bool BigBufferInputStream::HadError() const { + return false; +} + +size_t BigBufferInputStream::TotalSize() const { + return buffer_->size(); +} // // BigBufferOutputStream @@ -76,11 +85,17 @@ bool BigBufferOutputStream::Next(void** data, size_t* size) { return true; } -void BigBufferOutputStream::BackUp(size_t count) { buffer_->BackUp(count); } +void BigBufferOutputStream::BackUp(size_t count) { + buffer_->BackUp(count); +} -size_t BigBufferOutputStream::ByteCount() const { return buffer_->size(); } +size_t BigBufferOutputStream::ByteCount() const { + return buffer_->size(); +} -bool BigBufferOutputStream::HadError() const { return false; } +bool BigBufferOutputStream::HadError() const { + return false; +} } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/BigBufferInputStream.h b/tools/aapt2/io/BigBufferStream.h index 92612c744aae..8b5c8b84cd3c 100644 --- a/tools/aapt2/io/BigBufferInputStream.h +++ b/tools/aapt2/io/BigBufferStream.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef AAPT_IO_BIGBUFFERINPUTSTREAM_H -#define AAPT_IO_BIGBUFFERINPUTSTREAM_H +#ifndef AAPT_IO_BIGBUFFERSTREAM_H +#define AAPT_IO_BIGBUFFERSTREAM_H #include "io/Io.h" #include "util/BigBuffer.h" @@ -23,10 +23,11 @@ namespace aapt { namespace io { -class BigBufferInputStream : public InputStream { +class BigBufferInputStream : public KnownSizeInputStream { public: inline explicit BigBufferInputStream(const BigBuffer* buffer) - : buffer_(buffer), iter_(buffer->begin()) {} + : buffer_(buffer), iter_(buffer->begin()) { + } virtual ~BigBufferInputStream() = default; bool Next(const void** data, size_t* size) override; @@ -41,6 +42,8 @@ class BigBufferInputStream : public InputStream { bool HadError() const override; + size_t TotalSize() const override; + private: DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream); @@ -50,7 +53,27 @@ class BigBufferInputStream : public InputStream { size_t bytes_read_ = 0; }; +class BigBufferOutputStream : public OutputStream { + public: + inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) { + } + virtual ~BigBufferOutputStream() = default; + + bool Next(void** data, size_t* size) override; + + void BackUp(size_t count) override; + + size_t ByteCount() const override; + + bool HadError() const override; + + private: + DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream); + + BigBuffer* buffer_; +}; + } // namespace io } // namespace aapt -#endif // AAPT_IO_BIGBUFFERINPUTSTREAM_H +#endif // AAPT_IO_BIGBUFFERSTREAM_H diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h index 09dc7ea5b1c4..db91a77a5ae6 100644 --- a/tools/aapt2/io/Data.h +++ b/tools/aapt2/io/Data.h @@ -28,12 +28,16 @@ namespace aapt { namespace io { // Interface for a block of contiguous memory. An instance of this interface owns the data. -class IData : public InputStream { +class IData : public KnownSizeInputStream { public: virtual ~IData() = default; virtual const void* data() const = 0; virtual size_t size() const = 0; + + virtual size_t TotalSize() const override { + return size(); + } }; class DataSegment : public IData { diff --git a/tools/aapt2/io/File.cpp b/tools/aapt2/io/File.cpp index ee737280ec81..b4f1ff3a5b49 100644 --- a/tools/aapt2/io/File.cpp +++ b/tools/aapt2/io/File.cpp @@ -39,5 +39,9 @@ std::unique_ptr<IData> FileSegment::OpenAsData() { return {}; } +std::unique_ptr<io::InputStream> FileSegment::OpenInputStream() { + return OpenAsData(); +} + } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h index 7ef6d88c1e3b..f06e28c79c7b 100644 --- a/tools/aapt2/io/File.h +++ b/tools/aapt2/io/File.h @@ -43,6 +43,8 @@ class IFile { // Returns nullptr on failure. virtual std::unique_ptr<IData> OpenAsData() = 0; + virtual std::unique_ptr<io::InputStream> OpenInputStream() = 0; + // Returns the source of this file. This is for presentation to the user and // may not be a valid file system path (for example, it may contain a '@' sign to separate // the files within a ZIP archive from the path to the containing ZIP archive. @@ -71,8 +73,11 @@ class FileSegment : public IFile { : file_(file), offset_(offset), len_(len) {} std::unique_ptr<IData> OpenAsData() override; + std::unique_ptr<io::InputStream> OpenInputStream() override; - const Source& GetSource() const override { return file_->GetSource(); } + const Source& GetSource() const override { + return file_->GetSource(); + } private: DISALLOW_COPY_AND_ASSIGN(FileSegment); diff --git a/tools/aapt2/io/FileInputStream.cpp b/tools/aapt2/io/FileInputStream.cpp deleted file mode 100644 index 07dbb5a98add..000000000000 --- a/tools/aapt2/io/FileInputStream.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2017 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 "io/FileInputStream.h" - -#include <errno.h> // for errno -#include <fcntl.h> // for O_RDONLY -#include <unistd.h> // for read - -#include "android-base/errors.h" -#include "android-base/file.h" // for O_BINARY -#include "android-base/macros.h" -#include "android-base/utf8.h" - -using ::android::base::SystemErrorCodeToString; - -namespace aapt { -namespace io { - -FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity) - : FileInputStream(::android::base::utf8::open(path.c_str(), O_RDONLY | O_BINARY), - buffer_capacity) { -} - -FileInputStream::FileInputStream(int fd, size_t buffer_capacity) - : fd_(fd), - buffer_capacity_(buffer_capacity), - buffer_offset_(0u), - buffer_size_(0u), - total_byte_count_(0u) { - if (fd_ == -1) { - error_ = SystemErrorCodeToString(errno); - } else { - buffer_.reset(new uint8_t[buffer_capacity_]); - } -} - -bool FileInputStream::Next(const void** data, size_t* size) { - if (HadError()) { - return false; - } - - // Deal with any remaining bytes after BackUp was called. - if (buffer_offset_ != buffer_size_) { - *data = buffer_.get() + buffer_offset_; - *size = buffer_size_ - buffer_offset_; - total_byte_count_ += buffer_size_ - buffer_offset_; - buffer_offset_ = buffer_size_; - return true; - } - - ssize_t n = TEMP_FAILURE_RETRY(read(fd_, buffer_.get(), buffer_capacity_)); - if (n < 0) { - error_ = SystemErrorCodeToString(errno); - fd_.reset(); - return false; - } - - buffer_size_ = static_cast<size_t>(n); - buffer_offset_ = buffer_size_; - total_byte_count_ += buffer_size_; - - *data = buffer_.get(); - *size = buffer_size_; - return buffer_size_ != 0u; -} - -void FileInputStream::BackUp(size_t count) { - if (count > buffer_offset_) { - count = buffer_offset_; - } - buffer_offset_ -= count; - total_byte_count_ -= count; -} - -size_t FileInputStream::ByteCount() const { - return total_byte_count_; -} - -bool FileInputStream::HadError() const { - return !error_.empty(); -} - -std::string FileInputStream::GetError() const { - return error_; -} - -} // namespace io -} // namespace aapt diff --git a/tools/aapt2/io/FileInputStream.h b/tools/aapt2/io/FileInputStream.h deleted file mode 100644 index 6beb9a186ce5..000000000000 --- a/tools/aapt2/io/FileInputStream.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2017 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 AAPT_IO_FILEINPUTSTREAM_H -#define AAPT_IO_FILEINPUTSTREAM_H - -#include "io/Io.h" - -#include <memory> -#include <string> - -#include "android-base/macros.h" -#include "android-base/unique_fd.h" - -namespace aapt { -namespace io { - -class FileInputStream : public InputStream { - public: - explicit FileInputStream(const std::string& path, size_t buffer_capacity = 4096); - - // Takes ownership of `fd`. - explicit FileInputStream(int fd, size_t buffer_capacity = 4096); - - bool Next(const void** data, size_t* size) override; - - void BackUp(size_t count) override; - - size_t ByteCount() const override; - - bool HadError() const override; - - std::string GetError() const override; - - private: - DISALLOW_COPY_AND_ASSIGN(FileInputStream); - - android::base::unique_fd fd_; - std::string error_; - std::unique_ptr<uint8_t[]> buffer_; - size_t buffer_capacity_; - size_t buffer_offset_; - size_t buffer_size_; - size_t total_byte_count_; -}; - -} // namespace io -} // namespace aapt - -#endif // AAPT_IO_FILEINPUTSTREAM_H diff --git a/tools/aapt2/io/FileStream.cpp b/tools/aapt2/io/FileStream.cpp new file mode 100644 index 000000000000..27529bc08a16 --- /dev/null +++ b/tools/aapt2/io/FileStream.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2017 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 "io/FileStream.h" + +#include <errno.h> // for errno +#include <fcntl.h> // for O_RDONLY +#include <unistd.h> // for read + +#include "android-base/errors.h" +#include "android-base/file.h" // for O_BINARY +#include "android-base/macros.h" +#include "android-base/utf8.h" + +#if defined(_WIN32) +// This is only needed for O_CLOEXEC. +#include <windows.h> +#define O_CLOEXEC O_NOINHERIT +#endif + +using ::android::base::SystemErrorCodeToString; +using ::android::base::unique_fd; + +namespace aapt { +namespace io { + +FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity) + : buffer_capacity_(buffer_capacity) { + int mode = O_RDONLY | O_CLOEXEC | O_BINARY; + fd_.reset(TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), mode))); + if (fd_ == -1) { + error_ = SystemErrorCodeToString(errno); + } else { + buffer_.reset(new uint8_t[buffer_capacity_]); + } +} + +FileInputStream::FileInputStream(int fd, size_t buffer_capacity) + : fd_(fd), buffer_capacity_(buffer_capacity) { + if (fd_ < 0) { + error_ = "Bad File Descriptor"; + } else { + buffer_.reset(new uint8_t[buffer_capacity_]); + } +} + +bool FileInputStream::Next(const void** data, size_t* size) { + if (HadError()) { + return false; + } + + // Deal with any remaining bytes after BackUp was called. + if (buffer_offset_ != buffer_size_) { + *data = buffer_.get() + buffer_offset_; + *size = buffer_size_ - buffer_offset_; + total_byte_count_ += buffer_size_ - buffer_offset_; + buffer_offset_ = buffer_size_; + return true; + } + + ssize_t n = TEMP_FAILURE_RETRY(read(fd_, buffer_.get(), buffer_capacity_)); + if (n < 0) { + error_ = SystemErrorCodeToString(errno); + fd_.reset(); + buffer_.reset(); + return false; + } + + buffer_size_ = static_cast<size_t>(n); + buffer_offset_ = buffer_size_; + total_byte_count_ += buffer_size_; + + *data = buffer_.get(); + *size = buffer_size_; + return buffer_size_ != 0u; +} + +void FileInputStream::BackUp(size_t count) { + if (count > buffer_offset_) { + count = buffer_offset_; + } + buffer_offset_ -= count; + total_byte_count_ -= count; +} + +size_t FileInputStream::ByteCount() const { + return total_byte_count_; +} + +bool FileInputStream::HadError() const { + return fd_ == -1; +} + +std::string FileInputStream::GetError() const { + return error_; +} + +FileOutputStream::FileOutputStream(const std::string& path, size_t buffer_capacity) + : buffer_capacity_(buffer_capacity) { + int mode = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY; + owned_fd_.reset(TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), mode, 0666))); + fd_ = owned_fd_.get(); + if (fd_ < 0) { + error_ = SystemErrorCodeToString(errno); + } else { + buffer_.reset(new uint8_t[buffer_capacity_]); + } +} + +FileOutputStream::FileOutputStream(unique_fd fd, size_t buffer_capacity) + : FileOutputStream(fd.get(), buffer_capacity) { + owned_fd_ = std::move(fd); +} + +FileOutputStream::FileOutputStream(int fd, size_t buffer_capacity) + : fd_(fd), buffer_capacity_(buffer_capacity) { + if (fd_ < 0) { + error_ = "Bad File Descriptor"; + } else { + buffer_.reset(new uint8_t[buffer_capacity_]); + } +} + +FileOutputStream::~FileOutputStream() { + // Flush the buffer. + Flush(); +} + +bool FileOutputStream::Next(void** data, size_t* size) { + if (HadError()) { + return false; + } + + if (buffer_offset_ == buffer_capacity_) { + if (!FlushImpl()) { + return false; + } + } + + const size_t buffer_size = buffer_capacity_ - buffer_offset_; + *data = buffer_.get() + buffer_offset_; + *size = buffer_size; + total_byte_count_ += buffer_size; + buffer_offset_ = buffer_capacity_; + return true; +} + +void FileOutputStream::BackUp(size_t count) { + if (count > buffer_offset_) { + count = buffer_offset_; + } + buffer_offset_ -= count; + total_byte_count_ -= count; +} + +size_t FileOutputStream::ByteCount() const { + return total_byte_count_; +} + +bool FileOutputStream::Flush() { + if (!HadError()) { + return FlushImpl(); + } + return false; +} + +bool FileOutputStream::FlushImpl() { + ssize_t n = TEMP_FAILURE_RETRY(write(fd_, buffer_.get(), buffer_offset_)); + if (n < 0) { + error_ = SystemErrorCodeToString(errno); + owned_fd_.reset(); + fd_ = -1; + buffer_.reset(); + return false; + } + + buffer_offset_ = 0u; + return true; +} + +bool FileOutputStream::HadError() const { + return fd_ == -1; +} + +std::string FileOutputStream::GetError() const { + return error_; +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/FileStream.h b/tools/aapt2/io/FileStream.h new file mode 100644 index 000000000000..62d910f0b06a --- /dev/null +++ b/tools/aapt2/io/FileStream.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2017 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 AAPT_IO_FILESTREAM_H +#define AAPT_IO_FILESTREAM_H + +#include "io/Io.h" + +#include <memory> +#include <string> + +#include "android-base/macros.h" +#include "android-base/unique_fd.h" + +namespace aapt { +namespace io { + +constexpr size_t kDefaultBufferCapacity = 4096u; + +class FileInputStream : public InputStream { + public: + explicit FileInputStream(const std::string& path, + size_t buffer_capacity = kDefaultBufferCapacity); + + // Take ownership of `fd`. + explicit FileInputStream(int fd, size_t buffer_capacity = kDefaultBufferCapacity); + + bool Next(const void** data, size_t* size) override; + + void BackUp(size_t count) override; + + size_t ByteCount() const override; + + bool HadError() const override; + + std::string GetError() const override; + + private: + DISALLOW_COPY_AND_ASSIGN(FileInputStream); + + android::base::unique_fd fd_; + std::string error_; + std::unique_ptr<uint8_t[]> buffer_; + size_t buffer_capacity_ = 0u; + size_t buffer_offset_ = 0u; + size_t buffer_size_ = 0u; + size_t total_byte_count_ = 0u; +}; + +class FileOutputStream : public OutputStream { + public: + explicit FileOutputStream(const std::string& path, + size_t buffer_capacity = kDefaultBufferCapacity); + + // Does not take ownership of `fd`. + explicit FileOutputStream(int fd, size_t buffer_capacity = kDefaultBufferCapacity); + + // Takes ownership of `fd`. + explicit FileOutputStream(android::base::unique_fd fd, + size_t buffer_capacity = kDefaultBufferCapacity); + + ~FileOutputStream(); + + bool Next(void** data, size_t* size) override; + + // Immediately flushes out the contents of the buffer to disk. + bool Flush(); + + void BackUp(size_t count) override; + + size_t ByteCount() const override; + + bool HadError() const override; + + std::string GetError() const override; + + private: + DISALLOW_COPY_AND_ASSIGN(FileOutputStream); + + bool FlushImpl(); + + android::base::unique_fd owned_fd_; + int fd_; + std::string error_; + std::unique_ptr<uint8_t[]> buffer_; + size_t buffer_capacity_ = 0u; + size_t buffer_offset_ = 0u; + size_t total_byte_count_ = 0u; +}; + +} // namespace io +} // namespace aapt + +#endif // AAPT_IO_FILESTREAM_H diff --git a/tools/aapt2/io/FileInputStream_test.cpp b/tools/aapt2/io/FileStream_test.cpp index 7314ab7beeba..c0eaa8e08418 100644 --- a/tools/aapt2/io/FileInputStream_test.cpp +++ b/tools/aapt2/io/FileStream_test.cpp @@ -14,8 +14,9 @@ * limitations under the License. */ -#include "io/FileInputStream.h" +#include "io/FileStream.h" +#include "android-base/file.h" #include "android-base/macros.h" #include "android-base/test_utils.h" @@ -36,7 +37,7 @@ TEST(FileInputStreamTest, NextAndBackup) { lseek64(file.fd, 0, SEEK_SET); // Use a small buffer size so that we can call Next() a few times. - FileInputStream in(file.fd, 10u); + FileInputStream in(file.release(), 10u); ASSERT_FALSE(in.HadError()); EXPECT_THAT(in.ByteCount(), Eq(0u)); @@ -83,5 +84,45 @@ TEST(FileInputStreamTest, NextAndBackup) { EXPECT_FALSE(in.HadError()); } +TEST(FileOutputStreamTest, NextAndBackup) { + const std::string input = "this is a cool string"; + + TemporaryFile file; + + FileOutputStream out(file.fd, 10u); + ASSERT_FALSE(out.HadError()); + EXPECT_THAT(out.ByteCount(), Eq(0u)); + + char* buffer; + size_t size; + ASSERT_TRUE(out.Next(reinterpret_cast<void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(10u)); + ASSERT_THAT(buffer, NotNull()); + EXPECT_THAT(out.ByteCount(), Eq(10u)); + memcpy(buffer, input.c_str(), size); + + ASSERT_TRUE(out.Next(reinterpret_cast<void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(10u)); + ASSERT_THAT(buffer, NotNull()); + EXPECT_THAT(out.ByteCount(), Eq(20u)); + memcpy(buffer, input.c_str() + 10u, size); + + ASSERT_TRUE(out.Next(reinterpret_cast<void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(10u)); + ASSERT_THAT(buffer, NotNull()); + EXPECT_THAT(out.ByteCount(), Eq(30u)); + buffer[0] = input[20u]; + out.BackUp(size - 1); + EXPECT_THAT(out.ByteCount(), Eq(21u)); + + ASSERT_TRUE(out.Flush()); + + lseek64(file.fd, 0, SEEK_SET); + + std::string actual; + ASSERT_TRUE(android::base::ReadFdToString(file.fd, &actual)); + EXPECT_THAT(actual, StrEq(input)); +} + } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp index 027cbd041fa6..1387d2218ed4 100644 --- a/tools/aapt2/io/FileSystem.cpp +++ b/tools/aapt2/io/FileSystem.cpp @@ -20,11 +20,12 @@ #include "utils/FileMap.h" #include "Source.h" +#include "io/FileStream.h" #include "util/Files.h" #include "util/Maybe.h" #include "util/Util.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { namespace io { @@ -42,12 +43,20 @@ std::unique_ptr<IData> RegularFile::OpenAsData() { return {}; } -const Source& RegularFile::GetSource() const { return source_; } +std::unique_ptr<io::InputStream> RegularFile::OpenInputStream() { + return util::make_unique<FileInputStream>(source_.path); +} + +const Source& RegularFile::GetSource() const { + return source_; +} FileCollectionIterator::FileCollectionIterator(FileCollection* collection) : current_(collection->files_.begin()), end_(collection->files_.end()) {} -bool FileCollectionIterator::HasNext() { return current_ != end_; } +bool FileCollectionIterator::HasNext() { + return current_ != end_; +} IFile* FileCollectionIterator::Next() { IFile* result = current_->second.get(); diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h index dfd37172004b..6be8807735f1 100644 --- a/tools/aapt2/io/FileSystem.h +++ b/tools/aapt2/io/FileSystem.h @@ -24,17 +24,18 @@ namespace aapt { namespace io { -/** - * A regular file from the file system. Uses mmap to open the data. - */ +// A regular file from the file system. Uses mmap to open the data. class RegularFile : public IFile { public: explicit RegularFile(const Source& source); std::unique_ptr<IData> OpenAsData() override; + std::unique_ptr<io::InputStream> OpenInputStream() override; const Source& GetSource() const override; private: + DISALLOW_COPY_AND_ASSIGN(RegularFile); + Source source_; }; @@ -48,23 +49,26 @@ class FileCollectionIterator : public IFileCollectionIterator { io::IFile* Next() override; private: + DISALLOW_COPY_AND_ASSIGN(FileCollectionIterator); + std::map<std::string, std::unique_ptr<IFile>>::const_iterator current_, end_; }; -/** - * An IFileCollection representing the file system. - */ +// An IFileCollection representing the file system. class FileCollection : public IFileCollection { public: - /** - * Adds a file located at path. Returns the IFile representation of that file. - */ + FileCollection() = default; + + // Adds a file located at path. Returns the IFile representation of that file. IFile* InsertFile(const android::StringPiece& path); IFile* FindFile(const android::StringPiece& path) override; std::unique_ptr<IFileCollectionIterator> Iterator() override; private: + DISALLOW_COPY_AND_ASSIGN(FileCollection); + friend class FileCollectionIterator; + std::map<std::string, std::unique_ptr<IFile>> files_; }; diff --git a/tools/aapt2/io/Io.h b/tools/aapt2/io/Io.h index a656740b35da..e1df23a698f5 100644 --- a/tools/aapt2/io/Io.h +++ b/tools/aapt2/io/Io.h @@ -58,6 +58,12 @@ class InputStream { virtual bool HadError() const = 0; }; +// A sub-InputStream interface that knows the total size of its stream. +class KnownSizeInputStream : public InputStream { + public: + virtual size_t TotalSize() const = 0; +}; + // OutputStream interface that mimics protobuf's ZeroCopyOutputStream, // with added error handling methods to better report issues. class OutputStream { diff --git a/tools/aapt2/io/StringInputStream.cpp b/tools/aapt2/io/StringStream.cpp index 51a18a7d8a9f..4ca04a8c7477 100644 --- a/tools/aapt2/io/StringInputStream.cpp +++ b/tools/aapt2/io/StringStream.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io/StringInputStream.h" +#include "io/StringStream.h" using ::android::StringPiece; @@ -37,14 +37,64 @@ bool StringInputStream::Next(const void** data, size_t* size) { void StringInputStream::BackUp(size_t count) { if (count > offset_) { - count = offset_; + offset_ = 0u; + } else { + offset_ -= count; } - offset_ -= count; } size_t StringInputStream::ByteCount() const { return offset_; } +size_t StringInputStream::TotalSize() const { + return str_.size(); +} + +StringOutputStream::StringOutputStream(std::string* str, size_t buffer_capacity) + : str_(str), + buffer_capacity_(buffer_capacity), + buffer_offset_(0u), + buffer_(new char[buffer_capacity]) { +} + +StringOutputStream::~StringOutputStream() { + Flush(); +} + +bool StringOutputStream::Next(void** data, size_t* size) { + if (buffer_offset_ == buffer_capacity_) { + FlushImpl(); + } + + *data = buffer_.get() + buffer_offset_; + *size = buffer_capacity_ - buffer_offset_; + buffer_offset_ = buffer_capacity_; + return true; +} + +void StringOutputStream::BackUp(size_t count) { + if (count > buffer_offset_) { + buffer_offset_ = 0u; + } else { + buffer_offset_ -= count; + } +} + +size_t StringOutputStream::ByteCount() const { + return str_->size() + buffer_offset_; +} + +void StringOutputStream::Flush() { + if (buffer_offset_ != 0u) { + FlushImpl(); + } +} + +void StringOutputStream::FlushImpl() { + str_->append(buffer_.get(), buffer_offset_); + buffer_offset_ = 0u; +} + } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/StringInputStream.h b/tools/aapt2/io/StringStream.h index ff5b112ef274..f29890ab7ee5 100644 --- a/tools/aapt2/io/StringInputStream.h +++ b/tools/aapt2/io/StringStream.h @@ -14,18 +14,20 @@ * limitations under the License. */ -#ifndef AAPT_IO_STRINGINPUTSTREAM_H -#define AAPT_IO_STRINGINPUTSTREAM_H +#ifndef AAPT_IO_STRINGSTREAM_H +#define AAPT_IO_STRINGSTREAM_H #include "io/Io.h" +#include <memory> + #include "android-base/macros.h" #include "androidfw/StringPiece.h" namespace aapt { namespace io { -class StringInputStream : public InputStream { +class StringInputStream : public KnownSizeInputStream { public: explicit StringInputStream(const android::StringPiece& str); @@ -43,6 +45,8 @@ class StringInputStream : public InputStream { return {}; } + size_t TotalSize() const override; + private: DISALLOW_COPY_AND_ASSIGN(StringInputStream); @@ -50,7 +54,40 @@ class StringInputStream : public InputStream { size_t offset_; }; +class StringOutputStream : public OutputStream { + public: + explicit StringOutputStream(std::string* str, size_t buffer_capacity = 4096u); + + ~StringOutputStream(); + + bool Next(void** data, size_t* size) override; + + void BackUp(size_t count) override; + + void Flush(); + + size_t ByteCount() const override; + + inline bool HadError() const override { + return false; + } + + inline std::string GetError() const override { + return {}; + } + + private: + DISALLOW_COPY_AND_ASSIGN(StringOutputStream); + + void FlushImpl(); + + std::string* str_; + size_t buffer_capacity_; + size_t buffer_offset_; + std::unique_ptr<char[]> buffer_; +}; + } // namespace io } // namespace aapt -#endif // AAPT_IO_STRINGINPUTSTREAM_H +#endif // AAPT_IO_STRINGSTREAM_H diff --git a/tools/aapt2/io/StringInputStream_test.cpp b/tools/aapt2/io/StringStream_test.cpp index cc57bc498313..fb43fb773dd4 100644 --- a/tools/aapt2/io/StringInputStream_test.cpp +++ b/tools/aapt2/io/StringStream_test.cpp @@ -14,7 +14,9 @@ * limitations under the License. */ -#include "io/StringInputStream.h" +#include "io/StringStream.h" + +#include "io/Util.h" #include "test/Test.h" @@ -68,5 +70,16 @@ TEST(StringInputStreamTest, BackUp) { EXPECT_THAT(in.ByteCount(), Eq(input.size())); } +TEST(StringOutputStreamTest, NextAndBackUp) { + std::string input = "hello this is a string"; + std::string output; + + StringInputStream in(input); + StringOutputStream out(&output, 10u); + ASSERT_TRUE(Copy(&out, &in)); + out.Flush(); + EXPECT_THAT(output, StrEq(input)); +} + } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp index 15114e8dbf54..97516322c4cb 100644 --- a/tools/aapt2/io/Util.cpp +++ b/tools/aapt2/io/Util.cpp @@ -18,6 +18,9 @@ #include "google/protobuf/io/zero_copy_stream_impl_lite.h" +using ::android::StringPiece; +using ::google::protobuf::io::ZeroCopyOutputStream; + namespace aapt { namespace io { @@ -45,6 +48,12 @@ bool CopyFileToArchive(IAaptContext* context, io::IFile* file, const std::string return CopyInputStreamToArchive(context, data.get(), out_path, compression_flags, writer); } +bool CopyFileToArchivePreserveCompression(IAaptContext* context, io::IFile* file, + const std::string& out_path, IArchiveWriter* writer) { + uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u; + return CopyFileToArchive(context, file, out_path, compression_flags, writer); +} + bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::MessageLite* proto_msg, const std::string& out_path, uint32_t compression_flags, IArchiveWriter* writer) { @@ -91,5 +100,29 @@ bool Copy(OutputStream* out, InputStream* in) { return !in->HadError(); } +bool Copy(OutputStream* out, const StringPiece& in) { + const char* in_buffer = in.data(); + size_t in_len = in.size(); + while (in_len != 0) { + void* out_buffer; + size_t out_len; + if (!out->Next(&out_buffer, &out_len)) { + return false; + } + + const size_t bytes_to_copy = in_len < out_len ? in_len : out_len; + memcpy(out_buffer, in_buffer, bytes_to_copy); + out->BackUp(out_len - bytes_to_copy); + in_buffer += bytes_to_copy; + in_len -= bytes_to_copy; + } + return true; +} + +bool Copy(ZeroCopyOutputStream* out, InputStream* in) { + OutputStreamAdaptor adaptor(out); + return Copy(&adaptor, in); +} + } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h index ec1ddb8a07aa..b07fb5399313 100644 --- a/tools/aapt2/io/Util.h +++ b/tools/aapt2/io/Util.h @@ -21,7 +21,7 @@ #include "google/protobuf/message_lite.h" -#include "flatten/Archive.h" +#include "format/Archive.h" #include "io/File.h" #include "io/Io.h" #include "process/IResourceTableConsumer.h" @@ -35,6 +35,9 @@ bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std: bool CopyFileToArchive(IAaptContext* context, IFile* file, const std::string& out_path, uint32_t compression_flags, IArchiveWriter* writer); +bool CopyFileToArchivePreserveCompression(IAaptContext* context, IFile* file, + const std::string& out_path, IArchiveWriter* writer); + bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::MessageLite* proto_msg, const std::string& out_path, uint32_t compression_flags, IArchiveWriter* writer); @@ -42,6 +45,82 @@ bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::MessageLite* // Copies the data from in to out. Returns false if there was an error. // If there was an error, check the individual streams' HadError/GetError methods. bool Copy(OutputStream* out, InputStream* in); +bool Copy(OutputStream* out, const ::android::StringPiece& in); +bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, InputStream* in); + +class OutputStreamAdaptor : public io::OutputStream { + public: + explicit OutputStreamAdaptor(::google::protobuf::io::ZeroCopyOutputStream* out) : out_(out) { + } + + bool Next(void** data, size_t* size) override { + int out_size; + bool result = out_->Next(data, &out_size); + *size = static_cast<size_t>(out_size); + if (!result) { + error_ocurred_ = true; + } + return result; + } + + void BackUp(size_t count) override { + out_->BackUp(static_cast<int>(count)); + } + + size_t ByteCount() const override { + return static_cast<size_t>(out_->ByteCount()); + } + + bool HadError() const override { + return error_ocurred_; + } + + private: + DISALLOW_COPY_AND_ASSIGN(OutputStreamAdaptor); + + ::google::protobuf::io::ZeroCopyOutputStream* out_; + bool error_ocurred_ = false; +}; + +class ZeroCopyInputAdaptor : public ::google::protobuf::io::ZeroCopyInputStream { + public: + explicit ZeroCopyInputAdaptor(io::InputStream* in) : in_(in) { + } + + bool Next(const void** data, int* size) override { + size_t out_size; + bool result = in_->Next(data, &out_size); + *size = static_cast<int>(out_size); + return result; + } + + void BackUp(int count) override { + in_->BackUp(static_cast<size_t>(count)); + } + + bool Skip(int count) override { + const void* data; + int size; + while (Next(&data, &size)) { + if (size > count) { + BackUp(size - count); + return true; + } else { + count -= size; + } + } + return false; + } + + ::google::protobuf::int64 ByteCount() const override { + return static_cast<::google::protobuf::int64>(in_->ByteCount()); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ZeroCopyInputAdaptor); + + io::InputStream* in_; +}; } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp index 6494d2def748..269b6c5a12e1 100644 --- a/tools/aapt2/io/ZipArchive.cpp +++ b/tools/aapt2/io/ZipArchive.cpp @@ -22,7 +22,7 @@ #include "Source.h" #include "util/Util.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { namespace io { @@ -57,7 +57,13 @@ std::unique_ptr<IData> ZipFile::OpenAsData() { } } -const Source& ZipFile::GetSource() const { return source_; } +std::unique_ptr<io::InputStream> ZipFile::OpenInputStream() { + return OpenAsData(); +} + +const Source& ZipFile::GetSource() const { + return source_; +} bool ZipFile::WasCompressed() { return zip_entry_.method != kCompressStored; @@ -67,7 +73,9 @@ ZipFileCollectionIterator::ZipFileCollectionIterator( ZipFileCollection* collection) : current_(collection->files_.begin()), end_(collection->files_.end()) {} -bool ZipFileCollectionIterator::HasNext() { return current_ != end_; } +bool ZipFileCollectionIterator::HasNext() { + return current_ != end_; +} IFile* ZipFileCollectionIterator::Next() { IFile* result = current_->get(); diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h index 56c74e326e6c..8381259d77c5 100644 --- a/tools/aapt2/io/ZipArchive.h +++ b/tools/aapt2/io/ZipArchive.h @@ -28,23 +28,20 @@ namespace aapt { namespace io { -/** - * An IFile representing a file within a ZIP archive. If the file is compressed, - * it is uncompressed - * and copied into memory when opened. Otherwise it is mmapped from the ZIP - * archive. - */ +// An IFile representing a file within a ZIP archive. If the file is compressed, it is uncompressed +// and copied into memory when opened. Otherwise it is mmapped from the ZIP archive. class ZipFile : public IFile { public: - ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source); + ZipFile(::ZipArchiveHandle handle, const ::ZipEntry& entry, const Source& source); std::unique_ptr<IData> OpenAsData() override; + std::unique_ptr<io::InputStream> OpenInputStream() override; const Source& GetSource() const override; bool WasCompressed() override; private: - ZipArchiveHandle zip_handle_; - ZipEntry zip_entry_; + ::ZipArchiveHandle zip_handle_; + ::ZipEntry zip_entry_; Source source_; }; @@ -61,9 +58,7 @@ class ZipFileCollectionIterator : public IFileCollectionIterator { std::vector<std::unique_ptr<IFile>>::const_iterator current_, end_; }; -/** - * An IFileCollection that represents a ZIP archive and the entries within it. - */ +// An IFileCollection that represents a ZIP archive and the entries within it. class ZipFileCollection : public IFileCollection { public: static std::unique_ptr<ZipFileCollection> Create(const android::StringPiece& path, diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp index 1f83fa098d74..8d91b0098c1f 100644 --- a/tools/aapt2/java/AnnotationProcessor.cpp +++ b/tools/aapt2/java/AnnotationProcessor.cpp @@ -17,11 +17,13 @@ #include "java/AnnotationProcessor.h" #include <algorithm> +#include <array> #include "text/Unicode.h" #include "text/Utf8Iterator.h" #include "util/Util.h" +using ::aapt::text::Printer; using ::aapt::text::Utf8Iterator; using ::android::StringPiece; @@ -41,30 +43,54 @@ StringPiece AnnotationProcessor::ExtractFirstSentence(const StringPiece& comment return comment; } -void AnnotationProcessor::AppendCommentLine(std::string& comment) { +struct AnnotationRule { + enum : uint32_t { + kDeprecated = 0x01, + kSystemApi = 0x02, + kTestApi = 0x04, + }; + + StringPiece doc_str; + uint32_t bit_mask; + StringPiece annotation; +}; + +static std::array<AnnotationRule, 2> sAnnotationRules = {{ + {"@SystemApi", AnnotationRule::kSystemApi, "@android.annotation.SystemApi"}, + {"@TestApi", AnnotationRule::kTestApi, "@android.annotation.TestApi"}, +}}; + +void AnnotationProcessor::AppendCommentLine(std::string comment) { static const std::string sDeprecated = "@deprecated"; - static const std::string sSystemApi = "@SystemApi"; + // Treat deprecated specially, since we don't remove it from the source comment. if (comment.find(sDeprecated) != std::string::npos) { - annotation_bit_mask_ |= kDeprecated; + annotation_bit_mask_ |= AnnotationRule::kDeprecated; } - std::string::size_type idx = comment.find(sSystemApi); - if (idx != std::string::npos) { - annotation_bit_mask_ |= kSystemApi; - comment.erase(comment.begin() + idx, - comment.begin() + idx + sSystemApi.size()); + for (const AnnotationRule& rule : sAnnotationRules) { + std::string::size_type idx = comment.find(rule.doc_str.data()); + if (idx != std::string::npos) { + annotation_bit_mask_ |= rule.bit_mask; + comment.erase(comment.begin() + idx, comment.begin() + idx + rule.doc_str.size()); + } } - if (util::TrimWhitespace(comment).empty()) { + // Check if after removal of annotations the line is empty. + const StringPiece trimmed = util::TrimWhitespace(comment); + if (trimmed.empty()) { return; } + // If there was trimming to do, copy the string. + if (trimmed.size() != comment.size()) { + comment = trimmed.to_string(); + } + if (!has_comments_) { has_comments_ = true; comment_ << "/**"; } - comment_ << "\n * " << std::move(comment); } @@ -73,31 +99,34 @@ void AnnotationProcessor::AppendComment(const StringPiece& comment) { for (StringPiece line : util::Tokenize(comment, '\n')) { line = util::TrimWhitespace(line); if (!line.empty()) { - std::string lineCopy = line.to_string(); - AppendCommentLine(lineCopy); + AppendCommentLine(line.to_string()); } } } -void AnnotationProcessor::AppendNewLine() { comment_ << "\n *"; } +void AnnotationProcessor::AppendNewLine() { + if (has_comments_) { + comment_ << "\n *"; + } +} -void AnnotationProcessor::WriteToStream(std::ostream* out, - const StringPiece& prefix) const { +void AnnotationProcessor::Print(Printer* printer) const { if (has_comments_) { std::string result = comment_.str(); for (StringPiece line : util::Tokenize(result, '\n')) { - *out << prefix << line << "\n"; + printer->Println(line); } - *out << prefix << " */" - << "\n"; + printer->Println(" */"); } - if (annotation_bit_mask_ & kDeprecated) { - *out << prefix << "@Deprecated\n"; + if (annotation_bit_mask_ & AnnotationRule::kDeprecated) { + printer->Println("@Deprecated"); } - if (annotation_bit_mask_ & kSystemApi) { - *out << prefix << "@android.annotation.SystemApi\n"; + for (const AnnotationRule& rule : sAnnotationRules) { + if (annotation_bit_mask_ & rule.bit_mask) { + printer->Println(rule.annotation); + } } } diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h index a06eda0f9c5c..ae7bdb0c3ae2 100644 --- a/tools/aapt2/java/AnnotationProcessor.h +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -22,66 +22,57 @@ #include "androidfw/StringPiece.h" +#include "text/Printer.h" + namespace aapt { -/** - * Builds a JavaDoc comment from a set of XML comments. - * This will also look for instances of @SystemApi and convert them to - * actual Java annotations. - * - * Example: - * - * Input XML: - * - * <!-- This is meant to be hidden because - * It is system api. Also it is @deprecated - * @SystemApi - * --> - * - * Output JavaDoc: - * - * /\* - * * This is meant to be hidden because - * * It is system api. Also it is @deprecated - * *\/ - * - * Output Annotations: - * - * @Deprecated - * @android.annotation.SystemApi - * - */ +// Builds a JavaDoc comment from a set of XML comments. +// This will also look for instances of @SystemApi and convert them to +// actual Java annotations. +// +// Example: +// +// Input XML: +// +// <!-- This is meant to be hidden because +// It is system api. Also it is @deprecated +// @SystemApi +// --> +// +// Output JavaDoc: +// +// /** +// * This is meant to be hidden because +// * It is system api. Also it is @deprecated +// */ +// +// Output Annotations: +// +// @Deprecated +// @android.annotation.SystemApi class AnnotationProcessor { public: + // Extracts the first sentence of a comment. The algorithm selects the substring starting from + // the beginning of the string, and ending at the first '.' character that is followed by a + // whitespace character. If these requirements are not met, the whole string is returned. static android::StringPiece ExtractFirstSentence(const android::StringPiece& comment); - /** - * Adds more comments. Since resources can have various values with different - * configurations, - * we need to collect all the comments. - */ + // Adds more comments. Resources can have value definitions for various configurations, and + // each of the definitions may have comments that need to be processed. void AppendComment(const android::StringPiece& comment); void AppendNewLine(); - /** - * Writes the comments and annotations to the stream, with the given prefix - * before each line. - */ - void WriteToStream(std::ostream* out, const android::StringPiece& prefix) const; + // Writes the comments and annotations to the Printer. + void Print(text::Printer* printer) const; private: - enum : uint32_t { - kDeprecated = 0x01, - kSystemApi = 0x02, - }; - std::stringstream comment_; std::stringstream mAnnotations; bool has_comments_ = false; uint32_t annotation_bit_mask_ = 0; - void AppendCommentLine(std::string& line); + void AppendCommentLine(std::string line); }; } // namespace aapt diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp index 9ccac8888426..69f49c8b97c3 100644 --- a/tools/aapt2/java/AnnotationProcessor_test.cpp +++ b/tools/aapt2/java/AnnotationProcessor_test.cpp @@ -16,8 +16,12 @@ #include "java/AnnotationProcessor.h" +#include "io/StringStream.h" #include "test/Test.h" +#include "text/Printer.h" +using ::aapt::io::StringOutputStream; +using ::aapt::text::Printer; using ::testing::Eq; using ::testing::HasSubstr; using ::testing::Not; @@ -33,9 +37,11 @@ TEST(AnnotationProcessorTest, EmitsDeprecated) { AnnotationProcessor processor; processor.AppendComment(comment); - std::stringstream result; - processor.WriteToStream(&result, ""); - std::string annotations = result.str(); + std::string annotations; + StringOutputStream out(&annotations); + Printer printer(&out); + processor.Print(&printer); + out.Flush(); EXPECT_THAT(annotations, HasSubstr("@Deprecated")); } @@ -44,15 +50,32 @@ TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) { AnnotationProcessor processor; processor.AppendComment("@SystemApi This is a system API"); - std::stringstream result; - processor.WriteToStream(&result, ""); - std::string annotations = result.str(); + std::string annotations; + StringOutputStream out(&annotations); + Printer printer(&out); + processor.Print(&printer); + out.Flush(); EXPECT_THAT(annotations, HasSubstr("@android.annotation.SystemApi")); EXPECT_THAT(annotations, Not(HasSubstr("@SystemApi"))); EXPECT_THAT(annotations, HasSubstr("This is a system API")); } +TEST(AnnotationProcessorTest, EmitsTestApiAnnotationAndRemovesFromComment) { + AnnotationProcessor processor; + processor.AppendComment("@TestApi This is a test API"); + + std::string annotations; + StringOutputStream out(&annotations); + Printer printer(&out); + processor.Print(&printer); + out.Flush(); + + EXPECT_THAT(annotations, HasSubstr("@android.annotation.TestApi")); + EXPECT_THAT(annotations, Not(HasSubstr("@TestApi"))); + EXPECT_THAT(annotations, HasSubstr("This is a test API")); +} + TEST(AnnotationProcessor, ExtractsFirstSentence) { EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence("This is the only sentence"), Eq("This is the only sentence")); diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp index c139b73db296..b692ccf7e52d 100644 --- a/tools/aapt2/java/ClassDefinition.cpp +++ b/tools/aapt2/java/ClassDefinition.cpp @@ -18,25 +18,27 @@ #include "androidfw/StringPiece.h" -using android::StringPiece; +using ::aapt::text::Printer; +using ::android::StringPiece; namespace aapt { -void ClassMember::WriteToStream(const StringPiece& prefix, bool final, std::ostream* out) const { - processor_.WriteToStream(out, prefix); +void ClassMember::Print(bool /*final*/, Printer* printer) const { + processor_.Print(printer); } void MethodDefinition::AppendStatement(const StringPiece& statement) { statements_.push_back(statement.to_string()); } -void MethodDefinition::WriteToStream(const StringPiece& prefix, bool final, - std::ostream* out) const { - *out << prefix << signature_ << " {\n"; +void MethodDefinition::Print(bool final, Printer* printer) const { + printer->Print(signature_).Println(" {"); + printer->Indent(); for (const auto& statement : statements_) { - *out << prefix << " " << statement << "\n"; + printer->Println(statement); } - *out << prefix << "}"; + printer->Undent(); + printer->Print("}"); } ClassDefinition::Result ClassDefinition::AddMember(std::unique_ptr<ClassMember> member) { @@ -62,34 +64,32 @@ bool ClassDefinition::empty() const { return true; } -void ClassDefinition::WriteToStream(const StringPiece& prefix, bool final, - std::ostream* out) const { +void ClassDefinition::Print(bool final, Printer* printer) const { if (empty() && !create_if_empty_) { return; } - ClassMember::WriteToStream(prefix, final, out); + ClassMember::Print(final, printer); - *out << prefix << "public "; + printer->Print("public "); if (qualifier_ == ClassQualifier::kStatic) { - *out << "static "; + printer->Print("static "); } - *out << "final class " << name_ << " {\n"; - - std::string new_prefix = prefix.to_string(); - new_prefix.append(kIndent); + printer->Print("final class ").Print(name_).Println(" {"); + printer->Indent(); for (const std::unique_ptr<ClassMember>& member : ordered_members_) { // There can be nullptr members when a member is added to the ClassDefinition // and takes precedence over a previous member with the same name. The overridden member is // set to nullptr. if (member != nullptr) { - member->WriteToStream(new_prefix, final, out); - *out << "\n"; + member->Print(final, printer); + printer->Println(); } } - *out << prefix << "}"; + printer->Undent(); + printer->Print("}"); } constexpr static const char* sWarningHeader = @@ -100,12 +100,12 @@ constexpr static const char* sWarningHeader = " * should not be modified by hand.\n" " */\n\n"; -bool ClassDefinition::WriteJavaFile(const ClassDefinition* def, - const StringPiece& package, bool final, - std::ostream* out) { - *out << sWarningHeader << "package " << package << ";\n\n"; - def->WriteToStream("", final, out); - return bool(*out); +void ClassDefinition::WriteJavaFile(const ClassDefinition* def, const StringPiece& package, + bool final, io::OutputStream* out) { + Printer printer(out); + printer.Print(sWarningHeader).Print("package ").Print(package).Println(";"); + printer.Println(); + def->Print(final, &printer); } } // namespace aapt diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h index 28a3489e71a4..fb11266f1761 100644 --- a/tools/aapt2/java/ClassDefinition.h +++ b/tools/aapt2/java/ClassDefinition.h @@ -17,7 +17,6 @@ #ifndef AAPT_JAVA_CLASSDEFINITION_H #define AAPT_JAVA_CLASSDEFINITION_H -#include <ostream> #include <string> #include <unordered_map> #include <vector> @@ -27,6 +26,7 @@ #include "Resource.h" #include "java/AnnotationProcessor.h" +#include "text/Printer.h" #include "util/Util.h" namespace aapt { @@ -47,11 +47,10 @@ class ClassMember { virtual const std::string& GetName() const = 0; - // Writes the class member to the out stream. Subclasses should derive this method + // Writes the class member to the Printer. Subclasses should derive this method // to write their own data. Call this base method from the subclass to write out // this member's comments/annotations. - virtual void WriteToStream(const android::StringPiece& prefix, bool final, - std::ostream* out) const; + virtual void Print(bool final, text::Printer* printer) const; private: AnnotationProcessor processor_; @@ -71,11 +70,16 @@ class PrimitiveMember : public ClassMember { return name_; } - void WriteToStream(const android::StringPiece& prefix, bool final, - std::ostream* out) const override { - ClassMember::WriteToStream(prefix, final, out); - *out << prefix << "public static " << (final ? "final " : "") << "int " << name_ << "=" << val_ - << ";"; + void Print(bool final, text::Printer* printer) const override { + using std::to_string; + + ClassMember::Print(final, printer); + + printer->Print("public static "); + if (final) { + printer->Print("final "); + } + printer->Print("int ").Print(name_).Print("=").Print(to_string(val_)).Print(";"); } private: @@ -100,12 +104,14 @@ class PrimitiveMember<std::string> : public ClassMember { return name_; } - void WriteToStream(const android::StringPiece& prefix, bool final, - std::ostream* out) const override { - ClassMember::WriteToStream(prefix, final, out); + void Print(bool final, text::Printer* printer) const override { + ClassMember::Print(final, printer); - *out << prefix << "public static " << (final ? "final " : "") << "String " - << name_ << "=\"" << val_ << "\";"; + printer->Print("public static "); + if (final) { + printer->Print("final "); + } + printer->Print("String ").Print(name_).Print("=\"").Print(val_).Print("\";"); } private: @@ -136,25 +142,27 @@ class PrimitiveArrayMember : public ClassMember { return name_; } - void WriteToStream(const android::StringPiece& prefix, bool final, - std::ostream* out) const override { - ClassMember::WriteToStream(prefix, final, out); + void Print(bool final, text::Printer* printer) const override { + ClassMember::Print(final, printer); - *out << prefix << "public static final int[] " << name_ << "={"; + printer->Print("public static final int[] ").Print(name_).Print("={"); + printer->Indent(); const auto begin = elements_.begin(); const auto end = elements_.end(); for (auto current = begin; current != end; ++current) { if (std::distance(begin, current) % kAttribsPerLine == 0) { - *out << "\n" << prefix << kIndent << kIndent; + printer->Println(); } - *out << *current; + printer->Print(to_string(*current)); if (std::distance(current, end) > 1) { - *out << ", "; + printer->Print(", "); } } - *out << "\n" << prefix << kIndent << "};"; + printer->Println(); + printer->Undent(); + printer->Print("};"); } private: @@ -187,8 +195,7 @@ class MethodDefinition : public ClassMember { return false; } - void WriteToStream(const android::StringPiece& prefix, bool final, - std::ostream* out) const override; + void Print(bool final, text::Printer* printer) const override; private: DISALLOW_COPY_AND_ASSIGN(MethodDefinition); @@ -201,8 +208,8 @@ enum class ClassQualifier { kNone, kStatic }; class ClassDefinition : public ClassMember { public: - static bool WriteJavaFile(const ClassDefinition* def, const android::StringPiece& package, - bool final, std::ostream* out); + static void WriteJavaFile(const ClassDefinition* def, const android::StringPiece& package, + bool final, io::OutputStream* out); ClassDefinition(const android::StringPiece& name, ClassQualifier qualifier, bool createIfEmpty) : name_(name.to_string()), qualifier_(qualifier), create_if_empty_(createIfEmpty) {} @@ -220,8 +227,7 @@ class ClassDefinition : public ClassMember { return name_; } - void WriteToStream(const android::StringPiece& prefix, bool final, - std::ostream* out) const override; + void Print(bool final, text::Printer* printer) const override; private: DISALLOW_COPY_AND_ASSIGN(ClassDefinition); diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index 44fa0f19a0e5..8c8c2549609a 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -37,8 +37,10 @@ #include "java/ClassDefinition.h" #include "process/SymbolTable.h" -using android::StringPiece; -using android::base::StringPrintf; +using ::aapt::io::OutputStream; +using ::aapt::text::Printer; +using ::android::StringPiece; +using ::android::base::StringPrintf; namespace aapt { @@ -61,7 +63,7 @@ static bool IsValidSymbol(const StringPiece& symbol) { // Java symbols can not contain . or -, but those are valid in a resource name. // Replace those with '_'. -static std::string TransformToFieldName(const StringPiece& symbol) { +std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) { std::string output = symbol.to_string(); for (char& c : output) { if (c == '.' || c == '-') { @@ -89,9 +91,9 @@ static std::string TransformNestedAttr(const ResourceNameRef& attr_name, // the package. if (!attr_name.package.empty() && package_name_to_generate != attr_name.package) { - output += "_" + TransformToFieldName(attr_name.package); + output += "_" + JavaClassGenerator::TransformToFieldName(attr_name.package); } - output += "_" + TransformToFieldName(attr_name.entry); + output += "_" + JavaClassGenerator::TransformToFieldName(attr_name.entry); return output; } @@ -230,7 +232,7 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res const StringPiece& package_name_to_generate, ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method, - std::ostream* out_r_txt) { + Printer* r_txt_printer) { const std::string array_field_name = TransformToFieldName(name.entry); std::unique_ptr<ResourceArrayMember> array_def = util::make_unique<ResourceArrayMember>(array_field_name); @@ -270,7 +272,7 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res // Build the JavaDoc comment for the Styleable array. This has references to child attributes // and what possible values can be used for them. const size_t attr_count = sorted_attributes.size(); - if (attr_count > 0) { + if (out_class_def != nullptr && attr_count > 0) { std::stringstream styleable_comment; if (!styleable.GetComment().empty()) { styleable_comment << styleable.GetComment() << "\n"; @@ -323,8 +325,8 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res array_def->GetCommentBuilder()->AppendComment(styleable_comment.str()); } - if (out_r_txt != nullptr) { - *out_r_txt << "int[] styleable " << array_field_name << " {"; + if (r_txt_printer != nullptr) { + r_txt_printer->Print("int[] styleable ").Print(array_field_name).Print(" {"); } // Add the ResourceIds to the array member. @@ -332,16 +334,16 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res const ResourceId id = sorted_attributes[i].attr_ref->id.value_or_default(ResourceId(0)); array_def->AddElement(id); - if (out_r_txt != nullptr) { + if (r_txt_printer != nullptr) { if (i != 0) { - *out_r_txt << ","; + r_txt_printer->Print(","); } - *out_r_txt << " " << id; + r_txt_printer->Print(" ").Print(id.to_string()); } } - if (out_r_txt != nullptr) { - *out_r_txt << " }\n"; + if (r_txt_printer != nullptr) { + r_txt_printer->Println(" }"); } // Add the Styleable array to the Styleable class. @@ -354,54 +356,56 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res continue; } - StringPiece comment = styleable_attr.attr_ref->GetComment(); - if (styleable_attr.symbol.value().attribute && comment.empty()) { - comment = styleable_attr.symbol.value().attribute->GetComment(); - } + if (out_class_def != nullptr) { + StringPiece comment = styleable_attr.attr_ref->GetComment(); + if (styleable_attr.symbol.value().attribute && comment.empty()) { + comment = styleable_attr.symbol.value().attribute->GetComment(); + } - if (comment.contains("@removed")) { - // Removed attributes are public but hidden from the documentation, so - // don't emit them as part of the class documentation. - continue; - } + if (comment.contains("@removed")) { + // Removed attributes are public but hidden from the documentation, so + // don't emit them as part of the class documentation. + continue; + } - const ResourceName& attr_name = styleable_attr.attr_ref->name.value(); + const ResourceName& attr_name = styleable_attr.attr_ref->name.value(); - StringPiece package_name = attr_name.package; - if (package_name.empty()) { - package_name = context_->GetCompilationPackage(); - } + StringPiece package_name = attr_name.package; + if (package_name.empty()) { + package_name = context_->GetCompilationPackage(); + } - std::unique_ptr<IntMember> index_member = util::make_unique<IntMember>( - sorted_attributes[i].field_name, static_cast<uint32_t>(i)); + std::unique_ptr<IntMember> index_member = + util::make_unique<IntMember>(sorted_attributes[i].field_name, static_cast<uint32_t>(i)); + + AnnotationProcessor* attr_processor = index_member->GetCommentBuilder(); + + if (!comment.empty()) { + attr_processor->AppendComment("<p>\n@attr description"); + attr_processor->AppendComment(comment); + } else { + std::stringstream default_comment; + default_comment << "<p>This symbol is the offset where the " + << "{@link " << package_name << ".R.attr#" + << TransformToFieldName(attr_name.entry) << "}\n" + << "attribute's value can be found in the " + << "{@link #" << array_field_name << "} array."; + attr_processor->AppendComment(default_comment.str()); + } - AnnotationProcessor* attr_processor = index_member->GetCommentBuilder(); + attr_processor->AppendNewLine(); + AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get()); + attr_processor->AppendNewLine(); + attr_processor->AppendComment( + StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data())); - if (!comment.empty()) { - attr_processor->AppendComment("<p>\n@attr description"); - attr_processor->AppendComment(comment); - } else { - std::stringstream default_comment; - default_comment << "<p>This symbol is the offset where the " - << "{@link " << package_name << ".R.attr#" - << TransformToFieldName(attr_name.entry) << "}\n" - << "attribute's value can be found in the " - << "{@link #" << array_field_name << "} array."; - attr_processor->AppendComment(default_comment.str()); + out_class_def->AddMember(std::move(index_member)); } - attr_processor->AppendNewLine(); - AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get()); - attr_processor->AppendNewLine(); - attr_processor->AppendComment( - StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data())); - - if (out_r_txt != nullptr) { - *out_r_txt << StringPrintf("int styleable %s %d\n", sorted_attributes[i].field_name.data(), - (int)i); + if (r_txt_printer != nullptr) { + r_txt_printer->Println( + StringPrintf("int styleable %s %zd", sorted_attributes[i].field_name.c_str(), i)); } - - out_class_def->AddMember(std::move(index_member)); } // If there is a rewrite method to generate, add the statements that rewrite package IDs @@ -422,46 +426,55 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id, const ResourceEntry& entry, ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method, - std::ostream* out_r_txt) { + text::Printer* r_txt_printer) { ResourceId real_id = id; if (context_->GetMinSdkVersion() < SDK_O && name.type == ResourceType::kId && id.package_id() > kAppPackageId) { + // Workaround for feature splits using package IDs > 0x7F. + // See b/37498913. real_id = ResourceId(kAppPackageId, id.package_id(), id.entry_id()); } const std::string field_name = TransformToFieldName(name.entry); - std::unique_ptr<ResourceMember> resource_member = - util::make_unique<ResourceMember>(field_name, real_id); + if (out_class_def != nullptr) { + std::unique_ptr<ResourceMember> resource_member = + util::make_unique<ResourceMember>(field_name, real_id); - // Build the comments and annotations for this entry. - AnnotationProcessor* processor = resource_member->GetCommentBuilder(); + // Build the comments and annotations for this entry. + AnnotationProcessor* processor = resource_member->GetCommentBuilder(); - // Add the comments from any <public> tags. - if (entry.symbol_status.state != SymbolState::kUndefined) { - processor->AppendComment(entry.symbol_status.comment); - } + // Add the comments from any <public> tags. + if (entry.symbol_status.state != SymbolState::kUndefined) { + processor->AppendComment(entry.symbol_status.comment); + } - // Add the comments from all configurations of this entry. - for (const auto& config_value : entry.values) { - processor->AppendComment(config_value->value->GetComment()); - } + // Add the comments from all configurations of this entry. + for (const auto& config_value : entry.values) { + processor->AppendComment(config_value->value->GetComment()); + } - // If this is an Attribute, append the format Javadoc. - if (!entry.values.empty()) { - if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) { - // We list out the available values for the given attribute. - AddAttributeFormatDoc(processor, attr); + // If this is an Attribute, append the format Javadoc. + if (!entry.values.empty()) { + if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) { + // We list out the available values for the given attribute. + AddAttributeFormatDoc(processor, attr); + } } - } - out_class_def->AddMember(std::move(resource_member)); + out_class_def->AddMember(std::move(resource_member)); + } - if (out_r_txt != nullptr) { - *out_r_txt << "int " << name.type << " " << field_name << " " << real_id << "\n"; + if (r_txt_printer != nullptr) { + r_txt_printer->Print("int ") + .Print(to_string(name.type)) + .Print(" ") + .Print(field_name) + .Print(" ") + .Println(real_id.to_string()); } if (out_rewrite_method != nullptr) { - const StringPiece& type_str = ToString(name.type); + const StringPiece& type_str = to_string(name.type); out_rewrite_method->AppendStatement(StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | (p << 24);", type_str.data(), field_name.data(), type_str.data(), field_name.data())); @@ -480,7 +493,7 @@ Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& packa if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) { // The entry name was mangled, and we successfully unmangled it. // Check that we want to emit this symbol. - if (package_name != unmangled_package) { + if (package_name_to_generate != unmangled_package) { // Skip the entry if it doesn't belong to the package we're writing. return {}; } @@ -497,7 +510,7 @@ bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate const ResourceTableType& type, ClassDefinition* out_type_class_def, MethodDefinition* out_rewrite_method_def, - std::ostream* out_r_txt) { + Printer* r_txt_printer) { for (const auto& entry : type.entries) { const Maybe<std::string> unmangled_name = UnmangleResource(package.name, package_name_to_generate, *entry); @@ -532,18 +545,18 @@ bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate static_cast<const Styleable*>(entry->values.front()->value.get()); ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def, - out_rewrite_method_def, out_r_txt); + out_rewrite_method_def, r_txt_printer); } else { ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def, - out_r_txt); + r_txt_printer); } } return true; } -bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, std::ostream* out, - std::ostream* out_r_txt) { - return Generate(package_name_to_generate, package_name_to_generate, out); +bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out, + OutputStream* out_r_txt) { + return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt); } static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations, @@ -556,13 +569,18 @@ static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations } bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, - const StringPiece& out_package_name, std::ostream* out, - std::ostream* out_r_txt) { + const StringPiece& out_package_name, OutputStream* out, + OutputStream* out_r_txt) { ClassDefinition r_class("R", ClassQualifier::kNone, true); std::unique_ptr<MethodDefinition> rewrite_method; + std::unique_ptr<Printer> r_txt_printer; + if (out_r_txt != nullptr) { + r_txt_printer = util::make_unique<Printer>(out_r_txt); + } + // Generate an onResourcesLoaded() callback if requested. - if (options_.rewrite_callback_options) { + if (out != nullptr && options_.rewrite_callback_options) { rewrite_method = util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)"); for (const std::string& package_to_callback : @@ -579,15 +597,18 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, continue; } - // Stay consistent with AAPT and generate an empty type class if the R class - // is public. + // Stay consistent with AAPT and generate an empty type class if the R class is public. const bool force_creation_if_empty = (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic); - std::unique_ptr<ClassDefinition> class_def = util::make_unique<ClassDefinition>( - ToString(type->type), ClassQualifier::kStatic, force_creation_if_empty); + std::unique_ptr<ClassDefinition> class_def; + if (out != nullptr) { + class_def = util::make_unique<ClassDefinition>( + to_string(type->type), ClassQualifier::kStatic, force_creation_if_empty); + } + if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(), - rewrite_method.get(), out_r_txt)) { + rewrite_method.get(), r_txt_printer.get())) { return false; } @@ -596,22 +617,23 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate); if (priv_type) { if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(), - rewrite_method.get(), out_r_txt)) { + rewrite_method.get(), r_txt_printer.get())) { return false; } } } - if (type->type == ResourceType::kStyleable && + if (out != nullptr && type->type == ResourceType::kStyleable && options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) { // When generating a public R class, we don't want Styleable to be part // of the API. It is only emitted for documentation purposes. class_def->GetCommentBuilder()->AppendComment("@doconly"); } - AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder()); - - r_class.AddMember(std::move(class_def)); + if (out != nullptr) { + AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder()); + r_class.AddMember(std::move(class_def)); + } } } @@ -619,23 +641,10 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, r_class.AddMember(std::move(rewrite_method)); } - AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder()); - - if (!ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out)) { - return false; - } - - out->flush(); - - if (out_r_txt != nullptr) { - out_r_txt->flush(); - - if (!*out_r_txt) { - error_ = android::base::SystemErrorCodeToString(errno); - return false; - } + if (out != nullptr) { + AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder()); + ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out); } - return true; } diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h index 18746ffc5a0a..4992f077c566 100644 --- a/tools/aapt2/java/JavaClassGenerator.h +++ b/tools/aapt2/java/JavaClassGenerator.h @@ -17,15 +17,16 @@ #ifndef AAPT_JAVA_CLASS_GENERATOR_H #define AAPT_JAVA_CLASS_GENERATOR_H -#include <ostream> #include <string> #include "androidfw/StringPiece.h" #include "ResourceTable.h" #include "ResourceValues.h" +#include "io/Io.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" +#include "text/Printer.h" namespace aapt { @@ -69,14 +70,16 @@ class JavaClassGenerator { // All symbols technically belong to a single package, but linked libraries will // have their names mangled, denoting that they came from a different package. // We need to generate these symbols in a separate file. Returns true on success. - bool Generate(const android::StringPiece& package_name_to_generate, std::ostream* out, - std::ostream* out_r_txt = nullptr); + bool Generate(const android::StringPiece& package_name_to_generate, io::OutputStream* out, + io::OutputStream* out_r_txt = nullptr); bool Generate(const android::StringPiece& package_name_to_generate, - const android::StringPiece& output_package_name, std::ostream* out, - std::ostream* out_r_txt = nullptr); + const android::StringPiece& output_package_name, io::OutputStream* out, + io::OutputStream* out_r_txt = nullptr); - const std::string& getError() const; + const std::string& GetError() const; + + static std::string TransformToFieldName(const android::StringPiece& symbol); private: bool SkipSymbol(SymbolState state); @@ -91,13 +94,13 @@ class JavaClassGenerator { bool ProcessType(const android::StringPiece& package_name_to_generate, const ResourceTablePackage& package, const ResourceTableType& type, ClassDefinition* out_type_class_def, MethodDefinition* out_rewrite_method_def, - std::ostream* out_r_txt); + text::Printer* r_txt_printer); // Writes a resource to the R.java file, optionally writing out a rewrite rule for its package // ID if `out_rewrite_method` is not nullptr. void ProcessResource(const ResourceNameRef& name, const ResourceId& id, const ResourceEntry& entry, ClassDefinition* out_class_def, - MethodDefinition* out_rewrite_method, std::ostream* out_r_txt); + MethodDefinition* out_rewrite_method, text::Printer* r_txt_printer); // Writes a styleable resource to the R.java file, optionally writing out a rewrite rule for // its package ID if `out_rewrite_method` is not nullptr. @@ -106,7 +109,7 @@ class JavaClassGenerator { const Styleable& styleable, const android::StringPiece& package_name_to_generate, ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method, - std::ostream* out_r_txt); + text::Printer* r_txt_printer); IAaptContext* context_; ResourceTable* table_; @@ -114,7 +117,7 @@ class JavaClassGenerator { std::string error_; }; -inline const std::string& JavaClassGenerator::getError() const { +inline const std::string& JavaClassGenerator::GetError() const { return error_; } diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp index 84bf04134ad9..02f4cb14eb41 100644 --- a/tools/aapt2/java/JavaClassGenerator_test.cpp +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -16,15 +16,18 @@ #include "java/JavaClassGenerator.h" -#include <sstream> #include <string> +#include "io/StringStream.h" #include "test/Test.h" #include "util/Util.h" -using android::StringPiece; +using ::aapt::io::StringOutputStream; +using ::android::StringPiece; +using ::testing::HasSubstr; using ::testing::Lt; using ::testing::Ne; +using ::testing::Not; namespace aapt { @@ -43,7 +46,8 @@ TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) { .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); - std::stringstream out; + std::string result; + StringOutputStream out(&result); EXPECT_FALSE(generator.Generate("android", &out)); } @@ -54,34 +58,27 @@ TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { .AddSimple("android:id/hey-man", ResourceId(0x01020000)) .AddValue("android:attr/cool.attr", ResourceId(0x01010000), test::AttributeBuilder(false).Build()) - .AddValue( - "android:styleable/hey.dude", ResourceId(0x01030000), - test::StyleableBuilder() - .AddItem("android:attr/cool.attr", ResourceId(0x01010000)) - .Build()) + .AddValue("android:styleable/hey.dude", ResourceId(0x01030000), + test::StyleableBuilder() + .AddItem("android:attr/cool.attr", ResourceId(0x01010000)) + .Build()) .Build(); std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); - std::stringstream out; + std::string output; + StringOutputStream out(&output); EXPECT_TRUE(generator.Generate("android", &out)); + out.Flush(); - std::string output = out.str(); - - EXPECT_NE(std::string::npos, - output.find("public static final int hey_man=0x01020000;")); - - EXPECT_NE(std::string::npos, - output.find("public static final int[] hey_dude={")); - - EXPECT_NE(std::string::npos, - output.find("public static final int hey_dude_cool_attr=0;")); + EXPECT_THAT(output, HasSubstr("public static final int hey_man=0x01020000;")); + EXPECT_THAT(output, HasSubstr("public static final int[] hey_dude={")); + EXPECT_THAT(output, HasSubstr("public static final int hey_dude_cool_attr=0;")); } TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) { @@ -94,20 +91,20 @@ TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); - std::stringstream out; + + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", "com.android.internal", &out)); + out.Flush(); - std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("package com.android.internal;")); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_EQ(std::string::npos, output.find("two")); - EXPECT_EQ(std::string::npos, output.find("com_foo$two")); + EXPECT_THAT(output, HasSubstr("package com.android.internal;")); + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, Not(HasSubstr("two"))); + EXPECT_THAT(output, Not(HasSubstr("com_foo$two"))); } TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) { @@ -120,18 +117,18 @@ TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); - std::stringstream out; + + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); + out.Flush(); - std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("public static final class attr")); - EXPECT_EQ(std::string::npos, - output.find("public static final class ^attr-private")); + EXPECT_THAT(output, HasSubstr("public static final class attr")); + EXPECT_THAT(output, Not(HasSubstr("public static final class ^attr-private"))); } TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { @@ -142,16 +139,13 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { .AddSimple("android:id/one", ResourceId(0x01020000)) .AddSimple("android:id/two", ResourceId(0x01020001)) .AddSimple("android:id/three", ResourceId(0x01020002)) - .SetSymbolState("android:id/one", ResourceId(0x01020000), - SymbolState::kPublic) - .SetSymbolState("android:id/two", ResourceId(0x01020001), - SymbolState::kPrivate) + .SetSymbolState("android:id/one", ResourceId(0x01020000), SymbolState::kPublic) + .SetSymbolState("android:id/two", ResourceId(0x01020001), SymbolState::kPrivate) .Build(); std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); @@ -159,40 +153,40 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; { JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); - std::string output = out.str(); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_EQ(std::string::npos, output.find("two")); - EXPECT_EQ(std::string::npos, output.find("three")); + out.Flush(); + + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, Not(HasSubstr("two"))); + EXPECT_THAT(output, Not(HasSubstr("three"))); } options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; { JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); - std::string output = out.str(); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_NE(std::string::npos, - output.find("public static final int two=0x01020001;")); - EXPECT_EQ(std::string::npos, output.find("three")); + out.Flush(); + + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, HasSubstr("public static final int two=0x01020001;")); + EXPECT_THAT(output, Not(HasSubstr("three"))); } options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; { JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); - std::string output = out.str(); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_NE(std::string::npos, - output.find("public static final int two=0x01020001;")); - EXPECT_NE(std::string::npos, - output.find("public static final int three=0x01020002;")); + out.Flush(); + + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, HasSubstr("public static final int two=0x01020001;")); + EXPECT_THAT(output, HasSubstr("public static final int three=0x01020002;")); } } @@ -248,18 +242,18 @@ TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); - std::stringstream out; + std::string output; + StringOutputStream out(&output); EXPECT_TRUE(generator.Generate("android", &out)); + out.Flush(); - std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("int foo_bar=")); - EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar=")); + EXPECT_THAT(output, HasSubstr("int foo_bar=")); + EXPECT_THAT(output, HasSubstr("int foo_com_lib_bar=")); } TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) { @@ -273,24 +267,24 @@ TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); - std::stringstream out; + + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); - std::string actual = out.str(); + out.Flush(); - const char* expectedText = + const char* expected_text = R"EOF(/** * This is a comment * @deprecated */ @Deprecated public static final int foo=0x01010000;)EOF"; - - EXPECT_NE(std::string::npos, actual.find(expectedText)); + EXPECT_THAT(output, HasSubstr(expected_text)); } TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {} @@ -300,8 +294,7 @@ TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) attr.SetComment(StringPiece("This is an attribute")); Styleable styleable; - styleable.entries.push_back( - Reference(test::ParseNameOrDie("android:attr/one"))); + styleable.entries.push_back(Reference(test::ParseNameOrDie("android:attr/one"))); styleable.SetComment(StringPiece("This is a styleable")); std::unique_ptr<ResourceTable> table = @@ -314,21 +307,22 @@ TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGeneratorOptions options; options.use_final = false; JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; + + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); - std::string actual = out.str(); + out.Flush(); - EXPECT_NE(std::string::npos, actual.find("attr name android:one")); - EXPECT_NE(std::string::npos, actual.find("attr description")); - EXPECT_NE(std::string::npos, actual.find(attr.GetComment().data())); - EXPECT_NE(std::string::npos, actual.find(styleable.GetComment().data())); + EXPECT_THAT(output, HasSubstr("attr name android:one")); + EXPECT_THAT(output, HasSubstr("attr description")); + EXPECT_THAT(output, HasSubstr(attr.GetComment())); + EXPECT_THAT(output, HasSubstr(styleable.GetComment())); } TEST(JavaClassGeneratorTest, StyleableAndIndicesAreColocated) { @@ -355,9 +349,11 @@ TEST(JavaClassGeneratorTest, StyleableAndIndicesAreColocated) { JavaClassGeneratorOptions options; JavaClassGenerator generator(context.get(), table.get(), {}); - std::stringstream out; + + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); - std::string output = out.str(); + out.Flush(); std::string::size_type actionbar_pos = output.find("int[] ActionBar"); ASSERT_THAT(actionbar_pos, Ne(std::string::npos)); @@ -390,26 +386,27 @@ TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGeneratorOptions options; options.use_final = false; JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; + + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); - std::string actual = out.str(); + out.Flush(); - EXPECT_EQ(std::string::npos, actual.find("@attr name android:one")); - EXPECT_EQ(std::string::npos, actual.find("@attr description")); + EXPECT_THAT(output, Not(HasSubstr("@attr name android:one"))); + EXPECT_THAT(output, Not(HasSubstr("@attr description"))); // We should find @removed only in the attribute javadoc and not anywhere else - // (i.e. the class - // javadoc). - const size_t pos = actual.find("removed"); - EXPECT_NE(std::string::npos, pos); - EXPECT_EQ(std::string::npos, actual.find("removed", pos + 1)); + // (i.e. the class javadoc). + const std::string kRemoved("removed"); + ASSERT_THAT(output, HasSubstr(kRemoved)); + std::string after_first_match = output.substr(output.find(kRemoved) + kRemoved.size()); + EXPECT_THAT(after_first_match, Not(HasSubstr(kRemoved))); } TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary) { @@ -430,19 +427,17 @@ TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary) JavaClassGeneratorOptions options; options.use_final = false; - options.rewrite_callback_options = OnResourcesLoadedCallbackOptions{ - {"com.foo", "com.boo"}, - }; + options.rewrite_callback_options = OnResourcesLoadedCallbackOptions{{"com.foo", "com.boo"}}; JavaClassGenerator generator(context.get(), table.get(), options); - std::stringstream out; + std::string output; + StringOutputStream out(&output); ASSERT_TRUE(generator.Generate("android", &out)); + out.Flush(); - std::string actual = out.str(); - - EXPECT_NE(std::string::npos, actual.find("void onResourcesLoaded")); - EXPECT_NE(std::string::npos, actual.find("com.foo.R.onResourcesLoaded")); - EXPECT_NE(std::string::npos, actual.find("com.boo.R.onResourcesLoaded")); + EXPECT_THAT(output, HasSubstr("void onResourcesLoaded")); + EXPECT_THAT(output, HasSubstr("com.foo.R.onResourcesLoaded")); + EXPECT_THAT(output, HasSubstr("com.boo.R.onResourcesLoaded")); } } // namespace aapt diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp index 8981e0718be0..c4b36176aa71 100644 --- a/tools/aapt2/java/ManifestClassGenerator.cpp +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -25,7 +25,7 @@ #include "util/Maybe.h" #include "xml/XmlDom.h" -using android::StringPiece; +using ::android::StringPiece; using ::aapt::text::IsJavaIdentifier; namespace aapt { @@ -50,17 +50,16 @@ static Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag, const Source return result; } -static bool WriteSymbol(const Source& source, IDiagnostics* diag, - xml::Element* el, ClassDefinition* class_def) { +static bool WriteSymbol(const Source& source, IDiagnostics* diag, xml::Element* el, + ClassDefinition* class_def) { xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name"); if (!attr) { - diag->Error(DiagMessage(source) << "<" << el->name - << "> must define 'android:name'"); + diag->Error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'"); return false; } - Maybe<StringPiece> result = ExtractJavaIdentifier( - diag, source.WithLine(el->line_number), attr->value); + Maybe<StringPiece> result = + ExtractJavaIdentifier(diag, source.WithLine(el->line_number), attr->value); if (!result) { return false; } @@ -76,8 +75,7 @@ static bool WriteSymbol(const Source& source, IDiagnostics* diag, return true; } -std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, - xml::XmlResource* res) { +std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, xml::XmlResource* res) { xml::Element* el = xml::FindRootElement(res->root.get()); if (!el) { diag->Error(DiagMessage(res->file.source) << "no root tag defined"); @@ -85,8 +83,7 @@ std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, } if (el->name != "manifest" && !el->namespace_uri.empty()) { - diag->Error(DiagMessage(res->file.source) - << "no <manifest> root tag defined"); + diag->Error(DiagMessage(res->file.source) << "no <manifest> root tag defined"); return {}; } @@ -100,11 +97,9 @@ std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, for (xml::Element* child_el : children) { if (child_el->namespace_uri.empty()) { if (child_el->name == "permission") { - error |= !WriteSymbol(res->file.source, diag, child_el, - permission_class.get()); + error |= !WriteSymbol(res->file.source, diag, child_el, permission_class.get()); } else if (child_el->name == "permission-group") { - error |= !WriteSymbol(res->file.source, diag, child_el, - permission_group_class.get()); + error |= !WriteSymbol(res->file.source, diag, child_el, permission_group_class.get()); } } } diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h index b12202a8d137..3f6645facaa2 100644 --- a/tools/aapt2/java/ManifestClassGenerator.h +++ b/tools/aapt2/java/ManifestClassGenerator.h @@ -23,8 +23,7 @@ namespace aapt { -std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, - xml::XmlResource* res); +std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, xml::XmlResource* res); } // namespace aapt diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp index 44b6a1ffd5ae..c324238d3ecb 100644 --- a/tools/aapt2/java/ManifestClassGenerator_test.cpp +++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp @@ -16,8 +16,10 @@ #include "java/ManifestClassGenerator.h" +#include "io/StringStream.h" #include "test/Test.h" +using ::aapt::io::StringOutputStream; using ::testing::HasSubstr; using ::testing::Not; @@ -84,6 +86,8 @@ TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { @hide @SystemApi --> <permission android:name="android.permission.SECRET" /> + <!-- @TestApi This is a test only permission. --> + <permission android:name="android.permission.TEST_ONLY" /> </manifest>)"); std::string actual; @@ -110,6 +114,13 @@ TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { @android.annotation.SystemApi public static final String SECRET="android.permission.SECRET";)"; EXPECT_THAT(actual, HasSubstr(expected_secret)); + + const char* expected_test = R"( /** + * This is a test only permission. + */ + @android.annotation.TestApi + public static final String TEST_ONLY="android.permission.TEST_ONLY";)"; + EXPECT_THAT(actual, HasSubstr(expected_test)); } // This is bad but part of public API behaviour so we need to preserve it. @@ -135,12 +146,9 @@ static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, xm return ::testing::AssertionFailure() << "manifest_class == nullptr"; } - std::stringstream out; - if (!manifest_class->WriteJavaFile(manifest_class.get(), "android", true, &out)) { - return ::testing::AssertionFailure() << "failed to write java file"; - } - - *out_str = out.str(); + StringOutputStream out(out_str); + manifest_class->WriteJavaFile(manifest_class.get(), "android", true, &out); + out.Flush(); return ::testing::AssertionSuccess(); } diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index 10c46101123c..ffcef8966654 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -20,10 +20,18 @@ #include <string> #include "android-base/macros.h" +#include "androidfw/StringPiece.h" +#include "JavaClassGenerator.h" +#include "ResourceUtils.h" +#include "ValueVisitor.h" +#include "text/Printer.h" #include "util/Util.h" #include "xml/XmlDom.h" +using ::aapt::io::OutputStream; +using ::aapt::text::Printer; + namespace aapt { namespace proguard { @@ -31,7 +39,7 @@ class BaseVisitor : public xml::Visitor { public: using xml::Visitor::Visit; - BaseVisitor(const Source& source, KeepSet* keep_set) : source_(source), keep_set_(keep_set) { + BaseVisitor(const ResourceFile& file, KeepSet* keep_set) : file_(file), keep_set_(keep_set) { } void Visit(xml::Element* node) override { @@ -52,27 +60,47 @@ class BaseVisitor : public xml::Visitor { for (const auto& child : node->children) { child->Accept(this); } + + for (const auto& attr : node->attributes) { + if (attr.compiled_value) { + auto ref = ValueCast<Reference>(attr.compiled_value.get()); + if (ref) { + AddReference(node->line_number, ref); + } + } + } } protected: - void AddClass(size_t line_number, const std::string& class_name) { - keep_set_->AddClass(Source(source_.path, line_number), class_name); + ResourceFile file_; + KeepSet* keep_set_; + + virtual void AddClass(size_t line_number, const std::string& class_name) { + keep_set_->AddConditionalClass({file_.name, file_.source.WithLine(line_number)}, class_name); } void AddMethod(size_t line_number, const std::string& method_name) { - keep_set_->AddMethod(Source(source_.path, line_number), method_name); + keep_set_->AddMethod({file_.name, file_.source.WithLine(line_number)}, method_name); + } + + void AddReference(size_t line_number, Reference* ref) { + if (ref && ref->name) { + ResourceName ref_name = ref->name.value(); + if (ref_name.package.empty()) { + ref_name = ResourceName(file_.name.package, ref_name.type, ref_name.entry); + } + keep_set_->AddReference({file_.name, file_.source.WithLine(line_number)}, ref_name); + } } private: DISALLOW_COPY_AND_ASSIGN(BaseVisitor); - Source source_; - KeepSet* keep_set_; }; class LayoutVisitor : public BaseVisitor { public: - LayoutVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + LayoutVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) { } void Visit(xml::Element* node) override { @@ -110,7 +138,7 @@ class LayoutVisitor : public BaseVisitor { class MenuVisitor : public BaseVisitor { public: - MenuVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + MenuVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) { } void Visit(xml::Element* node) override { @@ -136,7 +164,7 @@ class MenuVisitor : public BaseVisitor { class XmlResourceVisitor : public BaseVisitor { public: - XmlResourceVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + XmlResourceVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) { } void Visit(xml::Element* node) override { @@ -163,7 +191,7 @@ class XmlResourceVisitor : public BaseVisitor { class TransitionVisitor : public BaseVisitor { public: - TransitionVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + TransitionVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) { } void Visit(xml::Element* node) override { @@ -185,8 +213,9 @@ class TransitionVisitor : public BaseVisitor { class ManifestVisitor : public BaseVisitor { public: - ManifestVisitor(const Source& source, KeepSet* keep_set, bool main_dex_only) - : BaseVisitor(source, keep_set), main_dex_only_(main_dex_only) {} + ManifestVisitor(const ResourceFile& file, KeepSet* keep_set, bool main_dex_only) + : BaseVisitor(file, keep_set), main_dex_only_(main_dex_only) { + } void Visit(xml::Element* node) override { if (node->namespace_uri.empty()) { @@ -241,6 +270,10 @@ class ManifestVisitor : public BaseVisitor { BaseVisitor::Visit(node); } + virtual void AddClass(size_t line_number, const std::string& class_name) override { + keep_set_->AddManifestClass({file_.name, file_.source.WithLine(line_number)}, class_name); + } + private: DISALLOW_COPY_AND_ASSIGN(ManifestVisitor); @@ -249,9 +282,8 @@ class ManifestVisitor : public BaseVisitor { std::string default_process_; }; -bool CollectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keep_set, - bool main_dex_only) { - ManifestVisitor visitor(source, keep_set, main_dex_only); +bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set, bool main_dex_only) { + ManifestVisitor visitor(res->file, keep_set, main_dex_only); if (res->root) { res->root->Accept(&visitor); return true; @@ -259,55 +291,150 @@ bool CollectProguardRulesForManifest(const Source& source, xml::XmlResource* res return false; } -bool CollectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keep_set) { +bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set) { if (!res->root) { return false; } switch (res->file.name.type) { case ResourceType::kLayout: { - LayoutVisitor visitor(source, keep_set); + LayoutVisitor visitor(res->file, keep_set); res->root->Accept(&visitor); break; } case ResourceType::kXml: { - XmlResourceVisitor visitor(source, keep_set); + XmlResourceVisitor visitor(res->file, keep_set); res->root->Accept(&visitor); break; } case ResourceType::kTransition: { - TransitionVisitor visitor(source, keep_set); + TransitionVisitor visitor(res->file, keep_set); res->root->Accept(&visitor); break; } case ResourceType::kMenu: { - MenuVisitor visitor(source, keep_set); + MenuVisitor visitor(res->file, keep_set); res->root->Accept(&visitor); break; } - default: + default: { + BaseVisitor visitor(res->file, keep_set); + res->root->Accept(&visitor); break; + } + } + return true; +} + +void WriteKeepSet(const KeepSet& keep_set, OutputStream* out) { + Printer printer(out); + for (const auto& entry : keep_set.manifest_class_set_) { + for (const UsageLocation& location : entry.second) { + printer.Print("# Referenced at ").Println(location.source.to_string()); + } + printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }"); + } + + for (const auto& entry : keep_set.conditional_class_set_) { + std::set<UsageLocation> locations; + bool can_be_conditional = true; + for (const UsageLocation& location : entry.second) { + can_be_conditional &= CollectLocations(location, keep_set, &locations); + } + + if (keep_set.conditional_keep_rules_ && can_be_conditional) { + for (const UsageLocation& location : locations) { + printer.Print("# Referenced at ").Println(location.source.to_string()); + printer.Print("-if class **.R$layout { int ") + .Print(JavaClassGenerator::TransformToFieldName(location.name.entry)) + .Println("; }"); + printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }"); + } + } else { + for (const UsageLocation& location : entry.second) { + printer.Print("# Referenced at ").Println(location.source.to_string()); + } + printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }"); + } + printer.Println(); + } + + for (const auto& entry : keep_set.method_set_) { + for (const UsageLocation& location : entry.second) { + printer.Print("# Referenced at ").Println(location.source.to_string()); + } + printer.Print("-keepclassmembers class * { *** ").Print(entry.first).Println("(...); }"); + printer.Println(); + } +} + +bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set, + std::set<UsageLocation>* locations) { + locations->insert(location); + + // TODO: allow for more reference types if we can determine its safe. + if (location.name.type != ResourceType::kLayout) { + return false; + } + + for (const auto& entry : keep_set.reference_set_) { + if (entry.first == location.name) { + for (auto& refLocation : entry.second) { + // Don't get stuck in loops + if (locations->find(refLocation) != locations->end()) { + return false; + } + if (!CollectLocations(refLocation, keep_set, locations)) { + return false; + } + } + } } + return true; } -bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set) { - for (const auto& entry : keep_set.keep_set_) { - for (const Source& source : entry.second) { - *out << "# Referenced at " << source << "\n"; +class ReferenceVisitor : public ValueVisitor { + public: + using ValueVisitor::Visit; + + ReferenceVisitor(aapt::IAaptContext* context, ResourceName from, KeepSet* keep_set) + : context_(context), from_(from), keep_set_(keep_set) { + } + + void Visit(Reference* reference) override { + if (reference->name) { + ResourceName reference_name = reference->name.value(); + if (reference_name.package.empty()) { + reference_name = ResourceName(context_->GetCompilationPackage(), reference_name.type, + reference_name.entry); + } + keep_set_->AddReference({from_, reference->GetSource()}, reference_name); } - *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl; } - for (const auto& entry : keep_set.keep_method_set_) { - for (const Source& source : entry.second) { - *out << "# Referenced at " << source << "\n"; + private: + aapt::IAaptContext* context_; + ResourceName from_; + KeepSet* keep_set_; +}; + +bool CollectResourceReferences(aapt::IAaptContext* context, ResourceTable* table, + KeepSet* keep_set) { + for (auto& pkg : table->packages) { + for (auto& type : pkg->types) { + for (auto& entry : type->entries) { + for (auto& config_value : entry->values) { + ResourceName from(pkg->name, type->type, entry->name); + ReferenceVisitor visitor(context, from, keep_set); + config_value->value->Accept(&visitor); + } + } } - *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl; } return true; } diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h index 3c349bab1217..46827ee7cf93 100644 --- a/tools/aapt2/java/ProguardRules.h +++ b/tools/aapt2/java/ProguardRules.h @@ -22,37 +22,83 @@ #include <set> #include <string> +#include "androidfw/StringPiece.h" + #include "Resource.h" +#include "ResourceTable.h" #include "Source.h" +#include "ValueVisitor.h" +#include "io/Io.h" +#include "process/IResourceTableConsumer.h" #include "xml/XmlDom.h" namespace aapt { namespace proguard { +struct UsageLocation { + ResourceName name; + Source source; +}; + class KeepSet { public: - inline void AddClass(const Source& source, const std::string& class_name) { - keep_set_[class_name].insert(source); + KeepSet() = default; + + KeepSet(bool conditional_keep_rules) : conditional_keep_rules_(conditional_keep_rules) { } - inline void AddMethod(const Source& source, const std::string& method_name) { - keep_method_set_[method_name].insert(source); + inline void AddManifestClass(const UsageLocation& file, const std::string& class_name) { + manifest_class_set_[class_name].insert(file); + } + + inline void AddConditionalClass(const UsageLocation& file, const std::string& class_name) { + conditional_class_set_[class_name].insert(file); + } + + inline void AddMethod(const UsageLocation& file, const std::string& method_name) { + method_set_[method_name].insert(file); + } + + inline void AddReference(const UsageLocation& file, const ResourceName& resource_name) { + reference_set_[resource_name].insert(file); } private: - friend bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set); + friend void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out); + + friend bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set, + std::set<UsageLocation>* locations); - std::map<std::string, std::set<Source>> keep_set_; - std::map<std::string, std::set<Source>> keep_method_set_; + bool conditional_keep_rules_ = false; + std::map<std::string, std::set<UsageLocation>> manifest_class_set_; + std::map<std::string, std::set<UsageLocation>> method_set_; + std::map<std::string, std::set<UsageLocation>> conditional_class_set_; + std::map<ResourceName, std::set<UsageLocation>> reference_set_; }; -bool CollectProguardRulesForManifest(const Source& source, - xml::XmlResource* res, KeepSet* keep_set, +bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set, bool main_dex_only = false); -bool CollectProguardRules(const Source& source, xml::XmlResource* res, - KeepSet* keep_set); -bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set); +bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set); + +bool CollectResourceReferences(IAaptContext* context, ResourceTable* table, KeepSet* keep_set); + +void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out); + +bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set, + std::set<UsageLocation>* locations); + +// +// UsageLocation implementation. +// + +inline bool operator==(const UsageLocation& lhs, const UsageLocation& rhs) { + return lhs.name == rhs.name; +} + +inline int operator<(const UsageLocation& lhs, const UsageLocation& rhs) { + return lhs.name.compare(rhs.name); +} } // namespace proguard } // namespace aapt diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp index 900b07339715..37d1a5fbaeb8 100644 --- a/tools/aapt2/java/ProguardRules_test.cpp +++ b/tools/aapt2/java/ProguardRules_test.cpp @@ -15,14 +15,25 @@ */ #include "java/ProguardRules.h" +#include "link/Linkers.h" +#include "io/StringStream.h" #include "test/Test.h" +using ::aapt::io::StringOutputStream; using ::testing::HasSubstr; using ::testing::Not; namespace aapt { +std::string GetKeepSetString(const proguard::KeepSet& set) { + std::string out; + StringOutputStream sout(&out); + proguard::WriteKeepSet(set, &sout); + sout.Flush(); + return out; +} + TEST(ProguardRulesTest, FragmentNameRuleIsEmitted) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( @@ -31,12 +42,10 @@ TEST(ProguardRulesTest, FragmentNameRuleIsEmitted) { layout->file.name = test::ParseNameOrDie("layout/foo"); proguard::KeepSet set; - ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set)); - std::stringstream out; - ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + std::string actual = GetKeepSetString(set); - std::string actual = out.str(); EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); } @@ -47,12 +56,10 @@ TEST(ProguardRulesTest, FragmentClassRuleIsEmitted) { layout->file.name = test::ParseNameOrDie("layout/foo"); proguard::KeepSet set; - ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set)); - std::stringstream out; - ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + std::string actual = GetKeepSetString(set); - std::string actual = out.str(); EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); } @@ -65,16 +72,110 @@ TEST(ProguardRulesTest, FragmentNameAndClassRulesAreEmitted) { layout->file.name = test::ParseNameOrDie("layout/foo"); proguard::KeepSet set; - ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set)); - std::stringstream out; - ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + std::string actual = GetKeepSetString(set); - std::string actual = out.str(); EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); EXPECT_THAT(actual, HasSubstr("com.foo.Baz")); } +TEST(ProguardRulesTest, CustomViewRulesAreEmitted) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( + <View xmlns:android="http://schemas.android.com/apk/res/android"> + <com.foo.Bar /> + </View>)"); + layout->file.name = test::ParseNameOrDie("layout/foo"); + + proguard::KeepSet set; + ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set)); + + std::string actual = GetKeepSetString(set); + + EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); +} + +TEST(ProguardRulesTest, IncludedLayoutRulesAreConditional) { + std::unique_ptr<xml::XmlResource> bar_layout = test::BuildXmlDom(R"( + <View xmlns:android="http://schemas.android.com/apk/res/android"> + <com.foo.Bar /> + </View>)"); + bar_layout->file.name = test::ParseNameOrDie("com.foo:layout/bar"); + + ResourceTable table; + StdErrDiagnostics errDiagnostics; + table.AddResource(bar_layout->file.name, ConfigDescription::DefaultConfig(), "", + util::make_unique<FileReference>(), &errDiagnostics); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .SetCompilationPackage("com.foo") + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(&table)) + .Build(); + + std::unique_ptr<xml::XmlResource> foo_layout = test::BuildXmlDom(R"( + <View xmlns:android="http://schemas.android.com/apk/res/android"> + <include layout="@layout/bar" /> + </View>)"); + foo_layout->file.name = test::ParseNameOrDie("com.foo:layout/foo"); + + XmlReferenceLinker xml_linker; + ASSERT_TRUE(xml_linker.Consume(context.get(), bar_layout.get())); + ASSERT_TRUE(xml_linker.Consume(context.get(), foo_layout.get())); + + proguard::KeepSet set = proguard::KeepSet(true); + ASSERT_TRUE(proguard::CollectProguardRules(bar_layout.get(), &set)); + ASSERT_TRUE(proguard::CollectProguardRules(foo_layout.get(), &set)); + + std::string actual = GetKeepSetString(set); + + EXPECT_THAT(actual, HasSubstr("-if class **.R$layout")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }")); + EXPECT_THAT(actual, HasSubstr("int foo")); + EXPECT_THAT(actual, HasSubstr("int bar")); + EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); +} + +TEST(ProguardRulesTest, AliasedLayoutRulesAreConditional) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( + <View xmlns:android="http://schemas.android.com/apk/res/android"> + <com.foo.Bar /> + </View>)"); + layout->file.name = test::ParseNameOrDie("layout/foo"); + + proguard::KeepSet set = proguard::KeepSet(true); + set.AddReference({test::ParseNameOrDie("layout/bar"), {}}, layout->file.name); + ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set)); + + std::string actual = GetKeepSetString(set); + + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }")); + EXPECT_THAT(actual, HasSubstr("-if class **.R$layout")); + EXPECT_THAT(actual, HasSubstr("int foo")); + EXPECT_THAT(actual, HasSubstr("int bar")); + EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); +} + +TEST(ProguardRulesTest, NonLayoutReferencesAreUnconditional) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( + <View xmlns:android="http://schemas.android.com/apk/res/android"> + <com.foo.Bar /> + </View>)"); + layout->file.name = test::ParseNameOrDie("layout/foo"); + + proguard::KeepSet set = proguard::KeepSet(true); + set.AddReference({test::ParseNameOrDie("style/MyStyle"), {}}, layout->file.name); + ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set)); + + std::string actual = GetKeepSetString(set); + + EXPECT_THAT(actual, Not(HasSubstr("-if"))); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }")); +} + TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( @@ -83,12 +184,10 @@ TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) { layout->file.name = test::ParseNameOrDie("layout/foo"); proguard::KeepSet set; - ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set)); - std::stringstream out; - ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + std::string actual = GetKeepSetString(set); - std::string actual = out.str(); EXPECT_THAT(actual, HasSubstr("bar_method")); } @@ -104,12 +203,10 @@ TEST(ProguardRulesTest, MenuRulesAreEmitted) { menu->file.name = test::ParseNameOrDie("menu/foo"); proguard::KeepSet set; - ASSERT_TRUE(proguard::CollectProguardRules({}, menu.get(), &set)); + ASSERT_TRUE(proguard::CollectProguardRules(menu.get(), &set)); - std::stringstream out; - ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + std::string actual = GetKeepSetString(set); - std::string actual = out.str(); EXPECT_THAT(actual, HasSubstr("on_click")); EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); EXPECT_THAT(actual, HasSubstr("com.foo.Baz")); diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h index 5527f9092c87..3c9c4767b3d1 100644 --- a/tools/aapt2/link/Linkers.h +++ b/tools/aapt2/link/Linkers.h @@ -21,6 +21,7 @@ #include <unordered_set> #include "android-base/macros.h" +#include "androidfw/StringPiece.h" #include "Resource.h" #include "SdkConstants.h" @@ -33,18 +34,15 @@ class ResourceTable; class ResourceEntry; struct ConfigDescription; -/** - * Defines the location in which a value exists. This determines visibility of - * other package's private symbols. - */ +// Defines the context in which a resource value is defined. Most resources are defined with the +// implicit package name of their compilation context. Understanding the package name of a resource +// allows to determine visibility of other symbols which may or may not have their packages defined. struct CallSite { - ResourceNameRef resource; + std::string package; }; -/** - * Determines whether a versioned resource should be created. If a versioned - * resource already exists, it takes precedence. - */ +// Determines whether a versioned resource should be created. If a versioned resource already +// exists, it takes precedence. bool ShouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config, const ApiVersion sdk_version_to_generate); @@ -62,39 +60,26 @@ class AutoVersioner : public IResourceTableConsumer { DISALLOW_COPY_AND_ASSIGN(AutoVersioner); }; -/** - * If any attribute resource values are defined as public, this consumer will - * move all private - * attribute resource values to a private ^private-attr type, avoiding backwards - * compatibility - * issues with new apps running on old platforms. - * - * The Android platform ignores resource attributes it doesn't recognize, so an - * app developer can - * use new attributes in their layout XML files without worrying about - * versioning. This assumption - * actually breaks on older platforms. OEMs may add private attributes that are - * used internally. - * AAPT originally assigned all private attributes IDs immediately proceeding - * the public attributes' - * IDs. - * - * This means that on a newer Android platform, an ID previously assigned to a - * private attribute - * may end up assigned to a public attribute. - * - * App developers assume using the newer attribute is safe on older platforms - * because it will - * be ignored. Instead, the platform thinks the new attribute is an older, - * private attribute and - * will interpret it as such. This leads to unintended styling and exceptions - * thrown due to - * unexpected types. - * - * By moving the private attributes to a completely different type, this ID - * conflict will never - * occur. - */ +// If any attribute resource values are defined as public, this consumer will move all private +// attribute resource values to a private ^private-attr type, avoiding backwards compatibility +// issues with new apps running on old platforms. +// +// The Android platform ignores resource attributes it doesn't recognize, so an app developer can +// use new attributes in their layout XML files without worrying about versioning. This assumption +// actually breaks on older platforms. OEMs may add private attributes that are used internally. +// AAPT originally assigned all private attributes IDs immediately proceeding the public attributes' +// IDs. +// +// This means that on a newer Android platform, an ID previously assigned to a private attribute +// may end up assigned to a public attribute. +// +// App developers assume using the newer attribute is safe on older platforms because it will +// be ignored. Instead, the platform thinks the new attribute is an older, private attribute and +// will interpret it as such. This leads to unintended styling and exceptions thrown due to +// unexpected types. +// +// By moving the private attributes to a completely different type, this ID conflict will never +// occur. class PrivateAttributeMover : public IResourceTableConsumer { public: PrivateAttributeMover() = default; @@ -126,14 +111,10 @@ class ProductFilter : public IResourceTableConsumer { std::unordered_set<std::string> products_; }; -/** - * Removes namespace nodes and URI information from the XmlResource. - * - * Once an XmlResource is processed by this consumer, it is no longer able to - * have its attributes - * parsed. As such, this XmlResource must have already been processed by - * XmlReferenceLinker. - */ +// Removes namespace nodes and URI information from the XmlResource. +// +// Once an XmlResource is processed by this consumer, it is no longer able to have its attributes +// parsed. As such, this XmlResource must have already been processed by XmlReferenceLinker. class XmlNamespaceRemover : public IXmlResourceConsumer { public: explicit XmlNamespaceRemover(bool keep_uris = false) : keep_uris_(keep_uris){}; @@ -146,11 +127,8 @@ class XmlNamespaceRemover : public IXmlResourceConsumer { bool keep_uris_; }; -/** - * Resolves attributes in the XmlResource and compiles string values to resource - * values. - * Once an XmlResource is processed by this linker, it is ready to be flattened. - */ +// Resolves attributes in the XmlResource and compiles string values to resource values. +// Once an XmlResource is processed by this linker, it is ready to be flattened. class XmlReferenceLinker : public IXmlResourceConsumer { public: XmlReferenceLinker() = default; diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index de4fb736758c..a68df1dbc998 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -346,30 +346,15 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, return true; } -class FullyQualifiedClassNameVisitor : public xml::Visitor { - public: - using xml::Visitor::Visit; - - explicit FullyQualifiedClassNameVisitor(const StringPiece& package) : package_(package) {} - - void Visit(xml::Element* el) override { - for (xml::Attribute& attr : el->attributes) { - if (attr.namespace_uri == xml::kSchemaAndroid && - class_attributes_.find(attr.name) != class_attributes_.end()) { - if (Maybe<std::string> new_value = util::GetFullyQualifiedClassName(package_, attr.value)) { - attr.value = std::move(new_value.value()); - } - } +static void FullyQualifyClassName(const StringPiece& package, const StringPiece& attr_ns, + const StringPiece& attr_name, xml::Element* el) { + xml::Attribute* attr = el->FindAttribute(attr_ns, attr_name); + if (attr != nullptr) { + if (Maybe<std::string> new_value = util::GetFullyQualifiedClassName(package, attr->value)) { + attr->value = std::move(new_value.value()); } - - // Super implementation to iterate over the children. - xml::Visitor::Visit(el); } - - private: - StringPiece package_; - std::unordered_set<StringPiece> class_attributes_ = {"name"}; -}; +} static bool RenameManifestPackage(const StringPiece& package_override, xml::Element* manifest_el) { xml::Attribute* attr = manifest_el->FindAttribute({}, "package"); @@ -381,8 +366,25 @@ static bool RenameManifestPackage(const StringPiece& package_override, xml::Elem std::string original_package = std::move(attr->value); attr->value = package_override.to_string(); - FullyQualifiedClassNameVisitor visitor(original_package); - manifest_el->Accept(&visitor); + xml::Element* application_el = manifest_el->FindChild({}, "application"); + if (application_el != nullptr) { + FullyQualifyClassName(original_package, xml::kSchemaAndroid, "name", application_el); + FullyQualifyClassName(original_package, xml::kSchemaAndroid, "backupAgent", application_el); + + for (xml::Element* child_el : application_el->GetChildElements()) { + if (child_el->namespace_uri.empty()) { + if (child_el->name == "activity" || child_el->name == "activity-alias" || + child_el->name == "provider" || child_el->name == "receiver" || + child_el->name == "service") { + FullyQualifyClassName(original_package, xml::kSchemaAndroid, "name", child_el); + } + + if (child_el->name == "activity-alias") { + FullyQualifyClassName(original_package, xml::kSchemaAndroid, "targetActivity", child_el); + } + } + } + } return true; } @@ -404,6 +406,25 @@ bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) { root->InsertChild(0, std::move(uses_sdk)); } + if (options_.compile_sdk_version) { + xml::Attribute* attr = root->FindOrCreateAttribute(xml::kSchemaAndroid, "compileSdkVersion"); + + // Make sure we un-compile the value if it was set to something else. + attr->compiled_value = {}; + + attr->value = options_.compile_sdk_version.value(); + } + + if (options_.compile_sdk_version_codename) { + xml::Attribute* attr = + root->FindOrCreateAttribute(xml::kSchemaAndroid, "compileSdkVersionCodename"); + + // Make sure we un-compile the value if it was set to something else. + attr->compiled_value = {}; + + attr->value = options_.compile_sdk_version_codename.value(); + } + xml::XmlActionExecutor executor; if (!BuildRules(&executor, context->GetDiagnostics())) { return false; diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h index 470f65eb01c4..f5715f605b04 100644 --- a/tools/aapt2/link/ManifestFixer.h +++ b/tools/aapt2/link/ManifestFixer.h @@ -29,22 +29,42 @@ namespace aapt { struct ManifestFixerOptions { + // The minimum SDK version to set if no 'android:minSdkVersion' is defined in a <uses-sdk> tag. Maybe<std::string> min_sdk_version_default; + + // The target SDK version to set if no 'android:targetSdkVersion' is defined in a <uses-sdk> tag. Maybe<std::string> target_sdk_version_default; + + // The Android package to use instead of the one defined in 'package' in <manifest>. + // This also renames all relative package/class names in the manifest to fully qualified + // Java names. Maybe<std::string> rename_manifest_package; + + // The Android package to use instead of the one defined in 'android:targetPackage' in + // <instrumentation>. Maybe<std::string> rename_instrumentation_target_package; + + // The version name to set if 'android:versionName' is not defined in <manifest>. Maybe<std::string> version_name_default; + + // The version code to set if 'android:versionCode' is not defined in <manifest>. Maybe<std::string> version_code_default; + + // The version of the framework being compiled against to set for 'android:compileSdkVersion' in + // the <manifest> tag. + Maybe<std::string> compile_sdk_version; + + // The version codename of the framework being compiled against to set for + // 'android:compileSdkVersionCodename' in the <manifest> tag. + Maybe<std::string> compile_sdk_version_codename; }; -/** - * Verifies that the manifest is correctly formed and inserts defaults - * where specified with ManifestFixerOptions. - */ +// Verifies that the manifest is correctly formed and inserts defaults where specified with +// ManifestFixerOptions. class ManifestFixer : public IXmlResourceConsumer { public: - explicit ManifestFixer(const ManifestFixerOptions& options) - : options_(options) {} + explicit ManifestFixer(const ManifestFixerOptions& options) : options_(options) { + } bool Consume(IAaptContext* context, xml::XmlResource* doc) override; diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index da7f410b8b08..1320dcd2a170 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -19,7 +19,12 @@ #include "test/Test.h" using ::android::StringPiece; +using ::testing::Eq; +using ::testing::Gt; +using ::testing::IsNull; +using ::testing::Ne; using ::testing::NotNull; +using ::testing::StrEq; namespace aapt { @@ -72,22 +77,20 @@ struct ManifestFixerTest : public ::testing::Test { }; TEST_F(ManifestFixerTest, EnsureManifestIsRootTag) { - EXPECT_EQ(nullptr, Verify("<other-tag />")); - EXPECT_EQ(nullptr, Verify("<ns:manifest xmlns:ns=\"com\" />")); - EXPECT_NE(nullptr, Verify("<manifest package=\"android\"></manifest>")); + EXPECT_THAT(Verify("<other-tag />"), IsNull()); + EXPECT_THAT(Verify("<ns:manifest xmlns:ns=\"com\" />"), IsNull()); + EXPECT_THAT(Verify("<manifest package=\"android\"></manifest>"), NotNull()); } TEST_F(ManifestFixerTest, EnsureManifestHasPackage) { - EXPECT_NE(nullptr, Verify("<manifest package=\"android\" />")); - EXPECT_NE(nullptr, Verify("<manifest package=\"com.android\" />")); - EXPECT_NE(nullptr, Verify("<manifest package=\"com.android.google\" />")); - EXPECT_EQ(nullptr, - Verify("<manifest package=\"com.android.google.Class$1\" />")); - EXPECT_EQ(nullptr, Verify("<manifest " - "xmlns:android=\"http://schemas.android.com/apk/" - "res/android\" " - "android:package=\"com.android\" />")); - EXPECT_EQ(nullptr, Verify("<manifest package=\"@string/str\" />")); + EXPECT_THAT(Verify("<manifest package=\"android\" />"), NotNull()); + EXPECT_THAT(Verify("<manifest package=\"com.android\" />"), NotNull()); + EXPECT_THAT(Verify("<manifest package=\"com.android.google\" />"), NotNull()); + EXPECT_THAT(Verify("<manifest package=\"com.android.google.Class$1\" />"), IsNull()); + EXPECT_THAT(Verify("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" " + "android:package=\"com.android\" />"), + IsNull()); + EXPECT_THAT(Verify("<manifest package=\"@string/str\" />"), IsNull()); } TEST_F(ManifestFixerTest, AllowMetaData) { @@ -105,7 +108,7 @@ TEST_F(ManifestFixerTest, AllowMetaData) { </application> <instrumentation android:name=".Go"><meta-data /></instrumentation> </manifest>)EOF"); - ASSERT_NE(nullptr, doc); + ASSERT_THAT(doc, NotNull()); } TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { @@ -117,21 +120,21 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" /> </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); + ASSERT_THAT(doc, NotNull()); xml::Element* el; xml::Attribute* attr; el = doc->root.get(); - ASSERT_NE(nullptr, el); + ASSERT_THAT(el, NotNull()); el = el->FindChild({}, "uses-sdk"); - ASSERT_NE(nullptr, el); + ASSERT_THAT(el, NotNull()); attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("7", attr->value); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("7")); attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("21", attr->value); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("21")); doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" @@ -139,18 +142,18 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { <uses-sdk android:targetSdkVersion="21" /> </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); + ASSERT_THAT(doc, NotNull()); el = doc->root.get(); - ASSERT_NE(nullptr, el); + ASSERT_THAT(el, NotNull()); el = el->FindChild({}, "uses-sdk"); - ASSERT_NE(nullptr, el); + ASSERT_THAT(el, NotNull()); attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("8", attr->value); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("8")); attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("21", attr->value); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("21")); doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" @@ -158,35 +161,35 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { <uses-sdk /> </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); + ASSERT_THAT(doc, NotNull()); el = doc->root.get(); - ASSERT_NE(nullptr, el); + ASSERT_THAT(el, NotNull()); el = el->FindChild({}, "uses-sdk"); - ASSERT_NE(nullptr, el); + ASSERT_THAT(el, NotNull()); attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("8", attr->value); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("8")); attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("22", attr->value); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("22")); doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android" />)EOF", options); - ASSERT_NE(nullptr, doc); + ASSERT_THAT(doc, NotNull()); el = doc->root.get(); - ASSERT_NE(nullptr, el); + ASSERT_THAT(el, NotNull()); el = el->FindChild({}, "uses-sdk"); - ASSERT_NE(nullptr, el); + ASSERT_THAT(el, NotNull()); attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("8", attr->value); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("8")); attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("22", attr->value); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("22")); } TEST_F(ManifestFixerTest, UsesSdkMustComeBeforeApplication) { @@ -197,17 +200,17 @@ TEST_F(ManifestFixerTest, UsesSdkMustComeBeforeApplication) { <application android:name=".MainApplication" /> </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); + ASSERT_THAT(doc, NotNull()); xml::Element* manifest_el = doc->root.get(); - ASSERT_NE(nullptr, manifest_el); + ASSERT_THAT(manifest_el, NotNull()); ASSERT_EQ("manifest", manifest_el->name); xml::Element* application_el = manifest_el->FindChild("", "application"); - ASSERT_NE(nullptr, application_el); + ASSERT_THAT(application_el, NotNull()); xml::Element* uses_sdk_el = manifest_el->FindChild("", "uses-sdk"); - ASSERT_NE(nullptr, uses_sdk_el); + ASSERT_THAT(uses_sdk_el, NotNull()); // Check that the uses_sdk_el comes before application_el in the children // vector. @@ -225,12 +228,12 @@ TEST_F(ManifestFixerTest, UsesSdkMustComeBeforeApplication) { return child.get() == application_el; }); - ASSERT_NE(manifest_el->children.end(), uses_sdk_iter); - ASSERT_NE(manifest_el->children.end(), application_iter); + ASSERT_THAT(uses_sdk_iter, Ne(manifest_el->children.end())); + ASSERT_THAT(application_iter, Ne(manifest_el->children.end())); // The distance should be positive, meaning uses_sdk_iter comes before // application_iter. - EXPECT_GT(std::distance(uses_sdk_iter, application_iter), 0); + EXPECT_THAT(std::distance(uses_sdk_iter, application_iter), Gt(0)); } TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) { @@ -240,48 +243,56 @@ TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) { std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> + <uses-split android:name="feature_a" /> <application android:name=".MainApplication" text="hello"> <activity android:name=".activity.Start" /> <receiver android:name="com.google.android.Receiver" /> </application> </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); + ASSERT_THAT(doc, NotNull()); - xml::Element* manifestEl = doc->root.get(); - ASSERT_NE(nullptr, manifestEl); + xml::Element* manifest_el = doc->root.get(); + ASSERT_THAT(manifest_el, NotNull()); xml::Attribute* attr = nullptr; - attr = manifestEl->FindAttribute({}, "package"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::string("com.android"), attr->value); + attr = manifest_el->FindAttribute({}, "package"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("com.android")); + + xml::Element* uses_split_el = manifest_el->FindChild({}, "uses-split"); + ASSERT_THAT(uses_split_el, NotNull()); + attr = uses_split_el->FindAttribute(xml::kSchemaAndroid, "name"); + ASSERT_THAT(attr, NotNull()); + // This should NOT have been affected. + EXPECT_THAT(attr->value, StrEq("feature_a")); - xml::Element* applicationEl = manifestEl->FindChild({}, "application"); - ASSERT_NE(nullptr, applicationEl); + xml::Element* application_el = manifest_el->FindChild({}, "application"); + ASSERT_THAT(application_el, NotNull()); - attr = applicationEl->FindAttribute(xml::kSchemaAndroid, "name"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::string("android.MainApplication"), attr->value); + attr = application_el->FindAttribute(xml::kSchemaAndroid, "name"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("android.MainApplication")); - attr = applicationEl->FindAttribute({}, "text"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::string("hello"), attr->value); + attr = application_el->FindAttribute({}, "text"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("hello")); xml::Element* el; - el = applicationEl->FindChild({}, "activity"); - ASSERT_NE(nullptr, el); + el = application_el->FindChild({}, "activity"); + ASSERT_THAT(el, NotNull()); attr = el->FindAttribute(xml::kSchemaAndroid, "name"); - ASSERT_NE(nullptr, el); - EXPECT_EQ(std::string("android.activity.Start"), attr->value); + ASSERT_THAT(el, NotNull()); + EXPECT_THAT(attr->value, StrEq("android.activity.Start")); - el = applicationEl->FindChild({}, "receiver"); - ASSERT_NE(nullptr, el); + el = application_el->FindChild({}, "receiver"); + ASSERT_THAT(el, NotNull()); attr = el->FindAttribute(xml::kSchemaAndroid, "name"); - ASSERT_NE(nullptr, el); - EXPECT_EQ(std::string("com.google.android.Receiver"), attr->value); + ASSERT_THAT(el, NotNull()); + EXPECT_THAT(attr->value, StrEq("com.google.android.Receiver")); } TEST_F(ManifestFixerTest, @@ -295,19 +306,19 @@ TEST_F(ManifestFixerTest, <instrumentation android:name=".TestRunner" android:targetPackage="android" /> </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); + ASSERT_THAT(doc, NotNull()); xml::Element* manifest_el = doc->root.get(); - ASSERT_NE(nullptr, manifest_el); + ASSERT_THAT(manifest_el, NotNull()); xml::Element* instrumentation_el = manifest_el->FindChild({}, "instrumentation"); - ASSERT_NE(nullptr, instrumentation_el); + ASSERT_THAT(instrumentation_el, NotNull()); xml::Attribute* attr = instrumentation_el->FindAttribute(xml::kSchemaAndroid, "targetPackage"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::string("com.android"), attr->value); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("com.android")); } TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) { @@ -319,41 +330,39 @@ TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) { <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android" />)EOF", options); - ASSERT_NE(nullptr, doc); + ASSERT_THAT(doc, NotNull()); xml::Element* manifest_el = doc->root.get(); - ASSERT_NE(nullptr, manifest_el); + ASSERT_THAT(manifest_el, NotNull()); xml::Attribute* attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::string("Beta"), attr->value); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("Beta")); attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::string("0x10000000"), attr->value); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("0x10000000")); } TEST_F(ManifestFixerTest, EnsureManifestAttributesAreTyped) { - EXPECT_EQ(nullptr, - Verify("<manifest package=\"android\" coreApp=\"hello\" />")); - EXPECT_EQ(nullptr, - Verify("<manifest package=\"android\" coreApp=\"1dp\" />")); + EXPECT_THAT(Verify("<manifest package=\"android\" coreApp=\"hello\" />"), IsNull()); + EXPECT_THAT(Verify("<manifest package=\"android\" coreApp=\"1dp\" />"), IsNull()); std::unique_ptr<xml::XmlResource> doc = Verify("<manifest package=\"android\" coreApp=\"true\" />"); - ASSERT_NE(nullptr, doc); + ASSERT_THAT(doc, NotNull()); xml::Element* el = doc->root.get(); - ASSERT_NE(nullptr, el); + ASSERT_THAT(el, NotNull()); - EXPECT_EQ("manifest", el->name); + EXPECT_THAT(el->name, StrEq("manifest")); xml::Attribute* attr = el->FindAttribute("", "coreApp"); - ASSERT_NE(nullptr, attr); + ASSERT_THAT(attr, NotNull()); - EXPECT_NE(nullptr, attr->compiled_value); - EXPECT_NE(nullptr, ValueCast<BinaryPrimitive>(attr->compiled_value.get())); + EXPECT_THAT(attr->compiled_value, NotNull()); + EXPECT_THAT(ValueCast<BinaryPrimitive>(attr->compiled_value.get()), NotNull()); } TEST_F(ManifestFixerTest, UsesFeatureMustHaveNameOrGlEsVersion) { @@ -368,21 +377,21 @@ TEST_F(ManifestFixerTest, UsesFeatureMustHaveNameOrGlEsVersion) { <uses-feature android:glEsVersion="2" /> </feature-group> </manifest>)EOF"; - EXPECT_NE(nullptr, Verify(input)); + EXPECT_THAT(Verify(input), NotNull()); input = R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <uses-feature android:name="feature" android:glEsVersion="1" /> </manifest>)EOF"; - EXPECT_EQ(nullptr, Verify(input)); + EXPECT_THAT(Verify(input), IsNull()); input = R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <uses-feature /> </manifest>)EOF"; - EXPECT_EQ(nullptr, Verify(input)); + EXPECT_THAT(Verify(input), IsNull()); input = R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" @@ -391,7 +400,7 @@ TEST_F(ManifestFixerTest, UsesFeatureMustHaveNameOrGlEsVersion) { <uses-feature android:name="feature" android:glEsVersion="1" /> </feature-group> </manifest>)EOF"; - EXPECT_EQ(nullptr, Verify(input)); + EXPECT_THAT(Verify(input), IsNull()); input = R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" @@ -400,7 +409,7 @@ TEST_F(ManifestFixerTest, UsesFeatureMustHaveNameOrGlEsVersion) { <uses-feature /> </feature-group> </manifest>)EOF"; - EXPECT_EQ(nullptr, Verify(input)); + EXPECT_THAT(Verify(input), IsNull()); } TEST_F(ManifestFixerTest, IgnoreNamespacedElements) { @@ -409,7 +418,7 @@ TEST_F(ManifestFixerTest, IgnoreNamespacedElements) { package="android"> <special:tag whoo="true" xmlns:special="http://google.com" /> </manifest>)EOF"; - EXPECT_NE(nullptr, Verify(input)); + EXPECT_THAT(Verify(input), NotNull()); } TEST_F(ManifestFixerTest, DoNotIgnoreNonNamespacedElements) { @@ -418,7 +427,7 @@ TEST_F(ManifestFixerTest, DoNotIgnoreNonNamespacedElements) { package="android"> <tag whoo="true" /> </manifest>)EOF"; - EXPECT_EQ(nullptr, Verify(input)); + EXPECT_THAT(Verify(input), IsNull()); } TEST_F(ManifestFixerTest, SupportKeySets) { @@ -439,4 +448,23 @@ TEST_F(ManifestFixerTest, SupportKeySets) { EXPECT_THAT(Verify(input), NotNull()); } +TEST_F(ManifestFixerTest, InsertCompileSdkVersions) { + std::string input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android" />)"; + ManifestFixerOptions options; + options.compile_sdk_version = {"28"}; + options.compile_sdk_version_codename = {"P"}; + + std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options); + ASSERT_THAT(manifest, NotNull()); + + xml::Attribute* attr = manifest->root->FindAttribute(xml::kSchemaAndroid, "compileSdkVersion"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("28")); + + attr = manifest->root->FindAttribute(xml::kSchemaAndroid, "compileSdkVersionCodename"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("P")); +} + } // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 414e56eb5dcc..ad7d8b65350d 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -30,26 +30,21 @@ #include "util/Util.h" #include "xml/XmlUtil.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { namespace { -/** - * The ReferenceLinkerVisitor will follow all references and make sure they - * point - * to resources that actually exist, either in the local resource table, or as - * external - * symbols. Once the target resource has been found, the ID of the resource will - * be assigned - * to the reference object. - * - * NOTE: All of the entries in the ResourceTable must be assigned IDs. - */ -class ReferenceLinkerVisitor : public ValueVisitor { +// The ReferenceLinkerVisitor will follow all references and make sure they point +// to resources that actually exist, either in the local resource table, or as external +// symbols. Once the target resource has been found, the ID of the resource will be assigned +// to the reference object. +// +// NOTE: All of the entries in the ResourceTable must be assigned IDs. +class ReferenceLinkerVisitor : public DescendingValueVisitor { public: - using ValueVisitor::Visit; + using DescendingValueVisitor::Visit; ReferenceLinkerVisitor(const CallSite& callsite, IAaptContext* context, SymbolTable* symbols, StringPool* string_pool, xml::IPackageDeclStack* decl) @@ -65,14 +60,9 @@ class ReferenceLinkerVisitor : public ValueVisitor { } } - /** - * We visit the Style specially because during this phase, values of - * attributes are - * all RawString values. Now that we are expected to resolve all symbols, we - * can - * lookup the attributes to find out which types are allowed for the - * attributes' values. - */ + // We visit the Style specially because during this phase, values of attributes are + // all RawString values. Now that we are expected to resolve all symbols, we can + // lookup the attributes to find out which types are allowed for the attributes' values. void Visit(Style* style) override { if (style->parent) { Visit(&style->parent.value()); @@ -81,28 +71,21 @@ class ReferenceLinkerVisitor : public ValueVisitor { for (Style::Entry& entry : style->entries) { std::string err_str; - // Transform the attribute reference so that it is using the fully - // qualified package - // name. This will also mark the reference as being able to see private - // resources if - // there was a '*' in the reference or if the package came from the - // private namespace. + // Transform the attribute reference so that it is using the fully qualified package + // name. This will also mark the reference as being able to see private resources if + // there was a '*' in the reference or if the package came from the private namespace. Reference transformed_reference = entry.key; - TransformReferenceFromNamespace(package_decls_, - context_->GetCompilationPackage(), - &transformed_reference); + ResolvePackage(package_decls_, &transformed_reference); - // Find the attribute in the symbol table and check if it is visible from - // this callsite. + // Find the attribute in the symbol table and check if it is visible from this callsite. const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveAttributeCheckVisibility( transformed_reference, callsite_, symbols_, &err_str); if (symbol) { - // Assign our style key the correct ID. - // The ID may not exist. + // Assign our style key the correct ID. The ID may not exist. entry.key.id = symbol->id; - // Try to convert the value to a more specific, typed value based on the - // attribute it is set to. + // Try to convert the value to a more specific, typed value based on the attribute it is + // set to. entry.value = ParseValueWithAttribute(std::move(entry.value), symbol->attribute.get()); // Link/resolve the final value (mostly if it's a reference). @@ -115,8 +98,8 @@ class ReferenceLinkerVisitor : public ValueVisitor { // The actual type of this item is incompatible with the attribute. DiagMessage msg(entry.key.GetSource()); - // Call the matches method again, this time with a DiagMessage so we - // fill in the actual error message. + // Call the matches method again, this time with a DiagMessage so we fill in the actual + // error message. symbol->attribute->Matches(*entry.value, &msg); context_->GetDiagnostics()->Error(msg); error_ = true; @@ -125,7 +108,7 @@ class ReferenceLinkerVisitor : public ValueVisitor { } else { DiagMessage msg(entry.key.GetSource()); msg << "style attribute '"; - ReferenceLinker::WriteResourceName(&msg, entry.key, transformed_reference); + ReferenceLinker::WriteResourceName(entry.key, callsite_, package_decls_, &msg); msg << "' " << err_str; context_->GetDiagnostics()->Error(msg); error_ = true; @@ -133,17 +116,15 @@ class ReferenceLinkerVisitor : public ValueVisitor { } } - bool HasError() { return error_; } + bool HasError() { + return error_; + } private: DISALLOW_COPY_AND_ASSIGN(ReferenceLinkerVisitor); - /** - * Transform a RawString value into a more specific, appropriate value, based - * on the - * Attribute. If a non RawString value is passed in, this is an identity - * transform. - */ + // Transform a RawString value into a more specific, appropriate value, based on the + // Attribute. If a non RawString value is passed in, this is an identity transform. std::unique_ptr<Item> ParseValueWithAttribute(std::unique_ptr<Item> value, const Attribute* attr) { if (RawString* raw_string = ValueCast<RawString>(value.get())) { @@ -178,11 +159,9 @@ class EmptyDeclStack : public xml::IPackageDeclStack { public: EmptyDeclStack() = default; - Maybe<xml::ExtractedPackage> TransformPackageAlias( - const StringPiece& alias, - const StringPiece& local_package) const override { + Maybe<xml::ExtractedPackage> TransformPackageAlias(const StringPiece& alias) const override { if (alias.empty()) { - return xml::ExtractedPackage{local_package.to_string(), true /* private */}; + return xml::ExtractedPackage{{}, true /*private*/}; } return {}; } @@ -191,32 +170,44 @@ class EmptyDeclStack : public xml::IPackageDeclStack { DISALLOW_COPY_AND_ASSIGN(EmptyDeclStack); }; -} // namespace +// The symbol is visible if it is public, or if the reference to it is requesting private access +// or if the callsite comes from the same package. +bool IsSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref, + const CallSite& callsite) { + if (symbol.is_public || ref.private_reference) { + return true; + } -/** - * The symbol is visible if it is public, or if the reference to it is - * requesting private access - * or if the callsite comes from the same package. - */ -bool ReferenceLinker::IsSymbolVisible(const SymbolTable::Symbol& symbol, - const Reference& ref, - const CallSite& callsite) { - if (!symbol.is_public && !ref.private_reference) { - if (ref.name) { - return callsite.resource.package == ref.name.value().package; - } else if (ref.id && symbol.id) { - return ref.id.value().package_id() == symbol.id.value().package_id(); - } else { - return false; + if (ref.name) { + const ResourceName& name = ref.name.value(); + if (name.package.empty()) { + // If the symbol was found, and the package is empty, that means it was found in the local + // scope, which is always visible (private local). + return true; } + + // The symbol is visible if the reference is local to the same package it is defined in. + return callsite.package == name.package; } - return true; + + if (ref.id && symbol.id) { + return ref.id.value().package_id() == symbol.id.value().package_id(); + } + return false; } +} // namespace + const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol(const Reference& reference, + const CallSite& callsite, SymbolTable* symbols) { if (reference.name) { - return symbols->FindByName(reference.name.value()); + const ResourceName& name = reference.name.value(); + if (name.package.empty()) { + // Use the callsite's package name if no package name was defined. + return symbols->FindByName(ResourceName(callsite.package, name.type, name.entry)); + } + return symbols->FindByName(name); } else if (reference.id) { return symbols->FindById(reference.id.value()); } else { @@ -228,7 +219,7 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility(const R const CallSite& callsite, SymbolTable* symbols, std::string* out_error) { - const SymbolTable::Symbol* symbol = ResolveSymbol(reference, symbols); + const SymbolTable::Symbol* symbol = ResolveSymbol(reference, callsite, symbols); if (!symbol) { if (out_error) *out_error = "not found"; return nullptr; @@ -274,24 +265,62 @@ Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(const Reference& return xml::AaptAttribute(*symbol->attribute, symbol->id); } -void ReferenceLinker::WriteResourceName(DiagMessage* out_msg, - const Reference& orig, - const Reference& transformed) { +void ReferenceLinker::WriteResourceName(const Reference& ref, const CallSite& callsite, + const xml::IPackageDeclStack* decls, DiagMessage* out_msg) { CHECK(out_msg != nullptr); + if (!ref.name) { + *out_msg << ref.id.value(); + return; + } - if (orig.name) { - *out_msg << orig.name.value(); - if (transformed.name.value() != orig.name.value()) { - *out_msg << " (aka " << transformed.name.value() << ")"; - } - } else { - *out_msg << orig.id.value(); + *out_msg << ref.name.value(); + + Reference fully_qualified = ref; + xml::ResolvePackage(decls, &fully_qualified); + + ResourceName& full_name = fully_qualified.name.value(); + if (full_name.package.empty()) { + full_name.package = callsite.package; + } + + if (full_name != ref.name.value()) { + *out_msg << " (aka " << full_name << ")"; + } +} + +void ReferenceLinker::WriteAttributeName(const Reference& ref, const CallSite& callsite, + const xml::IPackageDeclStack* decls, + DiagMessage* out_msg) { + CHECK(out_msg != nullptr); + if (!ref.name) { + *out_msg << ref.id.value(); + return; + } + + const ResourceName& ref_name = ref.name.value(); + CHECK_EQ(ref_name.type, ResourceType::kAttr); + + if (!ref_name.package.empty()) { + *out_msg << ref_name.package << ":"; + } + *out_msg << ref_name.entry; + + Reference fully_qualified = ref; + xml::ResolvePackage(decls, &fully_qualified); + + ResourceName& full_name = fully_qualified.name.value(); + if (full_name.package.empty()) { + full_name.package = callsite.package; + } + + if (full_name != ref.name.value()) { + *out_msg << " (aka " << full_name.package << ":" << full_name.entry << ")"; } } bool ReferenceLinker::LinkReference(const CallSite& callsite, Reference* reference, IAaptContext* context, SymbolTable* symbols, - xml::IPackageDeclStack* decls) { + const xml::IPackageDeclStack* decls) { CHECK(reference != nullptr); if (!reference->name && !reference->id) { // This is @null. @@ -299,7 +328,7 @@ bool ReferenceLinker::LinkReference(const CallSite& callsite, Reference* referen } Reference transformed_reference = *reference; - TransformReferenceFromNamespace(decls, context->GetCompilationPackage(), &transformed_reference); + xml::ResolvePackage(decls, &transformed_reference); std::string err_str; const SymbolTable::Symbol* s = @@ -314,7 +343,7 @@ bool ReferenceLinker::LinkReference(const CallSite& callsite, Reference* referen DiagMessage error_msg(reference->GetSource()); error_msg << "resource "; - WriteResourceName(&error_msg, *reference, transformed_reference); + WriteResourceName(*reference, callsite, decls, &error_msg); error_msg << " " << err_str; context->GetDiagnostics()->Error(error_msg); return false; @@ -324,21 +353,24 @@ bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) { EmptyDeclStack decl_stack; bool error = false; for (auto& package : table->packages) { + // Since we're linking, each package must have a name. + CHECK(!package->name.empty()) << "all packages being linked must have a name"; + for (auto& type : package->types) { for (auto& entry : type->entries) { - // Symbol state information may be lost if there is no value for the - // resource. - if (entry->symbol_status.state != SymbolState::kUndefined && - entry->values.empty()) { - context->GetDiagnostics()->Error( - DiagMessage(entry->symbol_status.source) - << "no definition for declared symbol '" - << ResourceNameRef(package->name, type->type, entry->name) - << "'"); + // First, unmangle the name if necessary. + ResourceName name(package->name, type->type, entry->name); + NameMangler::Unmangle(&name.entry, &name.package); + + // Symbol state information may be lost if there is no value for the resource. + if (entry->symbol_status.state != SymbolState::kUndefined && entry->values.empty()) { + context->GetDiagnostics()->Error(DiagMessage(entry->symbol_status.source) + << "no definition for declared symbol '" << name << "'"); error = true; } - CallSite callsite = {ResourceNameRef(package->name, type->type, entry->name)}; + // The context of this resource is the package in which it is defined. + const CallSite callsite{name.package}; ReferenceLinkerVisitor visitor(callsite, context, context->GetExternalSymbols(), &table->string_pool, &decl_stack); diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h index b3d0196d9e7c..b0b49457e5dd 100644 --- a/tools/aapt2/link/ReferenceLinker.h +++ b/tools/aapt2/link/ReferenceLinker.h @@ -21,7 +21,6 @@ #include "Resource.h" #include "ResourceValues.h" -#include "ValueVisitor.h" #include "link/Linkers.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" @@ -29,83 +28,58 @@ namespace aapt { -/** - * Resolves all references to resources in the ResourceTable and assigns them - * IDs. - * The ResourceTable must already have IDs assigned to each resource. - * Once the ResourceTable is processed by this linker, it is ready to be - * flattened. - */ +// Resolves all references to resources in the ResourceTable and assigns them IDs. +// The ResourceTable must already have IDs assigned to each resource. +// Once the ResourceTable is processed by this linker, it is ready to be flattened. class ReferenceLinker : public IResourceTableConsumer { public: ReferenceLinker() = default; - /** - * Returns true if the symbol is visible by the reference and from the - * callsite. - */ - static bool IsSymbolVisible(const SymbolTable::Symbol& symbol, - const Reference& ref, const CallSite& callsite); - - /** - * Performs name mangling and looks up the resource in the symbol table. - * Returns nullptr if the symbol was not found. - */ - static const SymbolTable::Symbol* ResolveSymbol(const Reference& reference, SymbolTable* symbols); - - /** - * Performs name mangling and looks up the resource in the symbol table. If - * the symbol is not visible by the reference at the callsite, nullptr is - * returned. out_error holds the error message. - */ + // Performs name mangling and looks up the resource in the symbol table. Uses the callsite's + // package if the reference has no package name defined (implicit). + // Returns nullptr if the symbol was not found. + static const SymbolTable::Symbol* ResolveSymbol(const Reference& reference, + const CallSite& callsite, SymbolTable* symbols); + + // Performs name mangling and looks up the resource in the symbol table. If the symbol is not + // visible by the reference at the callsite, nullptr is returned. + // `out_error` holds the error message. static const SymbolTable::Symbol* ResolveSymbolCheckVisibility(const Reference& reference, const CallSite& callsite, SymbolTable* symbols, std::string* out_error); - /** - * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is - * an attribute. - * That is, the return value will have a non-null value for - * ISymbolTable::Symbol::attribute. - */ + // Same as ResolveSymbolCheckVisibility(), but also makes sure the symbol is an attribute. + // That is, the return value will have a non-null value for ISymbolTable::Symbol::attribute. static const SymbolTable::Symbol* ResolveAttributeCheckVisibility(const Reference& reference, const CallSite& callsite, SymbolTable* symbols, std::string* out_error); - /** - * Resolves the attribute reference and returns an xml::AaptAttribute if - * successful. - * If resolution fails, outError holds the error message. - */ + // Resolves the attribute reference and returns an xml::AaptAttribute if successful. + // If resolution fails, outError holds the error message. static Maybe<xml::AaptAttribute> CompileXmlAttribute(const Reference& reference, const CallSite& callsite, SymbolTable* symbols, std::string* out_error); - /** - * Writes the resource name to the DiagMessage, using the - * "orig_name (aka <transformed_name>)" syntax. - */ - static void WriteResourceName(DiagMessage* out_msg, const Reference& orig, - const Reference& transformed); - - /** - * Transforms the package name of the reference to the fully qualified package - * name using - * the xml::IPackageDeclStack, then mangles and looks up the symbol. If the - * symbol is visible - * to the reference at the callsite, the reference is updated with an ID. - * Returns false on failure, and an error message is logged to the - * IDiagnostics in the context. - */ + // Writes the resource name to the DiagMessage, using the + // "orig_name (aka <transformed_name>)" syntax. + static void WriteResourceName(const Reference& orig, const CallSite& callsite, + const xml::IPackageDeclStack* decls, DiagMessage* out_msg); + + // Same as WriteResourceName but omits the 'attr' part. + static void WriteAttributeName(const Reference& ref, const CallSite& callsite, + const xml::IPackageDeclStack* decls, DiagMessage* out_msg); + + // Transforms the package name of the reference to the fully qualified package name using + // the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible + // to the reference at the callsite, the reference is updated with an ID. + // Returns false on failure, and an error message is logged to the IDiagnostics in the context. static bool LinkReference(const CallSite& callsite, Reference* reference, IAaptContext* context, - SymbolTable* symbols, xml::IPackageDeclStack* decls); + SymbolTable* symbols, const xml::IPackageDeclStack* decls); - /** - * Links all references in the ResourceTable. - */ + // Links all references in the ResourceTable. bool Consume(IAaptContext* context, ResourceTable* table) override; private: diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp index 72a91689e392..be38b967c986 100644 --- a/tools/aapt2/link/ReferenceLinker_test.cpp +++ b/tools/aapt2/link/ReferenceLinker_test.cpp @@ -18,7 +18,9 @@ #include "test/Test.h" -using android::ResTable_map; +using ::android::ResTable_map; +using ::testing::Eq; +using ::testing::IsNull; using ::testing::NotNull; namespace aapt { @@ -263,7 +265,7 @@ TEST(ReferenceLinkerTest, AppsWithSamePackageButDifferentIdAreVisibleNonPublic) .Build()); std::string error; - const CallSite call_site{ResourceNameRef("com.app.test", ResourceType::kString, "foo")}; + const CallSite call_site{"com.app.test"}; const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveSymbolCheckVisibility( *test::BuildReference("com.app.test:string/foo"), call_site, &table, &error); ASSERT_THAT(symbol, NotNull()); @@ -281,7 +283,7 @@ TEST(ReferenceLinkerTest, AppsWithDifferentPackageCanNotUseEachOthersAttribute) .Build()); std::string error; - const CallSite call_site{ResourceNameRef("com.app.ext", ResourceType::kLayout, "foo")}; + const CallSite call_site{"com.app.ext"}; EXPECT_FALSE(ReferenceLinker::CompileXmlAttribute( *test::BuildReference("com.app.test:attr/foo"), call_site, &table, &error)); @@ -293,4 +295,27 @@ TEST(ReferenceLinkerTest, AppsWithDifferentPackageCanNotUseEachOthersAttribute) EXPECT_TRUE(error.empty()); } +TEST(ReferenceLinkerTest, ReferenceWithNoPackageUsesCallSitePackage) { + NameMangler mangler(NameManglerPolicy{"com.app.test"}); + SymbolTable table(&mangler); + table.AppendSource(test::StaticSymbolSourceBuilder() + .AddSymbol("com.app.test:string/foo", ResourceId(0x7f010000)) + .AddSymbol("com.app.lib:string/foo", ResourceId(0x7f010001)) + .Build()); + + const SymbolTable::Symbol* s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), + CallSite{"com.app.test"}, &table); + ASSERT_THAT(s, NotNull()); + EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x7f010000))); + + s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), CallSite{"com.app.lib"}, + &table); + ASSERT_THAT(s, NotNull()); + EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x7f010001))); + + EXPECT_THAT(ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), + CallSite{"com.app.bad"}, &table), + IsNull()); +} + } // namespace aapt diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index 10e837c725e5..58d0607ed7b3 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -24,7 +24,7 @@ #include "ValueVisitor.h" #include "util/Util.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { @@ -32,27 +32,19 @@ TableMerger::TableMerger(IAaptContext* context, ResourceTable* out_table, const TableMergerOptions& options) : context_(context), master_table_(out_table), options_(options) { // Create the desired package that all tables will be merged into. - master_package_ = master_table_->CreatePackage( - context_->GetCompilationPackage(), context_->GetPackageId()); + master_package_ = + master_table_->CreatePackage(context_->GetCompilationPackage(), context_->GetPackageId()); CHECK(master_package_ != nullptr) << "package name or ID already taken"; } -bool TableMerger::Merge(const Source& src, ResourceTable* table, - io::IFileCollection* collection) { - return MergeImpl(src, table, collection, false /* overlay */, true /* allow new */); +bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay) { + // We allow adding new resources if this is not an overlay, or if the options allow overlays + // to add new resources. + return MergeImpl(src, table, overlay, options_.auto_add_overlay || !overlay /*allow_new*/); } -bool TableMerger::MergeOverlay(const Source& src, ResourceTable* table, - io::IFileCollection* collection) { - return MergeImpl(src, table, collection, true /* overlay */, options_.auto_add_overlay); -} - -/** - * This will merge packages with the same package name (or no package name). - */ -bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, - io::IFileCollection* collection, bool overlay, - bool allow_new) { +// This will merge packages with the same package name (or no package name). +bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, bool overlay, bool allow_new) { bool error = false; for (auto& package : table->packages) { // Only merge an empty package or the package we're building. @@ -60,85 +52,41 @@ bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, // This is because at compile time it is unknown if the attributes are // simply uses of the attribute or definitions. if (package->name.empty() || context_->GetCompilationPackage() == package->name) { - FileMergeCallback callback; - if (collection) { - callback = [&](const ResourceNameRef& name, - const ConfigDescription& config, FileReference* new_file, - FileReference* old_file) -> bool { - // The old file's path points inside the APK, so we can use it as is. - io::IFile* f = collection->FindFile(*old_file->path); - if (!f) { - context_->GetDiagnostics()->Error(DiagMessage(src) - << "file '" << *old_file->path << "' not found"); - return false; - } - - new_file->file = f; - return true; - }; - } - - // Merge here. Once the entries are merged and mangled, any references to - // them are still valid. This is because un-mangled references are - // mangled, then looked up at resolution time. - // Also, when linking, we convert references with no package name to use - // the compilation package name. - error |= !DoMerge(src, table, package.get(), false /* mangle */, overlay, - allow_new, callback); + // Merge here. Once the entries are merged and mangled, any references to them are still + // valid. This is because un-mangled references are mangled, then looked up at resolution + // time. Also, when linking, we convert references with no package name to use the compilation + // package name. + error |= !DoMerge(src, table, package.get(), false /*mangle*/, overlay, allow_new); } } return !error; } -/** - * This will merge and mangle resources from a static library. - */ -bool TableMerger::MergeAndMangle(const Source& src, - const StringPiece& package_name, - ResourceTable* table, - io::IFileCollection* collection) { +// This will merge and mangle resources from a static library. It is assumed that all FileReferences +// have correctly set their io::IFile*. +bool TableMerger::MergeAndMangle(const Source& src, const StringPiece& package_name, + ResourceTable* table) { bool error = false; for (auto& package : table->packages) { // Warn of packages with an unrelated ID. if (package_name != package->name) { - context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring package " - << package->name); + context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring package " << package->name); continue; } bool mangle = package_name != context_->GetCompilationPackage(); merged_packages_.insert(package->name); - - auto callback = [&]( - const ResourceNameRef& name, const ConfigDescription& config, - FileReference* new_file, FileReference* old_file) -> bool { - // The old file's path points inside the APK, so we can use it as is. - io::IFile* f = collection->FindFile(*old_file->path); - if (!f) { - context_->GetDiagnostics()->Error( - DiagMessage(src) << "file '" << *old_file->path << "' not found"); - return false; - } - - new_file->file = f; - return true; - }; - - error |= !DoMerge(src, table, package.get(), mangle, false /* overlay */, - true /* allow new */, callback); + error |= !DoMerge(src, table, package.get(), mangle, false /*overlay*/, true /*allow_new*/); } return !error; } -static bool MergeType(IAaptContext* context, const Source& src, - ResourceTableType* dst_type, +static bool MergeType(IAaptContext* context, const Source& src, ResourceTableType* dst_type, ResourceTableType* src_type) { if (dst_type->symbol_status.state < src_type->symbol_status.state) { - // The incoming type's visibility is stronger, so we should override - // the visibility. + // The incoming type's visibility is stronger, so we should override the visibility. if (src_type->symbol_status.state == SymbolState::kPublic) { - // Only copy the ID if the source is public, or else the ID is - // meaningless. + // Only copy the ID if the source is public, or else the ID is meaningless. dst_type->id = src_type->id; } dst_type->symbol_status = std::move(src_type->symbol_status); @@ -155,14 +103,12 @@ static bool MergeType(IAaptContext* context, const Source& src, return true; } -static bool MergeEntry(IAaptContext* context, const Source& src, - ResourceEntry* dst_entry, ResourceEntry* src_entry) { +static bool MergeEntry(IAaptContext* context, const Source& src, ResourceEntry* dst_entry, + ResourceEntry* src_entry) { if (dst_entry->symbol_status.state < src_entry->symbol_status.state) { - // The incoming type's visibility is stronger, so we should override - // the visibility. + // The incoming type's visibility is stronger, so we should override the visibility. if (src_entry->symbol_status.state == SymbolState::kPublic) { - // Only copy the ID if the source is public, or else the ID is - // meaningless. + // Only copy the ID if the source is public, or else the ID is meaningless. dst_entry->id = src_entry->id; } dst_entry->symbol_status = std::move(src_entry->symbol_status); @@ -171,9 +117,8 @@ static bool MergeEntry(IAaptContext* context, const Source& src, dst_entry->id && src_entry->id && dst_entry->id.value() != src_entry->id.value()) { // Both entries are public and have different IDs. - context->GetDiagnostics()->Error( - DiagMessage(src) << "cannot merge entry '" << src_entry->name - << "': conflicting public IDs"); + context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge entry '" << src_entry->name + << "': conflicting public IDs"); return false; } return true; @@ -181,12 +126,10 @@ static bool MergeEntry(IAaptContext* context, const Source& src, // Modified CollisionResolver which will merge Styleables and Styles. Used with overlays. // -// Styleables are not actual resources, but they are treated as such during the -// compilation phase. +// Styleables are not actual resources, but they are treated as such during the compilation phase. // -// Styleables and Styles don't simply overlay each other, their definitions merge -// and accumulate. If both values are Styleables/Styles, we just merge them into the -// existing value. +// Styleables and Styles don't simply overlay each other, their definitions merge and accumulate. +// If both values are Styleables/Styles, we just merge them into the existing value. static ResourceTable::CollisionResult ResolveMergeCollision(Value* existing, Value* incoming, StringPool* pool) { if (Styleable* existing_styleable = ValueCast<Styleable>(existing)) { @@ -208,7 +151,7 @@ static ResourceTable::CollisionResult ResolveMergeCollision(Value* existing, Val static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context, const ResourceNameRef& res_name, - const bool overlay, + bool overlay, ResourceConfigValue* dst_config_value, ResourceConfigValue* src_config_value, StringPool* pool) { @@ -241,10 +184,8 @@ static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context, } bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table, - ResourceTablePackage* src_package, - const bool mangle_package, const bool overlay, - const bool allow_new_resources, - const FileMergeCallback& callback) { + ResourceTablePackage* src_package, bool mangle_package, bool overlay, + bool allow_new_resources) { bool error = false; for (auto& src_type : src_package->types) { @@ -313,13 +254,6 @@ bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table, } else { new_file_ref = std::unique_ptr<FileReference>(f->Clone(&master_table_->string_pool)); } - - if (callback) { - if (!callback(res_name, src_config_value->config, new_file_ref.get(), f)) { - error = true; - continue; - } - } dst_config_value->value = std::move(new_file_ref); } else { @@ -342,17 +276,20 @@ std::unique_ptr<FileReference> TableMerger::CloneAndMangleFile( util::make_unique<FileReference>(master_table_->string_pool.MakeRef(newPath)); new_file_ref->SetComment(file_ref.GetComment()); new_file_ref->SetSource(file_ref.GetSource()); + new_file_ref->type = file_ref.type; + new_file_ref->file = file_ref.file; return new_file_ref; } return std::unique_ptr<FileReference>(file_ref.Clone(&master_table_->string_pool)); } -bool TableMerger::MergeFileImpl(const ResourceFile& file_desc, io::IFile* file, bool overlay) { +bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFile* file) { ResourceTable table; std::string path = ResourceUtils::BuildResourceFileName(file_desc); std::unique_ptr<FileReference> file_ref = util::make_unique<FileReference>(table.string_pool.MakeRef(path)); file_ref->SetSource(file_desc.source); + file_ref->type = file_desc.type; file_ref->file = file; ResourceTablePackage* pkg = table.CreatePackage(file_desc.name.package, 0x0); @@ -361,17 +298,8 @@ bool TableMerger::MergeFileImpl(const ResourceFile& file_desc, io::IFile* file, ->FindOrCreateValue(file_desc.config, {}) ->value = std::move(file_ref); - return DoMerge(file->GetSource(), &table, pkg, false /* mangle */, - overlay /* overlay */, true /* allow_new */, {}); -} - -bool TableMerger::MergeFile(const ResourceFile& file_desc, io::IFile* file) { - return MergeFileImpl(file_desc, file, false /* overlay */); -} - -bool TableMerger::MergeFileOverlay(const ResourceFile& file_desc, - io::IFile* file) { - return MergeFileImpl(file_desc, file, true /* overlay */); + return DoMerge(file->GetSource(), &table, pkg, false /*mangle*/, overlay /*overlay*/, + true /*allow_new*/); } } // namespace aapt diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h index c96b1b0b4dfb..47e23dded26f 100644 --- a/tools/aapt2/link/TableMerger.h +++ b/tools/aapt2/link/TableMerger.h @@ -33,106 +33,58 @@ namespace aapt { struct TableMergerOptions { - /** - * If true, resources in overlays can be added without previously having - * existed. - */ + // If true, resources in overlays can be added without previously having existed. bool auto_add_overlay = false; }; -/** - * TableMerger takes resource tables and merges all packages within the tables - * that have the same - * package ID. - * - * If a package has a different name, all the entries in that table have their - * names mangled - * to include the package name. This way there are no collisions. In order to do - * this correctly, - * the TableMerger needs to also mangle any FileReference paths. Once these are - * mangled, - * the original source path of the file, along with the new destination path is - * recorded in the - * queue returned from getFileMergeQueue(). - * - * Once the merging is complete, a separate process can go collect the files - * from the various - * source APKs and either copy or process their XML and put them in the correct - * location in - * the final APK. - */ +// TableMerger takes resource tables and merges all packages within the tables that have the same +// package ID. +// +// It is assumed that any FileReference values have their io::IFile pointer set to point to the +// file they represent. +// +// If a package has a different name, all the entries in that table have their names mangled +// to include the package name. This way there are no collisions. In order to do this correctly, +// the TableMerger needs to also mangle any FileReference paths. Once these are mangled, the +// `IFile` pointer in `FileReference` will point to the original file. +// +// Once the merging is complete, a separate phase can go collect the files from the various +// source APKs and either copy or process their XML and put them in the correct location in the +// final APK. class TableMerger { public: - /** - * Note: The out_table ResourceTable must live longer than this TableMerger. - * References are made to this ResourceTable for efficiency reasons. - */ - TableMerger(IAaptContext* context, ResourceTable* out_table, - const TableMergerOptions& options); - - const std::set<std::string>& merged_packages() const { + // Note: The out_table ResourceTable must live longer than this TableMerger. + // References are made to this ResourceTable for efficiency reasons. + TableMerger(IAaptContext* context, ResourceTable* out_table, const TableMergerOptions& options); + + inline const std::set<std::string>& merged_packages() const { return merged_packages_; } - /** - * Merges resources from the same or empty package. This is for local sources. - * An io::IFileCollection is optional and used to find the referenced Files - * and process them. - */ - bool Merge(const Source& src, ResourceTable* table, - io::IFileCollection* collection = nullptr); - - /** - * Merges resources from an overlay ResourceTable. - * An io::IFileCollection is optional and used to find the referenced Files - * and process them. - */ - bool MergeOverlay(const Source& src, ResourceTable* table, - io::IFileCollection* collection = nullptr); - - /** - * Merges resources from the given package, mangling the name. This is for - * static libraries. - * An io::IFileCollection is needed in order to find the referenced Files and - * process them. - */ - bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table, - io::IFileCollection* collection); - - /** - * Merges a compiled file that belongs to this same or empty package. This is - * for local sources. - */ - bool MergeFile(const ResourceFile& fileDesc, io::IFile* file); - - /** - * Merges a compiled file from an overlay, overriding an existing definition. - */ - bool MergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file); + // Merges resources from the same or empty package. This is for local sources. + // If overlay is true, the resources are treated as overlays. + bool Merge(const Source& src, ResourceTable* table, bool overlay); + + // Merges resources from the given package, mangling the name. This is for static libraries. + // All FileReference values must have their io::IFile set. + bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table); + + // Merges a compiled file that belongs to this same or empty package. + bool MergeFile(const ResourceFile& fileDesc, bool overlay, io::IFile* file); private: DISALLOW_COPY_AND_ASSIGN(TableMerger); - using FileMergeCallback = std::function<bool(const ResourceNameRef&, - const ConfigDescription& config, - FileReference*, FileReference*)>; - IAaptContext* context_; ResourceTable* master_table_; TableMergerOptions options_; ResourceTablePackage* master_package_; std::set<std::string> merged_packages_; - bool MergeFileImpl(const ResourceFile& file_desc, io::IFile* file, - bool overlay); - - bool MergeImpl(const Source& src, ResourceTable* src_table, - io::IFileCollection* collection, bool overlay, bool allow_new); + bool MergeImpl(const Source& src, ResourceTable* src_table, bool overlay, bool allow_new); - bool DoMerge(const Source& src, ResourceTable* src_table, - ResourceTablePackage* src_package, const bool mangle_package, - const bool overlay, const bool allow_new_resources, - const FileMergeCallback& callback); + bool DoMerge(const Source& src, ResourceTable* src_table, ResourceTablePackage* src_package, + bool mangle_package, bool overlay, bool allow_new_resources); std::unique_ptr<FileReference> CloneAndMangleFile(const std::string& package, const FileReference& value); diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp index 45b01a494164..6aab8ded24a5 100644 --- a/tools/aapt2/link/TableMerger_test.cpp +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -22,11 +22,12 @@ using ::aapt::test::ValueEq; using ::testing::Contains; +using ::testing::Eq; +using ::testing::Field; using ::testing::NotNull; -using ::testing::UnorderedElementsAreArray; using ::testing::Pointee; -using ::testing::Field; -using ::testing::Eq; +using ::testing::StrEq; +using ::testing::UnorderedElementsAreArray; namespace aapt { @@ -67,10 +68,9 @@ TEST_F(TableMergerTest, SimpleMerge) { ResourceTable final_table; TableMerger merger(context_.get(), &final_table, TableMergerOptions{}); - io::FileCollection collection; - ASSERT_TRUE(merger.Merge({}, table_a.get())); - ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection)); + ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/)); + ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get())); EXPECT_TRUE(merger.merged_packages().count("com.app.b") != 0); @@ -98,7 +98,7 @@ TEST_F(TableMergerTest, MergeFile) { file_desc.source = Source("res/layout-hdpi/main.xml"); test::TestFile test_file("path/to/res/layout-hdpi/main.xml.flat"); - ASSERT_TRUE(merger.MergeFile(file_desc, &test_file)); + ASSERT_TRUE(merger.MergeFile(file_desc, false /*overlay*/, &test_file)); FileReference* file = test::GetValueForConfig<FileReference>( &final_table, "com.app.a:layout/main", test::ParseConfigOrDie("hdpi-v4")); @@ -117,37 +117,40 @@ TEST_F(TableMergerTest, MergeFileOverlay) { test::TestFile file_a("path/to/fileA.xml.flat"); test::TestFile file_b("path/to/fileB.xml.flat"); - ASSERT_TRUE(merger.MergeFile(file_desc, &file_a)); - ASSERT_TRUE(merger.MergeFileOverlay(file_desc, &file_b)); + ASSERT_TRUE(merger.MergeFile(file_desc, false /*overlay*/, &file_a)); + ASSERT_TRUE(merger.MergeFile(file_desc, true /*overlay*/, &file_b)); } TEST_F(TableMergerTest, MergeFileReferences) { + test::TestFile file_a("res/xml/file.xml"); + test::TestFile file_b("res/xml/file.xml"); + std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .AddFileReference("com.app.a:xml/file", "res/xml/file.xml") + .AddFileReference("com.app.a:xml/file", "res/xml/file.xml", &file_a) .Build(); std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.b", 0x7f) - .AddFileReference("com.app.b:xml/file", "res/xml/file.xml") + .AddFileReference("com.app.b:xml/file", "res/xml/file.xml", &file_b) .Build(); ResourceTable final_table; TableMerger merger(context_.get(), &final_table, TableMergerOptions{}); - io::FileCollection collection; - collection.InsertFile("res/xml/file.xml"); - ASSERT_TRUE(merger.Merge({}, table_a.get())); - ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection)); + ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/)); + ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get())); FileReference* f = test::GetValue<FileReference>(&final_table, "com.app.a:xml/file"); ASSERT_THAT(f, NotNull()); - EXPECT_EQ(std::string("res/xml/file.xml"), *f->path); + EXPECT_THAT(*f->path, StrEq("res/xml/file.xml")); + EXPECT_THAT(f->file, Eq(&file_a)); f = test::GetValue<FileReference>(&final_table, "com.app.a:xml/com.app.b$file"); ASSERT_THAT(f, NotNull()); - EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), *f->path); + EXPECT_THAT(*f->path, StrEq("res/xml/com.app.b$file.xml")); + EXPECT_THAT(f->file, Eq(&file_b)); } TEST_F(TableMergerTest, OverrideResourceWithOverlay) { @@ -167,8 +170,8 @@ TEST_F(TableMergerTest, OverrideResourceWithOverlay) { options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, base.get())); - ASSERT_TRUE(merger.MergeOverlay({}, overlay.get())); + ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/)); + ASSERT_TRUE(merger.Merge({}, overlay.get(), true /*overlay*/)); BinaryPrimitive* foo = test::GetValue<BinaryPrimitive>(&final_table, "com.app.a:bool/foo"); ASSERT_THAT(foo, @@ -194,8 +197,8 @@ TEST_F(TableMergerTest, OverrideSameResourceIdsWithOverlay) { options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, base.get())); - ASSERT_TRUE(merger.MergeOverlay({}, overlay.get())); + ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/)); + ASSERT_TRUE(merger.Merge({}, overlay.get(), true /*overlay*/)); } TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) { @@ -217,8 +220,8 @@ TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) { options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, base.get())); - ASSERT_FALSE(merger.MergeOverlay({}, overlay.get())); + ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/)); + ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/)); } TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) { @@ -240,8 +243,8 @@ TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) { options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, base.get())); - ASSERT_FALSE(merger.MergeOverlay({}, overlay.get())); + ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/)); + ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/)); } TEST_F(TableMergerTest, MergeAddResourceFromOverlay) { @@ -259,8 +262,8 @@ TEST_F(TableMergerTest, MergeAddResourceFromOverlay) { options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, table_a.get())); - ASSERT_TRUE(merger.MergeOverlay({}, table_b.get())); + ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/)); + ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/)); } TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) { @@ -277,8 +280,8 @@ TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) { options.auto_add_overlay = true; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, table_a.get())); - ASSERT_TRUE(merger.MergeOverlay({}, table_b.get())); + ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/)); + ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/)); } TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) { @@ -295,8 +298,8 @@ TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) { options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, table_a.get())); - ASSERT_FALSE(merger.MergeOverlay({}, table_b.get())); + ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/)); + ASSERT_FALSE(merger.Merge({}, table_b.get(), true /*overlay*/)); } TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) { @@ -337,8 +340,8 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) { options.auto_add_overlay = true; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, table_a.get())); - ASSERT_TRUE(merger.MergeOverlay({}, table_b.get())); + ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/)); + ASSERT_TRUE(merger.Merge({}, table_b.get(), true /*overlay*/)); Styleable* styleable = test::GetValue<Styleable>(&final_table, "com.app.a:styleable/Foo"); ASSERT_THAT(styleable, NotNull()); diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index bcecd2003846..8852c8e8c66e 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -21,6 +21,7 @@ #include "Diagnostics.h" #include "ResourceUtils.h" #include "SdkConstants.h" +#include "ValueVisitor.h" #include "link/ReferenceLinker.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" @@ -31,16 +32,12 @@ namespace aapt { namespace { -/** - * Visits all references (including parents of styles, references in styles, - * arrays, etc) and - * links their symbolic name to their Resource ID, performing mangling and - * package aliasing - * as needed. - */ -class ReferenceVisitor : public ValueVisitor { +// Visits all references (including parents of styles, references in styles, arrays, etc) and +// links their symbolic name to their Resource ID, performing mangling and package aliasing +// as needed. +class ReferenceVisitor : public DescendingValueVisitor { public: - using ValueVisitor::Visit; + using DescendingValueVisitor::Visit; ReferenceVisitor(const CallSite& callsite, IAaptContext* context, SymbolTable* symbols, xml::IPackageDeclStack* decls) @@ -52,7 +49,9 @@ class ReferenceVisitor : public ValueVisitor { } } - bool HasError() const { return error_; } + bool HasError() const { + return error_; + } private: DISALLOW_COPY_AND_ASSIGN(ReferenceVisitor); @@ -64,9 +63,7 @@ class ReferenceVisitor : public ValueVisitor { bool error_; }; -/** - * Visits each xml Element and compiles the attributes within. - */ +// Visits each xml Element and compiles the attributes within. class XmlVisitor : public xml::PackageAwareVisitor { public: using xml::PackageAwareVisitor::Visit; @@ -92,18 +89,12 @@ class XmlVisitor : public xml::PackageAwareVisitor { // they were assigned to the default Attribute. const Attribute* attribute = &kDefaultAttribute; - std::string attribute_package; if (Maybe<xml::ExtractedPackage> maybe_package = xml::ExtractPackageFromNamespace(attr.namespace_uri)) { // There is a valid package name for this attribute. We will look this up. - attribute_package = maybe_package.value().package; - if (attribute_package.empty()) { - // Empty package means the 'current' or 'local' package. - attribute_package = context_->GetCompilationPackage(); - } - - Reference attr_ref(ResourceNameRef(attribute_package, ResourceType::kAttr, attr.name)); + Reference attr_ref( + ResourceNameRef(maybe_package.value().package, ResourceType::kAttr, attr.name)); attr_ref.private_reference = maybe_package.value().private_namespace; std::string err_str; @@ -111,9 +102,11 @@ class XmlVisitor : public xml::PackageAwareVisitor { ReferenceLinker::CompileXmlAttribute(attr_ref, callsite_, symbols_, &err_str); if (!attr.compiled_attribute) { - context_->GetDiagnostics()->Error(DiagMessage(source) << "attribute '" - << attribute_package << ":" - << attr.name << "' " << err_str); + DiagMessage error_msg(source); + error_msg << "attribute "; + ReferenceLinker::WriteAttributeName(attr_ref, callsite_, this, &error_msg); + error_msg << " " << err_str; + context_->GetDiagnostics()->Error(error_msg); error_ = true; continue; } @@ -129,12 +122,8 @@ class XmlVisitor : public xml::PackageAwareVisitor { } else if ((attribute->type_mask & android::ResTable_map::TYPE_STRING) == 0) { // We won't be able to encode this as a string. DiagMessage msg(source); - msg << "'" << attr.value << "' " - << "is incompatible with attribute "; - if (!attribute_package.empty()) { - msg << attribute_package << ":"; - } - msg << attr.name << " " << *attribute; + msg << "'" << attr.value << "' is incompatible with attribute " << attr.name << " " + << *attribute; context_->GetDiagnostics()->Error(msg); error_ = true; } @@ -163,7 +152,17 @@ class XmlVisitor : public xml::PackageAwareVisitor { } // namespace bool XmlReferenceLinker::Consume(IAaptContext* context, xml::XmlResource* resource) { - const CallSite callsite = {resource->file.name}; + CallSite callsite{resource->file.name.package}; + + std::string out_name = resource->file.name.entry; + NameMangler::Unmangle(&out_name, &callsite.package); + + if (callsite.package.empty()) { + // Assume an empty package means that the XML file is local. This is true of AndroidManifest.xml + // for example. + callsite.package = context->GetCompilationPackage(); + } + XmlVisitor visitor(resource->file.source, callsite, context, context->GetExternalSymbols()); if (resource->root) { resource->root->Accept(&visitor); diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp new file mode 100644 index 000000000000..da3b8792be69 --- /dev/null +++ b/tools/aapt2/optimize/MultiApkGenerator.cpp @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2017 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 "MultiApkGenerator.h" + +#include <algorithm> +#include <string> + +#include "androidfw/StringPiece.h" + +#include "LoadedApk.h" +#include "ResourceUtils.h" +#include "ValueVisitor.h" +#include "configuration/ConfigurationParser.h" +#include "filter/AbiFilter.h" +#include "filter/Filter.h" +#include "format/Archive.h" +#include "format/binary/XmlFlattener.h" +#include "optimize/VersionCollapser.h" +#include "process/IResourceTableConsumer.h" +#include "split/TableSplitter.h" +#include "util/Files.h" +#include "xml/XmlDom.h" +#include "xml/XmlUtil.h" + +namespace aapt { + +using ::aapt::configuration::AndroidSdk; +using ::aapt::configuration::Artifact; +using ::aapt::configuration::PostProcessingConfiguration; +using ::aapt::xml::kSchemaAndroid; +using ::aapt::xml::XmlResource; +using ::android::StringPiece; + +namespace { + +Maybe<AndroidSdk> GetAndroidSdk(const Artifact& artifact, const PostProcessingConfiguration& config, + IDiagnostics* diag) { + if (!artifact.android_sdk_group) { + return {}; + } + + const std::string& group_name = artifact.android_sdk_group.value(); + auto group = config.android_sdk_groups.find(group_name); + // TODO: Remove validation when configuration parser ensures referential integrity. + if (group == config.android_sdk_groups.end()) { + diag->Error(DiagMessage() << "could not find referenced group '" << group_name << "'"); + return {}; + } + + return group->second; +} + +} // namespace + +/** + * Context wrapper that allows the min Android SDK value to be overridden. + */ +class ContextWrapper : public IAaptContext { + public: + explicit ContextWrapper(IAaptContext* context) + : context_(context), min_sdk_(context_->GetMinSdkVersion()) { + } + + PackageType GetPackageType() override { + return context_->GetPackageType(); + } + + SymbolTable* GetExternalSymbols() override { + return context_->GetExternalSymbols(); + } + + IDiagnostics* GetDiagnostics() override { + if (source_diag_) { + return source_diag_.get(); + } + return context_->GetDiagnostics(); + } + + const std::string& GetCompilationPackage() override { + return context_->GetCompilationPackage(); + } + + uint8_t GetPackageId() override { + return context_->GetPackageId(); + } + + NameMangler* GetNameMangler() override { + return context_->GetNameMangler(); + } + + bool IsVerbose() override { + return context_->IsVerbose(); + } + + int GetMinSdkVersion() override { + return min_sdk_; + } + + void SetMinSdkVersion(int min_sdk) { + min_sdk_ = min_sdk; + } + + void SetSource(const Source& source) { + source_diag_ = util::make_unique<SourcePathDiagnostics>(source, context_->GetDiagnostics()); + } + + private: + IAaptContext* context_; + std::unique_ptr<SourcePathDiagnostics> source_diag_; + + int min_sdk_ = -1; +}; + +MultiApkGenerator::MultiApkGenerator(LoadedApk* apk, IAaptContext* context) + : apk_(apk), context_(context) { +} + +bool MultiApkGenerator::FromBaseApk(const MultiApkGeneratorOptions& options) { + // TODO(safarmer): Handle APK version codes for the generated APKs. + const PostProcessingConfiguration& config = options.config; + + const std::string& apk_name = file::GetFilename(apk_->GetSource().path).to_string(); + const StringPiece ext = file::GetExtension(apk_name); + const std::string base_name = apk_name.substr(0, apk_name.rfind(ext.to_string())); + + std::unordered_set<std::string> artifacts_to_keep = options.kept_artifacts; + std::unordered_set<std::string> filtered_artifacts; + std::unordered_set<std::string> kept_artifacts; + + // For now, just write out the stripped APK since ABI splitting doesn't modify anything else. + for (const Artifact& artifact : config.artifacts) { + SourcePathDiagnostics diag{{apk_name}, context_->GetDiagnostics()}; + + FilterChain filters; + + if (!artifact.name && !config.artifact_format) { + diag.Error( + DiagMessage() << "Artifact does not have a name and no global name template defined"); + return false; + } + + Maybe<std::string> maybe_artifact_name = + (artifact.name) ? artifact.Name(apk_name, &diag) + : artifact.ToArtifactName(config.artifact_format.value(), apk_name, &diag); + + if (!maybe_artifact_name) { + diag.Error(DiagMessage() << "Could not determine split APK artifact name"); + return false; + } + + const std::string& artifact_name = maybe_artifact_name.value(); + + ContextWrapper wrapped_context{context_}; + wrapped_context.SetSource({artifact_name}); + + if (!options.kept_artifacts.empty()) { + const auto& it = artifacts_to_keep.find(artifact_name); + if (it == artifacts_to_keep.end()) { + filtered_artifacts.insert(artifact_name); + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note(DiagMessage(artifact_name) << "skipping artifact"); + } + continue; + } else { + artifacts_to_keep.erase(it); + kept_artifacts.insert(artifact_name); + } + } + + std::unique_ptr<ResourceTable> table = + FilterTable(artifact, config, *apk_->GetResourceTable(), &wrapped_context, &filters); + if (!table) { + return false; + } + + std::unique_ptr<XmlResource> manifest; + if (!UpdateManifest(artifact, config, &manifest, &diag)) { + diag.Error(DiagMessage() << "could not update AndroidManifest.xml for output artifact"); + return false; + } + + std::string out = options.out_dir; + if (!file::mkdirs(out)) { + diag.Warn(DiagMessage() << "could not create out dir: " << out); + } + file::AppendPath(&out, artifact_name); + + if (context_->IsVerbose()) { + diag.Note(DiagMessage() << "Generating split: " << out); + } + + std::unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(&diag, out); + + if (context_->IsVerbose()) { + diag.Note(DiagMessage() << "Writing output: " << out); + } + + if (!apk_->WriteToArchive(&wrapped_context, table.get(), options.table_flattener_options, + &filters, writer.get(), manifest.get())) { + return false; + } + } + + // Make sure all of the requested artifacts were valid. If there are any kept artifacts left, + // either the config or the command line was wrong. + if (!artifacts_to_keep.empty()) { + context_->GetDiagnostics()->Error( + DiagMessage() << "The configuration and command line to filter artifacts do not match"); + + context_->GetDiagnostics()->Error(DiagMessage() << kept_artifacts.size() << " kept:"); + for (const auto& artifact : kept_artifacts) { + context_->GetDiagnostics()->Error(DiagMessage() << " " << artifact); + } + + context_->GetDiagnostics()->Error(DiagMessage() << filtered_artifacts.size() << " filtered:"); + for (const auto& artifact : filtered_artifacts) { + context_->GetDiagnostics()->Error(DiagMessage() << " " << artifact); + } + + context_->GetDiagnostics()->Error(DiagMessage() << artifacts_to_keep.size() << " missing:"); + for (const auto& artifact : artifacts_to_keep) { + context_->GetDiagnostics()->Error(DiagMessage() << " " << artifact); + } + + return false; + } + + return true; +} + +std::unique_ptr<ResourceTable> MultiApkGenerator::FilterTable( + const configuration::Artifact& artifact, + const configuration::PostProcessingConfiguration& config, const ResourceTable& old_table, + IAaptContext* context, FilterChain* filters) { + TableSplitterOptions splits; + AxisConfigFilter axis_filter; + ContextWrapper wrapped_context{context}; + + if (artifact.abi_group) { + const std::string& group_name = artifact.abi_group.value(); + + auto group = config.abi_groups.find(group_name); + // TODO: Remove validation when configuration parser ensures referential integrity. + if (group == config.abi_groups.end()) { + context->GetDiagnostics()->Error(DiagMessage() << "could not find referenced ABI group '" + << group_name << "'"); + return {}; + } + filters->AddFilter(AbiFilter::FromAbiList(group->second)); + } + + if (artifact.screen_density_group) { + const std::string& group_name = artifact.screen_density_group.value(); + + auto group = config.screen_density_groups.find(group_name); + // TODO: Remove validation when configuration parser ensures referential integrity. + if (group == config.screen_density_groups.end()) { + context->GetDiagnostics()->Error(DiagMessage() + << "could not find referenced group '" << group_name << "'"); + return {}; + } + + const std::vector<ConfigDescription>& densities = group->second; + for (const auto& density_config : densities) { + splits.preferred_densities.push_back(density_config.density); + } + } + + if (artifact.locale_group) { + const std::string& group_name = artifact.locale_group.value(); + auto group = config.locale_groups.find(group_name); + // TODO: Remove validation when configuration parser ensures referential integrity. + if (group == config.locale_groups.end()) { + context->GetDiagnostics()->Error(DiagMessage() + << "could not find referenced group '" << group_name << "'"); + return {}; + } + + const std::vector<ConfigDescription>& locales = group->second; + for (const auto& locale : locales) { + axis_filter.AddConfig(locale); + } + splits.config_filter = &axis_filter; + } + + Maybe<AndroidSdk> sdk = GetAndroidSdk(artifact, config, context->GetDiagnostics()); + if (sdk && sdk.value().min_sdk_version) { + wrapped_context.SetMinSdkVersion(sdk.value().min_sdk_version.value()); + } + + std::unique_ptr<ResourceTable> table = old_table.Clone(); + + VersionCollapser collapser; + if (!collapser.Consume(&wrapped_context, table.get())) { + context->GetDiagnostics()->Error(DiagMessage() << "Failed to strip versioned resources"); + return {}; + } + + TableSplitter splitter{{}, splits}; + splitter.SplitTable(table.get()); + return table; +} + +bool MultiApkGenerator::UpdateManifest(const Artifact& artifact, + const PostProcessingConfiguration& config, + std::unique_ptr<XmlResource>* updated_manifest, + IDiagnostics* diag) { + const xml::XmlResource* apk_manifest = apk_->GetManifest(); + if (apk_manifest == nullptr) { + return false; + } + + *updated_manifest = apk_manifest->Clone(); + XmlResource* manifest = updated_manifest->get(); + + // Make sure the first element is <manifest> with package attribute. + xml::Element* manifest_el = manifest->root.get(); + if (manifest_el == nullptr) { + return false; + } + + if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") { + diag->Error(DiagMessage(manifest->file.source) << "root tag must be <manifest>"); + return false; + } + + // Update the versionCode attribute. + xml::Attribute* versionCode = manifest_el->FindAttribute(kSchemaAndroid, "versionCode"); + if (versionCode == nullptr) { + diag->Error(DiagMessage(manifest->file.source) << "manifest must have a versionCode attribute"); + return false; + } + + auto* compiled_version = ValueCast<BinaryPrimitive>(versionCode->compiled_value.get()); + if (compiled_version == nullptr) { + diag->Error(DiagMessage(manifest->file.source) << "versionCode is invalid"); + return false; + } + + int new_version = compiled_version->value.data + artifact.version; + versionCode->compiled_value = ResourceUtils::TryParseInt(std::to_string(new_version)); + + // Check to see if the minSdkVersion needs to be updated. + Maybe<AndroidSdk> maybe_sdk = GetAndroidSdk(artifact, config, diag); + if (maybe_sdk) { + // TODO(safarmer): Handle the rest of the Android SDK. + const AndroidSdk& android_sdk = maybe_sdk.value(); + + if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) { + if (xml::Attribute* min_sdk_attr = + uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) { + // Populate with a pre-compiles attribute to we don't need to relink etc. + const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version.value()); + min_sdk_attr->compiled_value = ResourceUtils::TryParseInt(min_sdk_str); + } else { + // There was no minSdkVersion. This is strange since at this point we should have been + // through the manifest fixer which sets the default minSdkVersion. + diag->Error(DiagMessage(manifest->file.source) << "missing minSdkVersion from <uses-sdk>"); + return false; + } + } else { + // No uses-sdk present. This is strange since at this point we should have been + // through the manifest fixer which should have added it. + diag->Error(DiagMessage(manifest->file.source) << "missing <uses-sdk> from <manifest>"); + return false; + } + } + + if (artifact.screen_density_group) { + auto densities = config.screen_density_groups.find(artifact.screen_density_group.value()); + CHECK(densities != config.screen_density_groups.end()) << "Missing density group"; + + xml::Element* screens_el = manifest_el->FindChild({}, "compatible-screens"); + if (!screens_el) { + // create a new element. + std::unique_ptr<xml::Element> new_screens_el = util::make_unique<xml::Element>(); + new_screens_el->name = "compatible-screens"; + screens_el = new_screens_el.get(); + manifest_el->InsertChild(0, std::move(new_screens_el)); + } else { + // clear out the old element. + screens_el->GetChildElements().clear(); + } + + for (const auto& density : densities->second) { + std::unique_ptr<xml::Element> screen_el = util::make_unique<xml::Element>(); + screen_el->name = "screen"; + const char* density_str = density.toString().string(); + screen_el->attributes.push_back(xml::Attribute{kSchemaAndroid, "screenDensity", density_str}); + screens_el->AppendChild(std::move(screen_el)); + } + } + + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/optimize/MultiApkGenerator.h b/tools/aapt2/optimize/MultiApkGenerator.h new file mode 100644 index 000000000000..dedb610e1caa --- /dev/null +++ b/tools/aapt2/optimize/MultiApkGenerator.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 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 AAPT2_APKSPLITTER_H +#define AAPT2_APKSPLITTER_H + +#include <memory> +#include <string> +#include <unordered_set> + +#include "Diagnostics.h" +#include "LoadedApk.h" +#include "configuration/ConfigurationParser.h" + +namespace aapt { + +struct MultiApkGeneratorOptions { + std::string out_dir; + configuration::PostProcessingConfiguration config; + TableFlattenerOptions table_flattener_options; + std::unordered_set<std::string> kept_artifacts; +}; + +/** + * Generates a set of APKs that are a subset of the original base APKs. Each of the new APKs contain + * only the resources and assets for an artifact in the configuration file. + */ +class MultiApkGenerator { + public: + MultiApkGenerator(LoadedApk* apk, IAaptContext* context); + + /** + * Writes a set of APKs to the provided output directory. Each APK is a subset fo the base APK and + * represents an artifact in the post processing configuration. + */ + bool FromBaseApk(const MultiApkGeneratorOptions& options); + + protected: + virtual std::unique_ptr<ResourceTable> FilterTable( + const configuration::Artifact& artifact, + const configuration::PostProcessingConfiguration& config, const ResourceTable& old_table, + IAaptContext* context, FilterChain* chain); + + private: + IDiagnostics* GetDiagnostics() { + return context_->GetDiagnostics(); + } + + bool UpdateManifest(const configuration::Artifact& artifact, + const configuration::PostProcessingConfiguration& config, + std::unique_ptr<xml::XmlResource>* updated_manifest, IDiagnostics* diag); + + LoadedApk* apk_; + IAaptContext* context_; +}; + +} // namespace aapt + +#endif // AAPT2_APKSPLITTER_H diff --git a/tools/aapt2/optimize/MultiApkGenerator_test.cpp b/tools/aapt2/optimize/MultiApkGenerator_test.cpp new file mode 100644 index 000000000000..2d303e220f8f --- /dev/null +++ b/tools/aapt2/optimize/MultiApkGenerator_test.cpp @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2017 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 "optimize/MultiApkGenerator.h" + +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "LoadedApk.h" +#include "ResourceTable.h" +#include "configuration/ConfigurationParser.h" +#include "filter/Filter.h" +#include "format/Archive.h" +#include "format/binary/TableFlattener.h" +#include "process/IResourceTableConsumer.h" +#include "test/Context.h" +#include "test/Test.h" + +namespace aapt { +namespace { + +using ::aapt::configuration::Abi; +using ::aapt::configuration::AndroidSdk; +using ::aapt::configuration::Artifact; +using ::aapt::configuration::PostProcessingConfiguration; +using ::aapt::test::GetValue; +using ::aapt::test::GetValueForConfig; +using ::aapt::test::ParseConfigOrDie; +using ::testing::Eq; +using ::testing::IsNull; +using ::testing::Not; +using ::testing::NotNull; +using ::testing::PrintToString; +using ::testing::Return; +using ::testing::Test; +using ::testing::_; + +/** + * Subclass the MultiApkGenerator class so that we can access the protected FilterTable method to + * directly test table filter. + */ +class MultiApkGeneratorWrapper : public MultiApkGenerator { + public: + MultiApkGeneratorWrapper(LoadedApk* apk, IAaptContext* context) + : MultiApkGenerator(apk, context) { + } + + std::unique_ptr<ResourceTable> FilterTable( + const configuration::Artifact& artifact, + const configuration::PostProcessingConfiguration& config, const ResourceTable& old_table, + IAaptContext* context, FilterChain* filter_chain) override { + return MultiApkGenerator::FilterTable(artifact, config, old_table, context, filter_chain); + } +}; + +/** MultiApkGenerator test fixture. */ +class MultiApkGeneratorTest : public ::testing::Test { + public: + std::unique_ptr<ResourceTable> BuildTable() { + return test::ResourceTableBuilder() + .AddFileReference(kResourceName, "res/drawable-mdpi/icon.png", mdpi_) + .AddFileReference(kResourceName, "res/drawable-hdpi/icon.png", hdpi_) + .AddFileReference(kResourceName, "res/drawable-xhdpi/icon.png", xhdpi_) + .AddFileReference(kResourceName, "res/drawable-xxhdpi/icon.png", xxhdpi_) + .AddFileReference(kResourceName, "res/drawable-v19/icon.xml", v19_) + .AddFileReference(kResourceName, "res/drawable-v21/icon.xml", v21_) + .AddSimple("android:string/one") + .Build(); + } + + inline FileReference* ValueForConfig(ResourceTable* table, const ConfigDescription& config) { + return GetValueForConfig<FileReference>(table, kResourceName, config); + }; + + void SetUp() override { + } + + protected: + static constexpr const char* kResourceName = "android:drawable/icon"; + + ConfigDescription default_ = ParseConfigOrDie("").CopyWithoutSdkVersion(); + ConfigDescription mdpi_ = ParseConfigOrDie("mdpi").CopyWithoutSdkVersion(); + ConfigDescription hdpi_ = ParseConfigOrDie("hdpi").CopyWithoutSdkVersion(); + ConfigDescription xhdpi_ = ParseConfigOrDie("xhdpi").CopyWithoutSdkVersion(); + ConfigDescription xxhdpi_ = ParseConfigOrDie("xxhdpi").CopyWithoutSdkVersion(); + ConfigDescription xxxhdpi_ = ParseConfigOrDie("xxxhdpi").CopyWithoutSdkVersion(); + ConfigDescription v19_ = ParseConfigOrDie("v19"); + ConfigDescription v21_ = ParseConfigOrDie("v21"); +}; + +TEST_F(MultiApkGeneratorTest, VersionFilterNewerVersion) { + std::unique_ptr<ResourceTable> table = BuildTable(); + + LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}}; + std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(19).Build(); + PostProcessingConfiguration empty_config; + TableFlattenerOptions table_flattener_options; + FilterChain chain; + + Artifact x64 = test::ArtifactBuilder() + .SetName("${basename}.${sdk}.apk") + .SetDensityGroup("xhdpi") + .SetAndroidSdk("v23") + .Build(); + + auto config = test::PostProcessingConfigurationBuilder() + .SetLocaleGroup("en", {"en"}) + .SetAbiGroup("x64", {Abi::kX86_64}) + .SetDensityGroup("xhdpi", {"xhdpi"}) + .SetAndroidSdk("v23", AndroidSdk::ForMinSdk(23)) + .AddArtifact(x64) + .Build(); + + MultiApkGeneratorWrapper generator{&apk, ctx.get()}; + std::unique_ptr<ResourceTable> split = + generator.FilterTable(x64, config, *apk.GetResourceTable(), ctx.get(), &chain); + + ResourceTable* new_table = split.get(); + EXPECT_THAT(ValueForConfig(new_table, mdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, hdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, xxhdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, xxxhdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, v19_), IsNull()); + + // xhdpi directly matches one of the required dimensions. + EXPECT_THAT(ValueForConfig(new_table, xhdpi_), NotNull()); + // drawable-v21 was converted to drawable. + EXPECT_THAT(ValueForConfig(new_table, default_), NotNull()); + EXPECT_THAT(GetValue<Id>(new_table, "android:string/one"), NotNull()); +} + +TEST_F(MultiApkGeneratorTest, VersionFilterOlderVersion) { + std::unique_ptr<ResourceTable> table = BuildTable(); + + LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}}; + std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build(); + PostProcessingConfiguration empty_config; + TableFlattenerOptions table_flattener_options; + FilterChain chain; + + Artifact x64 = test::ArtifactBuilder() + .SetName("${basename}.${sdk}.apk") + .SetDensityGroup("xhdpi") + .SetAndroidSdk("v4") + .Build(); + + auto config = test::PostProcessingConfigurationBuilder() + .SetLocaleGroup("en", {"en"}) + .SetAbiGroup("x64", {Abi::kX86_64}) + .SetDensityGroup("xhdpi", {"xhdpi"}) + .SetAndroidSdk("v4", AndroidSdk::ForMinSdk(4)) + .AddArtifact(x64) + .Build(); + + MultiApkGeneratorWrapper generator{&apk, ctx.get()};; + std::unique_ptr<ResourceTable> split = + generator.FilterTable(x64, config, *apk.GetResourceTable(), ctx.get(), &chain); + + ResourceTable* new_table = split.get(); + EXPECT_THAT(ValueForConfig(new_table, mdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, hdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, xxhdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, xxxhdpi_), IsNull()); + + EXPECT_THAT(ValueForConfig(new_table, xhdpi_), NotNull()); + EXPECT_THAT(ValueForConfig(new_table, v19_), NotNull()); + EXPECT_THAT(ValueForConfig(new_table, v21_), NotNull()); + EXPECT_THAT(GetValue<Id>(new_table, "android:string/one"), NotNull()); +} + +TEST_F(MultiApkGeneratorTest, VersionFilterNoVersion) { + std::unique_ptr<ResourceTable> table = BuildTable(); + + LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}}; + std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build(); + PostProcessingConfiguration empty_config; + TableFlattenerOptions table_flattener_options; + FilterChain chain; + + Artifact x64 = + test::ArtifactBuilder().SetName("${basename}.${sdk}.apk").SetDensityGroup("xhdpi").Build(); + + auto config = test::PostProcessingConfigurationBuilder() + .SetLocaleGroup("en", {"en"}) + .SetAbiGroup("x64", {Abi::kX86_64}) + .SetDensityGroup("xhdpi", {"xhdpi"}) + .AddArtifact(x64) + .Build(); + + MultiApkGeneratorWrapper generator{&apk, ctx.get()}; + std::unique_ptr<ResourceTable> split = + generator.FilterTable(x64, config, *apk.GetResourceTable(), ctx.get(), &chain); + + ResourceTable* new_table = split.get(); + EXPECT_THAT(ValueForConfig(new_table, mdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, hdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, xxhdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, xxxhdpi_), IsNull()); + + EXPECT_THAT(ValueForConfig(new_table, xhdpi_), NotNull()); + EXPECT_THAT(ValueForConfig(new_table, v19_), NotNull()); + EXPECT_THAT(ValueForConfig(new_table, v21_), NotNull()); + EXPECT_THAT(GetValue<Id>(new_table, "android:string/one"), NotNull()); +} + +} // namespace +} // namespace aapt diff --git a/tools/aapt2/optimize/VersionCollapser.cpp b/tools/aapt2/optimize/VersionCollapser.cpp index d941b487e439..cc1fc1e6910b 100644 --- a/tools/aapt2/optimize/VersionCollapser.cpp +++ b/tools/aapt2/optimize/VersionCollapser.cpp @@ -63,11 +63,9 @@ FilterIterator<Iterator, Pred> make_filter_iterator(Iterator begin, } /** - * Every Configuration with an SDK version specified that is less than minSdk - * will be removed. - * The exception is when there is no exact matching resource for the minSdk. The - * next smallest - * one will be kept. + * Every Configuration with an SDK version specified that is less than minSdk will be removed. The + * exception is when there is no exact matching resource for the minSdk. The next smallest one will + * be kept. */ static void CollapseVersions(int min_sdk, ResourceEntry* entry) { // First look for all sdks less than minSdk. @@ -80,11 +78,9 @@ static void CollapseVersions(int min_sdk, ResourceEntry* entry) { const ConfigDescription& config = (*iter)->config; if (config.sdkVersion <= min_sdk) { - // This is the first configuration we've found with a smaller or equal SDK - // level - // to the minimum. We MUST keep this one, but remove all others we find, - // which get - // overridden by this one. + // This is the first configuration we've found with a smaller or equal SDK level to the + // minimum. We MUST keep this one, but remove all others we find, which get overridden by this + // one. ConfigDescription config_without_sdk = config.CopyWithoutSdkVersion(); auto pred = [&](const std::unique_ptr<ResourceConfigValue>& val) -> bool { @@ -115,11 +111,9 @@ static void CollapseVersions(int min_sdk, ResourceEntry* entry) { -> bool { return val == nullptr; }), entry->values.end()); - // Strip the version qualifiers for every resource with version <= minSdk. - // This will ensure - // that the resource entries are all packed together in the same ResTable_type - // struct - // and take up less space in the resources.arsc table. + // Strip the version qualifiers for every resource with version <= minSdk. This will ensure that + // the resource entries are all packed together in the same ResTable_type struct and take up less + // space in the resources.arsc table. bool modified = false; for (std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { if (config_value->config.sdkVersion != 0 && @@ -137,8 +131,8 @@ static void CollapseVersions(int min_sdk, ResourceEntry* entry) { } if (modified) { - // We've modified the keys (ConfigDescription) by changing the sdkVersion to - // 0. We MUST re-sort to ensure ordering guarantees hold. + // We've modified the keys (ConfigDescription) by changing the sdkVersion to 0. We MUST re-sort + // to ensure ordering guarantees hold. std::sort(entry->values.begin(), entry->values.end(), [](const std::unique_ptr<ResourceConfigValue>& a, const std::unique_ptr<ResourceConfigValue>& b) -> bool { diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index 882a85b1c1bb..2d517c76944b 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -289,7 +289,7 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( const android::ResTable& table = assets_.getResources(false); const std::u16string package16 = util::Utf8ToUtf16(name.package); - const std::u16string type16 = util::Utf8ToUtf16(ToString(name.type)); + const std::u16string type16 = util::Utf8ToUtf16(to_string(name.type)); const std::u16string entry16 = util::Utf8ToUtf16(name.entry); uint32_t type_spec_flags = 0; diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h index 4a2181e0b344..b676efb8d025 100644 --- a/tools/aapt2/process/SymbolTable.h +++ b/tools/aapt2/process/SymbolTable.h @@ -199,6 +199,10 @@ class AssetManagerSymbolSource : public ISymbolSource { std::unique_ptr<SymbolTable::Symbol> FindByReference( const Reference& ref) override; + android::AssetManager* GetAssetManager() { + return &assets_; + } + private: android::AssetManager assets_; diff --git a/tools/aapt2/proto/ProtoHelpers.cpp b/tools/aapt2/proto/ProtoHelpers.cpp deleted file mode 100644 index aa99c982f6ae..000000000000 --- a/tools/aapt2/proto/ProtoHelpers.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2016 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 "proto/ProtoHelpers.h" - -namespace aapt { - -void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool) { - BigBuffer buffer(1024); - StringPool::FlattenUtf8(&buffer, pool); - - std::string* data = out_pb_pool->mutable_data(); - data->reserve(buffer.size()); - - size_t offset = 0; - for (const BigBuffer::Block& block : buffer) { - data->insert(data->begin() + offset, block.buffer.get(), block.buffer.get() + block.size); - offset += block.size; - } -} - -void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source* out_pb_source) { - StringPool::Ref ref = src_pool->MakeRef(source.path); - out_pb_source->set_path_idx(static_cast<uint32_t>(ref.index())); - if (source.line) { - out_pb_source->mutable_position()->set_line_number(static_cast<uint32_t>(source.line.value())); - } -} - -void DeserializeSourceFromPb(const pb::Source& pb_source, const android::ResStringPool& src_pool, - Source* out_source) { - if (pb_source.has_path_idx()) { - out_source->path = util::GetString(src_pool, pb_source.path_idx()); - } - - if (pb_source.has_position()) { - out_source->line = static_cast<size_t>(pb_source.position().line_number()); - } -} - -pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state) { - switch (state) { - case SymbolState::kPrivate: - return pb::SymbolStatus_Visibility_PRIVATE; - case SymbolState::kPublic: - return pb::SymbolStatus_Visibility_PUBLIC; - default: - break; - } - return pb::SymbolStatus_Visibility_UNKNOWN; -} - -SymbolState DeserializeVisibilityFromPb(pb::SymbolStatus_Visibility pb_visibility) { - switch (pb_visibility) { - case pb::SymbolStatus_Visibility_PRIVATE: - return SymbolState::kPrivate; - case pb::SymbolStatus_Visibility_PUBLIC: - return SymbolState::kPublic; - default: - break; - } - return SymbolState::kUndefined; -} - -void SerializeConfig(const ConfigDescription& config, pb::ConfigDescription* out_pb_config) { - android::ResTable_config flat_config = config; - flat_config.size = sizeof(flat_config); - flat_config.swapHtoD(); - out_pb_config->set_data(&flat_config, sizeof(flat_config)); -} - -bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config, - ConfigDescription* out_config) { - if (!pb_config.has_data()) { - return false; - } - - const android::ResTable_config* config; - if (pb_config.data().size() > sizeof(*config)) { - return false; - } - - config = reinterpret_cast<const android::ResTable_config*>(pb_config.data().data()); - out_config->copyFromDtoH(*config); - return true; -} - -pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type) { - switch (type) { - case Reference::Type::kResource: - return pb::Reference_Type_REFERENCE; - case Reference::Type::kAttribute: - return pb::Reference_Type_ATTRIBUTE; - default: - break; - } - return pb::Reference_Type_REFERENCE; -} - -Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type) { - switch (pb_type) { - case pb::Reference_Type_REFERENCE: - return Reference::Type::kResource; - case pb::Reference_Type_ATTRIBUTE: - return Reference::Type::kAttribute; - default: - break; - } - return Reference::Type::kResource; -} - -pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx) { - switch (plural_idx) { - case Plural::Zero: - return pb::Plural_Arity_ZERO; - case Plural::One: - return pb::Plural_Arity_ONE; - case Plural::Two: - return pb::Plural_Arity_TWO; - case Plural::Few: - return pb::Plural_Arity_FEW; - case Plural::Many: - return pb::Plural_Arity_MANY; - default: - break; - } - return pb::Plural_Arity_OTHER; -} - -size_t DeserializePluralEnumFromPb(pb::Plural_Arity arity) { - switch (arity) { - case pb::Plural_Arity_ZERO: - return Plural::Zero; - case pb::Plural_Arity_ONE: - return Plural::One; - case pb::Plural_Arity_TWO: - return Plural::Two; - case pb::Plural_Arity_FEW: - return Plural::Few; - case pb::Plural_Arity_MANY: - return Plural::Many; - default: - break; - } - return Plural::Other; -} - -} // namespace aapt diff --git a/tools/aapt2/proto/ProtoHelpers.h b/tools/aapt2/proto/ProtoHelpers.h deleted file mode 100644 index 2f268f44752c..000000000000 --- a/tools/aapt2/proto/ProtoHelpers.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2016 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 AAPT_PROTO_PROTOHELPERS_H -#define AAPT_PROTO_PROTOHELPERS_H - -#include "androidfw/ResourceTypes.h" - -#include "ConfigDescription.h" -#include "ResourceTable.h" -#include "Source.h" -#include "StringPool.h" -#include "Resources.pb.h" -#include "ResourcesInternal.pb.h" - -namespace aapt { - -void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool); - -void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source* out_pb_source); - -void DeserializeSourceFromPb(const pb::Source& pb_source, const android::ResStringPool& src_pool, - Source* out_source); - -pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state); - -SymbolState DeserializeVisibilityFromPb(pb::SymbolStatus_Visibility pb_visibility); - -void SerializeConfig(const ConfigDescription& config, pb::ConfigDescription* out_pb_config); - -bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config, - ConfigDescription* out_config); - -pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type); - -Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type); - -pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx); - -size_t DeserializePluralEnumFromPb(pb::Plural_Arity arity); - -} // namespace aapt - -#endif /* AAPT_PROTO_PROTOHELPERS_H */ diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h deleted file mode 100644 index 8c46642e9090..000000000000 --- a/tools/aapt2/proto/ProtoSerialize.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2016 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 AAPT_FLATTEN_TABLEPROTOSERIALIZER_H -#define AAPT_FLATTEN_TABLEPROTOSERIALIZER_H - -#include "android-base/macros.h" -#include "google/protobuf/io/coded_stream.h" -#include "google/protobuf/io/zero_copy_stream_impl_lite.h" - -#include "Diagnostics.h" -#include "ResourceTable.h" -#include "Source.h" -#include "proto/ProtoHelpers.h" - -namespace aapt { - -class CompiledFileOutputStream { - public: - explicit CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out); - - void WriteLittleEndian32(uint32_t value); - void WriteCompiledFile(const pb::internal::CompiledFile* compiledFile); - void WriteData(const BigBuffer* buffer); - void WriteData(const void* data, size_t len); - bool HadError(); - - private: - DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream); - - void EnsureAlignedWrite(); - - google::protobuf::io::CodedOutputStream out_; -}; - -class CompiledFileInputStream { - public: - explicit CompiledFileInputStream(const void* data, size_t size); - - bool ReadLittleEndian32(uint32_t* outVal); - bool ReadCompiledFile(pb::internal::CompiledFile* outVal); - bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen); - - private: - DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream); - - void EnsureAlignedRead(); - - google::protobuf::io::CodedInputStream in_; -}; - -std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table); -std::unique_ptr<ResourceTable> DeserializeTableFromPb(const pb::ResourceTable& pbTable, - const Source& source, IDiagnostics* diag); - -std::unique_ptr<pb::internal::CompiledFile> SerializeCompiledFileToPb(const ResourceFile& file); -std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb( - const pb::internal::CompiledFile& pbFile, const Source& source, IDiagnostics* diag); - -} // namespace aapt - -#endif /* AAPT_FLATTEN_TABLEPROTOSERIALIZER_H */ diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp deleted file mode 100644 index b9d5878f2f71..000000000000 --- a/tools/aapt2/proto/TableProtoDeserializer.cpp +++ /dev/null @@ -1,423 +0,0 @@ -/* - * Copyright (C) 2016 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 "proto/ProtoSerialize.h" - -#include "android-base/logging.h" -#include "androidfw/ResourceTypes.h" - -#include "ResourceTable.h" -#include "ResourceUtils.h" -#include "ValueVisitor.h" -#include "proto/ProtoHelpers.h" - -namespace aapt { - -namespace { - -class ReferenceIdToNameVisitor : public ValueVisitor { - public: - using ValueVisitor::Visit; - - explicit ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceNameRef>* mapping) - : mapping_(mapping) { - CHECK(mapping_ != nullptr); - } - - void Visit(Reference* reference) override { - if (!reference->id || !reference->id.value().is_valid()) { - return; - } - - ResourceId id = reference->id.value(); - auto cache_iter = mapping_->find(id); - if (cache_iter != mapping_->end()) { - reference->name = cache_iter->second.ToResourceName(); - } - } - - private: - const std::map<ResourceId, ResourceNameRef>* mapping_; -}; - -class PackagePbDeserializer { - public: - PackagePbDeserializer(const android::ResStringPool* sourcePool, const Source& source, - IDiagnostics* diag) - : source_pool_(sourcePool), source_(source), diag_(diag) { - } - - public: - bool DeserializeFromPb(const pb::Package& pb_package, ResourceTable* table) { - Maybe<uint8_t> id; - if (pb_package.has_package_id()) { - id = static_cast<uint8_t>(pb_package.package_id()); - } - - std::map<ResourceId, ResourceNameRef> id_index; - - ResourceTablePackage* pkg = table->CreatePackage(pb_package.package_name(), id); - for (const pb::Type& pb_type : pb_package.type()) { - const ResourceType* res_type = ParseResourceType(pb_type.name()); - if (res_type == nullptr) { - diag_->Error(DiagMessage(source_) << "unknown type '" << pb_type.name() << "'"); - return {}; - } - - ResourceTableType* type = pkg->FindOrCreateType(*res_type); - - for (const pb::Entry& pb_entry : pb_type.entry()) { - ResourceEntry* entry = type->FindOrCreateEntry(pb_entry.name()); - - // Deserialize the symbol status (public/private with source and comments). - if (pb_entry.has_symbol_status()) { - const pb::SymbolStatus& pb_status = pb_entry.symbol_status(); - if (pb_status.has_source()) { - DeserializeSourceFromPb(pb_status.source(), *source_pool_, - &entry->symbol_status.source); - } - - if (pb_status.has_comment()) { - entry->symbol_status.comment = pb_status.comment(); - } - - entry->symbol_status.allow_new = pb_status.allow_new(); - - SymbolState visibility = DeserializeVisibilityFromPb(pb_status.visibility()); - entry->symbol_status.state = visibility; - - if (visibility == SymbolState::kPublic) { - // This is a public symbol, we must encode the ID now if there is one. - if (pb_entry.has_id()) { - entry->id = static_cast<uint16_t>(pb_entry.id()); - } - - if (type->symbol_status.state != SymbolState::kPublic) { - // If the type has not been made public, do so now. - type->symbol_status.state = SymbolState::kPublic; - if (pb_type.has_id()) { - type->id = static_cast<uint8_t>(pb_type.id()); - } - } - } else if (visibility == SymbolState::kPrivate) { - if (type->symbol_status.state == SymbolState::kUndefined) { - type->symbol_status.state = SymbolState::kPrivate; - } - } - } - - ResourceId resid(pb_package.package_id(), pb_type.id(), pb_entry.id()); - if (resid.is_valid()) { - id_index[resid] = ResourceNameRef(pkg->name, type->type, entry->name); - } - - for (const pb::ConfigValue& pb_config_value : pb_entry.config_value()) { - const pb::ConfigDescription& pb_config = pb_config_value.config(); - - ConfigDescription config; - if (!DeserializeConfigDescriptionFromPb(pb_config, &config)) { - diag_->Error(DiagMessage(source_) << "invalid configuration"); - return {}; - } - - ResourceConfigValue* config_value = entry->FindOrCreateValue(config, pb_config.product()); - if (config_value->value) { - // Duplicate config. - diag_->Error(DiagMessage(source_) << "duplicate configuration"); - return {}; - } - - config_value->value = - DeserializeValueFromPb(pb_config_value.value(), config, &table->string_pool); - if (!config_value->value) { - return {}; - } - } - } - } - - ReferenceIdToNameVisitor visitor(&id_index); - VisitAllValuesInPackage(pkg, &visitor); - return true; - } - - private: - std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, - const ConfigDescription& config, StringPool* pool) { - if (pb_item.has_ref()) { - const pb::Reference& pb_ref = pb_item.ref(); - std::unique_ptr<Reference> ref = util::make_unique<Reference>(); - if (!DeserializeReferenceFromPb(pb_ref, ref.get())) { - return {}; - } - return std::move(ref); - - } else if (pb_item.has_prim()) { - const pb::Primitive& pb_prim = pb_item.prim(); - return util::make_unique<BinaryPrimitive>(static_cast<uint8_t>(pb_prim.type()), - pb_prim.data()); - - } else if (pb_item.has_id()) { - return util::make_unique<Id>(); - - } else if (pb_item.has_str()) { - return util::make_unique<String>( - pool->MakeRef(pb_item.str().value(), StringPool::Context(config))); - - } else if (pb_item.has_raw_str()) { - return util::make_unique<RawString>( - pool->MakeRef(pb_item.raw_str().value(), StringPool::Context(config))); - - } else if (pb_item.has_styled_str()) { - const pb::StyledString& pb_str = pb_item.styled_str(); - StyleString style_str{pb_str.value()}; - for (const pb::StyledString::Span& pb_span : pb_str.span()) { - style_str.spans.push_back(Span{pb_span.tag(), pb_span.first_char(), pb_span.last_char()}); - } - return util::make_unique<StyledString>(pool->MakeRef( - style_str, StringPool::Context(StringPool::Context::kNormalPriority, config))); - - } else if (pb_item.has_file()) { - return util::make_unique<FileReference>(pool->MakeRef( - pb_item.file().path(), StringPool::Context(StringPool::Context::kHighPriority, config))); - - } else { - diag_->Error(DiagMessage(source_) << "unknown item"); - } - return {}; - } - - std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value, - const ConfigDescription& config, - StringPool* pool) { - std::unique_ptr<Value> value; - if (pb_value.has_item()) { - value = DeserializeItemFromPb(pb_value.item(), config, pool); - if (!value) { - return {}; - } - - } else if (pb_value.has_compound_value()) { - const pb::CompoundValue& pb_compound_value = pb_value.compound_value(); - if (pb_compound_value.has_attr()) { - const pb::Attribute& pb_attr = pb_compound_value.attr(); - std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(); - attr->type_mask = pb_attr.format_flags(); - attr->min_int = pb_attr.min_int(); - attr->max_int = pb_attr.max_int(); - for (const pb::Attribute_Symbol& pb_symbol : pb_attr.symbol()) { - Attribute::Symbol symbol; - DeserializeItemCommon(pb_symbol, &symbol.symbol); - if (!DeserializeReferenceFromPb(pb_symbol.name(), &symbol.symbol)) { - return {}; - } - symbol.value = pb_symbol.value(); - attr->symbols.push_back(std::move(symbol)); - } - value = std::move(attr); - - } else if (pb_compound_value.has_style()) { - const pb::Style& pb_style = pb_compound_value.style(); - std::unique_ptr<Style> style = util::make_unique<Style>(); - if (pb_style.has_parent()) { - style->parent = Reference(); - if (!DeserializeReferenceFromPb(pb_style.parent(), &style->parent.value())) { - return {}; - } - - if (pb_style.has_parent_source()) { - Source parent_source; - DeserializeSourceFromPb(pb_style.parent_source(), *source_pool_, &parent_source); - style->parent.value().SetSource(std::move(parent_source)); - } - } - - for (const pb::Style_Entry& pb_entry : pb_style.entry()) { - Style::Entry entry; - DeserializeItemCommon(pb_entry, &entry.key); - if (!DeserializeReferenceFromPb(pb_entry.key(), &entry.key)) { - return {}; - } - - entry.value = DeserializeItemFromPb(pb_entry.item(), config, pool); - if (!entry.value) { - return {}; - } - - DeserializeItemCommon(pb_entry, entry.value.get()); - style->entries.push_back(std::move(entry)); - } - value = std::move(style); - - } else if (pb_compound_value.has_styleable()) { - const pb::Styleable& pb_styleable = pb_compound_value.styleable(); - std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); - for (const pb::Styleable_Entry& pb_entry : pb_styleable.entry()) { - Reference attr_ref; - DeserializeItemCommon(pb_entry, &attr_ref); - DeserializeReferenceFromPb(pb_entry.attr(), &attr_ref); - styleable->entries.push_back(std::move(attr_ref)); - } - value = std::move(styleable); - - } else if (pb_compound_value.has_array()) { - const pb::Array& pb_array = pb_compound_value.array(); - std::unique_ptr<Array> array = util::make_unique<Array>(); - for (const pb::Array_Element& pb_entry : pb_array.element()) { - std::unique_ptr<Item> item = DeserializeItemFromPb(pb_entry.item(), config, pool); - if (!item) { - return {}; - } - - DeserializeItemCommon(pb_entry, item.get()); - array->elements.push_back(std::move(item)); - } - value = std::move(array); - - } else if (pb_compound_value.has_plural()) { - const pb::Plural& pb_plural = pb_compound_value.plural(); - std::unique_ptr<Plural> plural = util::make_unique<Plural>(); - for (const pb::Plural_Entry& pb_entry : pb_plural.entry()) { - size_t pluralIdx = DeserializePluralEnumFromPb(pb_entry.arity()); - plural->values[pluralIdx] = DeserializeItemFromPb(pb_entry.item(), config, pool); - if (!plural->values[pluralIdx]) { - return {}; - } - - DeserializeItemCommon(pb_entry, plural->values[pluralIdx].get()); - } - value = std::move(plural); - - } else { - diag_->Error(DiagMessage(source_) << "unknown compound value"); - return {}; - } - } else { - diag_->Error(DiagMessage(source_) << "unknown value"); - return {}; - } - - CHECK(value) << "forgot to set value"; - - value->SetWeak(pb_value.weak()); - DeserializeItemCommon(pb_value, value.get()); - return value; - } - - bool DeserializeReferenceFromPb(const pb::Reference& pb_ref, Reference* out_ref) { - out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type()); - out_ref->private_reference = pb_ref.private_(); - - if (pb_ref.has_id()) { - out_ref->id = ResourceId(pb_ref.id()); - } - - if (pb_ref.has_name()) { - ResourceNameRef name_ref; - if (!ResourceUtils::ParseResourceName(pb_ref.name(), &name_ref, nullptr)) { - diag_->Error(DiagMessage(source_) << "invalid reference name '" << pb_ref.name() << "'"); - return false; - } - - out_ref->name = name_ref.ToResourceName(); - } - return true; - } - - template <typename T> - void DeserializeItemCommon(const T& pb_item, Value* out_value) { - if (pb_item.has_source()) { - Source source; - DeserializeSourceFromPb(pb_item.source(), *source_pool_, &source); - out_value->SetSource(std::move(source)); - } - - if (pb_item.has_comment()) { - out_value->SetComment(pb_item.comment()); - } - } - - private: - const android::ResStringPool* source_pool_; - const Source source_; - IDiagnostics* diag_; -}; - -} // namespace - -std::unique_ptr<ResourceTable> DeserializeTableFromPb(const pb::ResourceTable& pb_table, - const Source& source, IDiagnostics* diag) { - // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which - // causes errors when qualifying it with android:: - using namespace android; - - std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); - - ResStringPool source_pool; - if (pb_table.has_source_pool()) { - status_t result = source_pool.setTo(pb_table.source_pool().data().data(), - pb_table.source_pool().data().size()); - if (result != NO_ERROR) { - diag->Error(DiagMessage(source) << "invalid source pool"); - return {}; - } - } - - PackagePbDeserializer package_pb_deserializer(&source_pool, source, diag); - for (const pb::Package& pb_package : pb_table.package()) { - if (!package_pb_deserializer.DeserializeFromPb(pb_package, table.get())) { - return {}; - } - } - return table; -} - -std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb( - const pb::internal::CompiledFile& pb_file, const Source& source, IDiagnostics* diag) { - std::unique_ptr<ResourceFile> file = util::make_unique<ResourceFile>(); - - ResourceNameRef name_ref; - - // Need to create an lvalue here so that nameRef can point to something real. - if (!ResourceUtils::ParseResourceName(pb_file.resource_name(), &name_ref)) { - diag->Error(DiagMessage(source) - << "invalid resource name in compiled file header: " - << pb_file.resource_name()); - return {}; - } - file->name = name_ref.ToResourceName(); - file->source.path = pb_file.source_path(); - DeserializeConfigDescriptionFromPb(pb_file.config(), &file->config); - - for (const pb::internal::CompiledFile_Symbol& pb_symbol : pb_file.exported_symbol()) { - // Need to create an lvalue here so that nameRef can point to something real. - if (!ResourceUtils::ParseResourceName(pb_symbol.resource_name(), &name_ref)) { - diag->Error(DiagMessage(source) - << "invalid resource name for exported symbol in " - "compiled file header: " - << pb_file.resource_name()); - return {}; - } - size_t line = 0u; - if (pb_symbol.has_source()) { - line = pb_symbol.source().line_number(); - } - file->exported_symbols.push_back(SourcedResourceName{name_ref.ToResourceName(), line}); - } - return file; -} - -} // namespace aapt diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp deleted file mode 100644 index a08df71eae1e..000000000000 --- a/tools/aapt2/proto/TableProtoSerializer.cpp +++ /dev/null @@ -1,399 +0,0 @@ -/* - * Copyright (C) 2016 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 "Resource.h" -#include "ResourceTable.h" -#include "StringPool.h" -#include "ValueVisitor.h" -#include "proto/ProtoHelpers.h" -#include "proto/ProtoSerialize.h" -#include "util/BigBuffer.h" - -#include "android-base/logging.h" - -using ::google::protobuf::io::CodedInputStream; -using ::google::protobuf::io::CodedOutputStream; -using ::google::protobuf::io::ZeroCopyOutputStream; - -namespace aapt { - -namespace { - -class PbSerializerVisitor : public RawValueVisitor { - public: - using RawValueVisitor::Visit; - - // Constructor to use when expecting to serialize any value. - PbSerializerVisitor(StringPool* source_pool, pb::Value* out_pb_value) - : source_pool_(source_pool), out_pb_value_(out_pb_value), out_pb_item_(nullptr) { - } - - // Constructor to use when expecting to serialize an Item. - PbSerializerVisitor(StringPool* sourcePool, pb::Item* outPbItem) - : source_pool_(sourcePool), out_pb_value_(nullptr), out_pb_item_(outPbItem) { - } - - void Visit(Reference* ref) override { - SerializeReferenceToPb(*ref, pb_item()->mutable_ref()); - } - - void Visit(String* str) override { - pb_item()->mutable_str()->set_value(*str->value); - } - - void Visit(RawString* str) override { - pb_item()->mutable_raw_str()->set_value(*str->value); - } - - void Visit(StyledString* str) override { - pb::StyledString* pb_str = pb_item()->mutable_styled_str(); - pb_str->set_value(str->value->value); - - for (const StringPool::Span& span : str->value->spans) { - pb::StyledString::Span* pb_span = pb_str->add_span(); - pb_span->set_tag(*span.name); - pb_span->set_first_char(span.first_char); - pb_span->set_last_char(span.last_char); - } - } - - void Visit(FileReference* file) override { - pb_item()->mutable_file()->set_path(*file->path); - } - - void Visit(Id* /*id*/) override { - pb_item()->mutable_id(); - } - - void Visit(BinaryPrimitive* prim) override { - android::Res_value val = {}; - prim->Flatten(&val); - - pb::Primitive* pb_prim = pb_item()->mutable_prim(); - pb_prim->set_type(val.dataType); - pb_prim->set_data(val.data); - } - - void VisitItem(Item* item) override { - LOG(FATAL) << "unimplemented item"; - } - - void Visit(Attribute* attr) override { - pb::Attribute* pb_attr = pb_compound_value()->mutable_attr(); - pb_attr->set_format_flags(attr->type_mask); - pb_attr->set_min_int(attr->min_int); - pb_attr->set_max_int(attr->max_int); - - for (auto& symbol : attr->symbols) { - pb::Attribute_Symbol* pb_symbol = pb_attr->add_symbol(); - SerializeItemCommonToPb(symbol.symbol, pb_symbol); - SerializeReferenceToPb(symbol.symbol, pb_symbol->mutable_name()); - pb_symbol->set_value(symbol.value); - } - } - - void Visit(Style* style) override { - pb::Style* pb_style = pb_compound_value()->mutable_style(); - if (style->parent) { - SerializeReferenceToPb(style->parent.value(), pb_style->mutable_parent()); - SerializeSourceToPb(style->parent.value().GetSource(), source_pool_, - pb_style->mutable_parent_source()); - } - - for (Style::Entry& entry : style->entries) { - pb::Style_Entry* pb_entry = pb_style->add_entry(); - SerializeReferenceToPb(entry.key, pb_entry->mutable_key()); - - pb::Item* pb_item = pb_entry->mutable_item(); - SerializeItemCommonToPb(entry.key, pb_entry); - PbSerializerVisitor sub_visitor(source_pool_, pb_item); - entry.value->Accept(&sub_visitor); - } - } - - void Visit(Styleable* styleable) override { - pb::Styleable* pb_styleable = pb_compound_value()->mutable_styleable(); - for (Reference& entry : styleable->entries) { - pb::Styleable_Entry* pb_entry = pb_styleable->add_entry(); - SerializeItemCommonToPb(entry, pb_entry); - SerializeReferenceToPb(entry, pb_entry->mutable_attr()); - } - } - - void Visit(Array* array) override { - pb::Array* pb_array = pb_compound_value()->mutable_array(); - for (auto& value : array->elements) { - pb::Array_Element* pb_element = pb_array->add_element(); - SerializeItemCommonToPb(*value, pb_element); - PbSerializerVisitor sub_visitor(source_pool_, pb_element->mutable_item()); - value->Accept(&sub_visitor); - } - } - - void Visit(Plural* plural) override { - pb::Plural* pb_plural = pb_compound_value()->mutable_plural(); - const size_t count = plural->values.size(); - for (size_t i = 0; i < count; i++) { - if (!plural->values[i]) { - // No plural value set here. - continue; - } - - pb::Plural_Entry* pb_entry = pb_plural->add_entry(); - pb_entry->set_arity(SerializePluralEnumToPb(i)); - pb::Item* pb_element = pb_entry->mutable_item(); - SerializeItemCommonToPb(*plural->values[i], pb_entry); - PbSerializerVisitor sub_visitor(source_pool_, pb_element); - plural->values[i]->Accept(&sub_visitor); - } - } - - private: - DISALLOW_COPY_AND_ASSIGN(PbSerializerVisitor); - - pb::Item* pb_item() { - if (out_pb_value_) { - return out_pb_value_->mutable_item(); - } - return out_pb_item_; - } - - pb::CompoundValue* pb_compound_value() { - CHECK(out_pb_value_ != nullptr); - return out_pb_value_->mutable_compound_value(); - } - - template <typename T> - void SerializeItemCommonToPb(const Item& item, T* pb_item) { - SerializeSourceToPb(item.GetSource(), source_pool_, pb_item->mutable_source()); - if (!item.GetComment().empty()) { - pb_item->set_comment(item.GetComment()); - } - } - - void SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref) { - if (ref.id) { - pb_ref->set_id(ref.id.value().id); - } - - if (ref.name) { - pb_ref->set_name(ref.name.value().ToString()); - } - - pb_ref->set_private_(ref.private_reference); - pb_ref->set_type(SerializeReferenceTypeToPb(ref.reference_type)); - } - - StringPool* source_pool_; - pb::Value* out_pb_value_; - pb::Item* out_pb_item_; -}; - -} // namespace - -std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) { - // We must do this before writing the resources, since the string pool IDs may change. - table->string_pool.Prune(); - table->string_pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int { - int diff = util::compare(a.priority, b.priority); - if (diff == 0) { - diff = a.config.compare(b.config); - } - return diff; - }); - - auto pb_table = util::make_unique<pb::ResourceTable>(); - StringPool source_pool; - - for (auto& package : table->packages) { - pb::Package* pb_package = pb_table->add_package(); - if (package->id) { - pb_package->set_package_id(package->id.value()); - } - pb_package->set_package_name(package->name); - - for (auto& type : package->types) { - pb::Type* pb_type = pb_package->add_type(); - if (type->id) { - pb_type->set_id(type->id.value()); - } - pb_type->set_name(ToString(type->type).to_string()); - - for (auto& entry : type->entries) { - pb::Entry* pb_entry = pb_type->add_entry(); - if (entry->id) { - pb_entry->set_id(entry->id.value()); - } - pb_entry->set_name(entry->name); - - // Write the SymbolStatus struct. - pb::SymbolStatus* pb_status = pb_entry->mutable_symbol_status(); - pb_status->set_visibility(SerializeVisibilityToPb(entry->symbol_status.state)); - SerializeSourceToPb(entry->symbol_status.source, &source_pool, pb_status->mutable_source()); - pb_status->set_comment(entry->symbol_status.comment); - pb_status->set_allow_new(entry->symbol_status.allow_new); - - for (auto& config_value : entry->values) { - pb::ConfigValue* pb_config_value = pb_entry->add_config_value(); - SerializeConfig(config_value->config, pb_config_value->mutable_config()); - if (!config_value->product.empty()) { - pb_config_value->mutable_config()->set_product(config_value->product); - } - - pb::Value* pb_value = pb_config_value->mutable_value(); - SerializeSourceToPb(config_value->value->GetSource(), &source_pool, - pb_value->mutable_source()); - if (!config_value->value->GetComment().empty()) { - pb_value->set_comment(config_value->value->GetComment()); - } - - if (config_value->value->IsWeak()) { - pb_value->set_weak(true); - } - - PbSerializerVisitor visitor(&source_pool, pb_value); - config_value->value->Accept(&visitor); - } - } - } - } - - SerializeStringPoolToPb(source_pool, pb_table->mutable_source_pool()); - return pb_table; -} - -std::unique_ptr<pb::internal::CompiledFile> SerializeCompiledFileToPb(const ResourceFile& file) { - auto pb_file = util::make_unique<pb::internal::CompiledFile>(); - pb_file->set_resource_name(file.name.ToString()); - pb_file->set_source_path(file.source.path); - SerializeConfig(file.config, pb_file->mutable_config()); - - for (const SourcedResourceName& exported : file.exported_symbols) { - pb::internal::CompiledFile_Symbol* pb_symbol = pb_file->add_exported_symbol(); - pb_symbol->set_resource_name(exported.name.ToString()); - pb_symbol->mutable_source()->set_line_number(exported.line); - } - return pb_file; -} - -CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out) : out_(out) { -} - -void CompiledFileOutputStream::EnsureAlignedWrite() { - const int padding = out_.ByteCount() % 4; - if (padding > 0) { - uint32_t zero = 0u; - out_.WriteRaw(&zero, padding); - } -} - -void CompiledFileOutputStream::WriteLittleEndian32(uint32_t val) { - EnsureAlignedWrite(); - out_.WriteLittleEndian32(val); -} - -void CompiledFileOutputStream::WriteCompiledFile(const pb::internal::CompiledFile* compiled_file) { - EnsureAlignedWrite(); - out_.WriteLittleEndian64(static_cast<uint64_t>(compiled_file->ByteSize())); - compiled_file->SerializeWithCachedSizes(&out_); -} - -void CompiledFileOutputStream::WriteData(const BigBuffer* buffer) { - EnsureAlignedWrite(); - out_.WriteLittleEndian64(static_cast<uint64_t>(buffer->size())); - for (const BigBuffer::Block& block : *buffer) { - out_.WriteRaw(block.buffer.get(), block.size); - } -} - -void CompiledFileOutputStream::WriteData(const void* data, size_t len) { - EnsureAlignedWrite(); - out_.WriteLittleEndian64(static_cast<uint64_t>(len)); - out_.WriteRaw(data, len); -} - -bool CompiledFileOutputStream::HadError() { - return out_.HadError(); -} - -CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size) - : in_(static_cast<const uint8_t*>(data), size) {} - -void CompiledFileInputStream::EnsureAlignedRead() { - const int padding = in_.CurrentPosition() % 4; - if (padding > 0) { - // Reads are always 4 byte aligned. - in_.Skip(padding); - } -} - -bool CompiledFileInputStream::ReadLittleEndian32(uint32_t* out_val) { - EnsureAlignedRead(); - return in_.ReadLittleEndian32(out_val); -} - -bool CompiledFileInputStream::ReadCompiledFile(pb::internal::CompiledFile* out_val) { - EnsureAlignedRead(); - - google::protobuf::uint64 pb_size = 0u; - if (!in_.ReadLittleEndian64(&pb_size)) { - return false; - } - - CodedInputStream::Limit l = in_.PushLimit(static_cast<int>(pb_size)); - - // Check that we haven't tried to read past the end. - if (static_cast<uint64_t>(in_.BytesUntilLimit()) != pb_size) { - in_.PopLimit(l); - in_.PushLimit(0); - return false; - } - - if (!out_val->ParsePartialFromCodedStream(&in_)) { - in_.PopLimit(l); - in_.PushLimit(0); - return false; - } - - in_.PopLimit(l); - return true; -} - -bool CompiledFileInputStream::ReadDataMetaData(uint64_t* out_offset, uint64_t* out_len) { - EnsureAlignedRead(); - - google::protobuf::uint64 pb_size = 0u; - if (!in_.ReadLittleEndian64(&pb_size)) { - return false; - } - - // Check that we aren't trying to read past the end. - if (pb_size > static_cast<uint64_t>(in_.BytesUntilLimit())) { - in_.PushLimit(0); - return false; - } - - uint64_t offset = static_cast<uint64_t>(in_.CurrentPosition()); - if (!in_.Skip(pb_size)) { - return false; - } - - *out_offset = offset; - *out_len = pb_size; - return true; -} - -} // namespace aapt diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp deleted file mode 100644 index 80608b3d9c05..000000000000 --- a/tools/aapt2/proto/TableProtoSerializer_test.cpp +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (C) 2016 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 "proto/ProtoSerialize.h" - -#include "ResourceTable.h" -#include "test/Test.h" - -using ::google::protobuf::io::StringOutputStream; -using ::testing::Eq; -using ::testing::NotNull; -using ::testing::SizeIs; - -namespace aapt { - -TEST(TableProtoSerializer, SerializeSinglePackage) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - std::unique_ptr<ResourceTable> table = - test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) - .AddFileReference("com.app.a:layout/main", ResourceId(0x7f020000), "res/layout/main.xml") - .AddReference("com.app.a:layout/other", ResourceId(0x7f020001), "com.app.a:layout/main") - .AddString("com.app.a:string/text", {}, "hi") - .AddValue("com.app.a:id/foo", {}, util::make_unique<Id>()) - .SetSymbolState("com.app.a:bool/foo", {}, SymbolState::kUndefined, true /*allow_new*/) - .Build(); - - Symbol public_symbol; - public_symbol.state = SymbolState::kPublic; - ASSERT_TRUE(table->SetSymbolState(test::ParseNameOrDie("com.app.a:layout/main"), - ResourceId(0x7f020000), public_symbol, - context->GetDiagnostics())); - - Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo"); - ASSERT_THAT(id, NotNull()); - - // Make a plural. - std::unique_ptr<Plural> plural = util::make_unique<Plural>(); - plural->values[Plural::One] = util::make_unique<String>(table->string_pool.MakeRef("one")); - ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:plurals/hey"), - ConfigDescription{}, {}, std::move(plural), - context->GetDiagnostics())); - - // Make a styled string. - StyleString style_string; - style_string.str = "hello"; - style_string.spans.push_back(Span{"b", 0u, 4u}); - ASSERT_TRUE( - table->AddResource(test::ParseNameOrDie("com.app.a:string/styled"), ConfigDescription{}, {}, - util::make_unique<StyledString>(table->string_pool.MakeRef(style_string)), - context->GetDiagnostics())); - - // Make a resource with different products. - ASSERT_TRUE(table->AddResource( - test::ParseNameOrDie("com.app.a:integer/one"), - test::ParseConfigOrDie("land"), {}, - test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 123u), - context->GetDiagnostics())); - ASSERT_TRUE(table->AddResource( - test::ParseNameOrDie("com.app.a:integer/one"), - test::ParseConfigOrDie("land"), "tablet", - test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 321u), - context->GetDiagnostics())); - - // Make a reference with both resource name and resource ID. - // The reference should point to a resource outside of this table to test that both name and id - // get serialized. - Reference expected_ref; - expected_ref.name = test::ParseNameOrDie("android:layout/main"); - expected_ref.id = ResourceId(0x01020000); - ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:layout/abc"), - ConfigDescription::DefaultConfig(), {}, - util::make_unique<Reference>(expected_ref), - context->GetDiagnostics())); - - std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table.get()); - ASSERT_THAT(pb_table, NotNull()); - - std::unique_ptr<ResourceTable> new_table = DeserializeTableFromPb( - *pb_table, Source{"test"}, context->GetDiagnostics()); - ASSERT_THAT(new_table, NotNull()); - - Id* new_id = test::GetValue<Id>(new_table.get(), "com.app.a:id/foo"); - ASSERT_THAT(new_id, NotNull()); - EXPECT_THAT(new_id->IsWeak(), Eq(id->IsWeak())); - - Maybe<ResourceTable::SearchResult> result = - new_table->FindResource(test::ParseNameOrDie("com.app.a:layout/main")); - ASSERT_TRUE(result); - - EXPECT_THAT(result.value().type->symbol_status.state, Eq(SymbolState::kPublic)); - EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic)); - - result = new_table->FindResource(test::ParseNameOrDie("com.app.a:bool/foo")); - ASSERT_TRUE(result); - EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kUndefined)); - EXPECT_TRUE(result.value().entry->symbol_status.allow_new); - - // Find the product-dependent values - BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( - new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), ""); - ASSERT_THAT(prim, NotNull()); - EXPECT_THAT(prim->value.data, Eq(123u)); - - prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( - new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), "tablet"); - ASSERT_THAT(prim, NotNull()); - EXPECT_THAT(prim->value.data, Eq(321u)); - - Reference* actual_ref = test::GetValue<Reference>(new_table.get(), "com.app.a:layout/abc"); - ASSERT_THAT(actual_ref, NotNull()); - ASSERT_TRUE(actual_ref->name); - ASSERT_TRUE(actual_ref->id); - EXPECT_THAT(*actual_ref, Eq(expected_ref)); - - StyledString* actual_styled_str = - test::GetValue<StyledString>(new_table.get(), "com.app.a:string/styled"); - ASSERT_THAT(actual_styled_str, NotNull()); - EXPECT_THAT(actual_styled_str->value->value, Eq("hello")); - ASSERT_THAT(actual_styled_str->value->spans, SizeIs(1u)); - EXPECT_THAT(*actual_styled_str->value->spans[0].name, Eq("b")); - EXPECT_THAT(actual_styled_str->value->spans[0].first_char, Eq(0u)); - EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u)); -} - -TEST(TableProtoSerializer, SerializeFileHeader) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - - ResourceFile f; - f.config = test::ParseConfigOrDie("hdpi-v9"); - f.name = test::ParseNameOrDie("com.app.a:layout/main"); - f.source.path = "res/layout-hdpi-v9/main.xml"; - f.exported_symbols.push_back( - SourcedResourceName{test::ParseNameOrDie("id/unchecked"), 23u}); - - const std::string expected_data1 = "123"; - const std::string expected_data2 = "1234"; - - std::string output_str; - { - std::unique_ptr<pb::internal::CompiledFile> pb_file1 = SerializeCompiledFileToPb(f); - - f.name.entry = "__" + f.name.entry + "$0"; - std::unique_ptr<pb::internal::CompiledFile> pb_file2 = SerializeCompiledFileToPb(f); - - StringOutputStream out_stream(&output_str); - CompiledFileOutputStream out_file_stream(&out_stream); - out_file_stream.WriteLittleEndian32(2); - out_file_stream.WriteCompiledFile(pb_file1.get()); - out_file_stream.WriteData(expected_data1.data(), expected_data1.size()); - out_file_stream.WriteCompiledFile(pb_file2.get()); - out_file_stream.WriteData(expected_data2.data(), expected_data2.size()); - ASSERT_FALSE(out_file_stream.HadError()); - } - - CompiledFileInputStream in_file_stream(output_str.data(), output_str.size()); - uint32_t num_files = 0; - ASSERT_TRUE(in_file_stream.ReadLittleEndian32(&num_files)); - ASSERT_EQ(2u, num_files); - - // Read the first compiled file. - - pb::internal::CompiledFile new_pb_file; - ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_file)); - - std::unique_ptr<ResourceFile> file = DeserializeCompiledFileFromPb( - new_pb_file, Source("test"), context->GetDiagnostics()); - ASSERT_THAT(file, NotNull()); - - uint64_t offset, len; - ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len)); - - std::string actual_data(output_str.data() + offset, len); - EXPECT_EQ(expected_data1, actual_data); - - // Expect the data to be aligned. - EXPECT_EQ(0u, offset & 0x03); - - ASSERT_EQ(1u, file->exported_symbols.size()); - EXPECT_EQ(test::ParseNameOrDie("id/unchecked"), file->exported_symbols[0].name); - - // Read the second compiled file. - - ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_file)); - - file = DeserializeCompiledFileFromPb(new_pb_file, Source("test"), context->GetDiagnostics()); - ASSERT_THAT(file, NotNull()); - - ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len)); - - actual_data = std::string(output_str.data() + offset, len); - EXPECT_EQ(expected_data2, actual_data); - - // Expect the data to be aligned. - EXPECT_EQ(0u, offset & 0x03); -} - -TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) { - ResourceFile f; - std::unique_ptr<pb::internal::CompiledFile> pb_file = SerializeCompiledFileToPb(f); - - const std::string expected_data = "1234"; - - std::string output_str; - { - StringOutputStream out_stream(&output_str); - CompiledFileOutputStream out_file_stream(&out_stream); - out_file_stream.WriteLittleEndian32(1); - out_file_stream.WriteCompiledFile(pb_file.get()); - out_file_stream.WriteData(expected_data.data(), expected_data.size()); - ASSERT_FALSE(out_file_stream.HadError()); - } - - output_str[4] = 0xff; - - CompiledFileInputStream in_file_stream(output_str.data(), output_str.size()); - - uint32_t num_files = 0; - EXPECT_TRUE(in_file_stream.ReadLittleEndian32(&num_files)); - EXPECT_EQ(1u, num_files); - - pb::internal::CompiledFile new_pb_file; - EXPECT_FALSE(in_file_stream.ReadCompiledFile(&new_pb_file)); - - uint64_t offset, len; - EXPECT_FALSE(in_file_stream.ReadDataMetaData(&offset, &len)); -} - -} // namespace aapt diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp index 27e13d81ff49..9d49ca6c0aa9 100644 --- a/tools/aapt2/split/TableSplitter.cpp +++ b/tools/aapt2/split/TableSplitter.cpp @@ -32,8 +32,7 @@ namespace aapt { using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>; -using ConfigDensityGroups = - std::map<ConfigDescription, std::vector<ResourceConfigValue*>>; +using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>; static ConfigDescription CopyWithoutDensity(const ConfigDescription& config) { ConfigDescription without_density = config; @@ -51,8 +50,7 @@ class SplitValueSelector { if (config.density == 0) { density_independent_configs_.insert(config); } else { - density_dependent_config_to_density_map_[CopyWithoutDensity(config)] = - config.density; + density_dependent_config_to_density_map_[CopyWithoutDensity(config)] = config.density; } } } @@ -94,9 +92,7 @@ class SplitValueSelector { ResourceConfigValue* best_value = nullptr; for (ResourceConfigValue* this_value : related_values) { - if (!best_value || - this_value->config.isBetterThan(best_value->config, - &target_density)) { + if (!best_value || this_value->config.isBetterThan(best_value->config, &target_density)) { best_value = this_value; } } @@ -120,9 +116,8 @@ class SplitValueSelector { }; /** - * Marking non-preferred densities as claimed will make sure the base doesn't - * include them, - * leaving only the preferred density behind. + * Marking non-preferred densities as claimed will make sure the base doesn't include them, leaving + * only the preferred density behind. */ static void MarkNonPreferredDensitiesAsClaimed( const std::vector<uint16_t>& preferred_densities, const ConfigDensityGroups& density_groups, @@ -161,8 +156,7 @@ bool TableSplitter::VerifySplitConstraints(IAaptContext* context) { for (size_t i = 0; i < split_constraints_.size(); i++) { for (size_t j = i + 1; j < split_constraints_.size(); j++) { for (const ConfigDescription& config : split_constraints_[i].configs) { - if (split_constraints_[j].configs.find(config) != - split_constraints_[j].configs.end()) { + if (split_constraints_[j].configs.find(config) != split_constraints_[j].configs.end()) { context->GetDiagnostics()->Error(DiagMessage() << "config '" << config << "' appears in multiple splits, " @@ -193,28 +187,22 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { for (auto& entry : type->entries) { if (options_.config_filter) { // First eliminate any resource that we definitely don't want. - for (std::unique_ptr<ResourceConfigValue>& config_value : - entry->values) { + for (std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { if (!options_.config_filter->Match(config_value->config)) { - // null out the entry. We will clean up and remove nulls at the - // end for performance reasons. + // null out the entry. We will clean up and remove nulls at the end for performance + // reasons. config_value.reset(); } } } - // Organize the values into two separate buckets. Those that are - // density-dependent - // and those that are density-independent. - // One density technically matches all density, it's just that some - // densities - // match better. So we need to be aware of the full set of densities to - // make this - // decision. + // Organize the values into two separate buckets. Those that are density-dependent and those + // that are density-independent. One density technically matches all density, it's just that + // some densities match better. So we need to be aware of the full set of densities to make + // this decision. ConfigDensityGroups density_groups; ConfigClaimedMap config_claimed_map; - for (const std::unique_ptr<ResourceConfigValue>& config_value : - entry->values) { + for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { if (config_value) { config_claimed_map[config_value.get()] = false; @@ -226,9 +214,8 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { } } - // First we check all the splits. If it doesn't match one of the splits, - // we - // leave it in the base. + // First we check all the splits. If it doesn't match one of the splits, we leave it in the + // base. for (size_t idx = 0; idx < split_count; idx++) { const SplitConstraints& split_constraint = split_constraints_[idx]; ResourceTable* split_table = splits_[idx].get(); @@ -240,20 +227,16 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { // No need to do any work if we selected nothing. if (!selected_values.empty()) { - // Create the same resource structure in the split. We do this - // lazily because we might not have actual values for each - // type/entry. - ResourceTablePackage* split_pkg = - split_table->FindPackage(pkg->name); - ResourceTableType* split_type = - split_pkg->FindOrCreateType(type->type); + // Create the same resource structure in the split. We do this lazily because we might + // not have actual values for each type/entry. + ResourceTablePackage* split_pkg = split_table->FindPackage(pkg->name); + ResourceTableType* split_type = split_pkg->FindOrCreateType(type->type); if (!split_type->id) { split_type->id = type->id; split_type->symbol_status = type->symbol_status; } - ResourceEntry* split_entry = - split_type->FindOrCreateEntry(entry->name); + ResourceEntry* split_entry = split_type->FindOrCreateEntry(entry->name); if (!split_entry->id) { split_entry->id = entry->id; split_entry->symbol_status = entry->symbol_status; @@ -262,8 +245,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { // Copy the selected values into the new Split Entry. for (ResourceConfigValue* config_value : selected_values) { ResourceConfigValue* new_config_value = - split_entry->FindOrCreateValue(config_value->config, - config_value->product); + split_entry->FindOrCreateValue(config_value->config, config_value->product); new_config_value->value = std::unique_ptr<Value>( config_value->value->Clone(&split_table->string_pool)); } @@ -276,11 +258,9 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { &config_claimed_map); } - // All splits are handled, now check to see what wasn't claimed and - // remove - // whatever exists in other splits. - for (std::unique_ptr<ResourceConfigValue>& config_value : - entry->values) { + // All splits are handled, now check to see what wasn't claimed and remove whatever exists + // in other splits. + for (std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { if (config_value && config_claimed_map[config_value.get()]) { // Claimed, remove from base. config_value.reset(); diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp index 8f9788e8a25d..ecec63f3cf31 100644 --- a/tools/aapt2/test/Builders.cpp +++ b/tools/aapt2/test/Builders.cpp @@ -19,10 +19,13 @@ #include "android-base/logging.h" #include "androidfw/StringPiece.h" -#include "io/StringInputStream.h" +#include "io/StringStream.h" #include "test/Common.h" #include "util/Util.h" +using ::aapt::configuration::Abi; +using ::aapt::configuration::AndroidSdk; +using ::aapt::configuration::Artifact; using ::aapt::io::StringInputStream; using ::android::StringPiece; @@ -75,21 +78,27 @@ ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, c } ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name, - const StringPiece& path) { - return AddFileReference(name, {}, path); + const StringPiece& path, + io::IFile* file) { + return AddFileReference(name, {}, path, file); } ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name, const ResourceId& id, - const StringPiece& path) { - return AddValue(name, id, util::make_unique<FileReference>(table_->string_pool.MakeRef(path))); + const StringPiece& path, + io::IFile* file) { + auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path)); + file_ref->file = file; + return AddValue(name, id, std::move(file_ref)); } ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name, const StringPiece& path, - const ConfigDescription& config) { - return AddValue(name, config, {}, - util::make_unique<FileReference>(table_->string_pool.MakeRef(path))); + const ConfigDescription& config, + io::IFile* file) { + auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path)); + file_ref->file = file; + return AddValue(name, config, {}, std::move(file_ref)); } ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, @@ -212,5 +221,74 @@ std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* contex return doc; } +PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetAbiGroup( + const std::string& name, const std::vector<Abi>& abis) { + config_.abi_groups[name] = abis; + return *this; +} + +PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetLocaleGroup( + const std::string& name, const std::vector<std::string>& locales) { + auto& group = config_.locale_groups[name]; + for (const auto& locale : locales) { + group.push_back(ParseConfigOrDie(locale)); + } + return *this; +} + +PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetDensityGroup( + const std::string& name, const std::vector<std::string>& densities) { + auto& group = config_.screen_density_groups[name]; + for (const auto& density : densities) { + group.push_back(ParseConfigOrDie(density)); + } + return *this; +} + +PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetAndroidSdk( + const std::string& name, const AndroidSdk& sdk) { + config_.android_sdk_groups[name] = sdk; + return *this; +} + +PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddArtifact( + const Artifact& artifact) { + config_.artifacts.push_back(artifact); + return *this; +} + +configuration::PostProcessingConfiguration PostProcessingConfigurationBuilder::Build() { + return config_; +} + +ArtifactBuilder& ArtifactBuilder::SetName(const std::string& name) { + artifact_.name = {name}; + return *this; +} + +ArtifactBuilder& ArtifactBuilder::SetAbiGroup(const std::string& name) { + artifact_.abi_group = {name}; + return *this; +} + +ArtifactBuilder& ArtifactBuilder::SetDensityGroup(const std::string& name) { + artifact_.screen_density_group = {name}; + return *this; +} + +ArtifactBuilder& ArtifactBuilder::SetLocaleGroup(const std::string& name) { + artifact_.locale_group = {name}; + return *this; +} + +ArtifactBuilder& ArtifactBuilder::SetAndroidSdk(const std::string& name) { + artifact_.android_sdk_group = {name}; + return *this; +} + +configuration::Artifact ArtifactBuilder::Build() { + return artifact_; +} + } // namespace test } // namespace aapt diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index d9f3912fb4c6..4cdfc33e92e3 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -24,7 +24,9 @@ #include "Resource.h" #include "ResourceTable.h" #include "ResourceValues.h" +#include "configuration/ConfigurationParser.h" #include "process/IResourceTableConsumer.h" +#include "test/Common.h" #include "util/Maybe.h" #include "xml/XmlDom.h" @@ -50,12 +52,15 @@ class ResourceTableBuilder { ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id, const ConfigDescription& config, const android::StringPiece& str); ResourceTableBuilder& AddFileReference(const android::StringPiece& name, - const android::StringPiece& path); + const android::StringPiece& path, + io::IFile* file = nullptr); ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id, - const android::StringPiece& path); + const android::StringPiece& path, + io::IFile* file = nullptr); ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const android::StringPiece& path, - const ConfigDescription& config); + const ConfigDescription& config, + io::IFile* file = nullptr); ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value); ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id, std::unique_ptr<Value> value); @@ -149,6 +154,40 @@ std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str); std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context, const android::StringPiece& str); +class PostProcessingConfigurationBuilder { + public: + PostProcessingConfigurationBuilder() = default; + + PostProcessingConfigurationBuilder& SetAbiGroup(const std::string& name, + const std::vector<configuration::Abi>& abis); + PostProcessingConfigurationBuilder& SetLocaleGroup(const std::string& name, + const std::vector<std::string>& locales); + PostProcessingConfigurationBuilder& SetDensityGroup(const std::string& name, + const std::vector<std::string>& densities); + PostProcessingConfigurationBuilder& SetAndroidSdk(const std::string& name, + const configuration::AndroidSdk& sdk); + PostProcessingConfigurationBuilder& AddArtifact(const configuration::Artifact& artifact); + configuration::PostProcessingConfiguration Build(); + + private: + configuration::PostProcessingConfiguration config_; +}; + +class ArtifactBuilder { + public: + ArtifactBuilder() = default; + + ArtifactBuilder& SetName(const std::string& name); + ArtifactBuilder& SetAbiGroup(const std::string& name); + ArtifactBuilder& SetDensityGroup(const std::string& name); + ArtifactBuilder& SetLocaleGroup(const std::string& name); + ArtifactBuilder& SetAndroidSdk(const std::string& name); + configuration::Artifact Build(); + + private: + configuration::Artifact artifact_; +}; + } // namespace test } // namespace aapt diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index e6b38c0007b4..4e318a92f3fa 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -41,13 +41,13 @@ IDiagnostics* GetDiagnostics(); inline ResourceName ParseNameOrDie(const android::StringPiece& str) { ResourceNameRef ref; - CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name"; + CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name: " << str; return ref.ToResourceName(); } inline ConfigDescription ParseConfigOrDie(const android::StringPiece& str) { ConfigDescription config; - CHECK(ConfigDescription::Parse(str, &config)) << "invalid configuration"; + CHECK(ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str; return config; } @@ -90,6 +90,10 @@ class TestFile : public io::IFile { return {}; } + std::unique_ptr<io::InputStream> OpenInputStream() override { + return OpenAsData(); + } + const Source& GetSource() const override { return source_; } diff --git a/tools/aapt2/text/Printer.cpp b/tools/aapt2/text/Printer.cpp new file mode 100644 index 000000000000..243800c9385f --- /dev/null +++ b/tools/aapt2/text/Printer.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2017 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 "text/Printer.h" + +#include <algorithm> + +#include "io/Util.h" + +using ::aapt::io::OutputStream; +using ::android::StringPiece; + +namespace aapt { +namespace text { + +Printer& Printer::Println(const StringPiece& str) { + Print(str); + return Print("\n"); +} + +Printer& Printer::Println() { + return Print("\n"); +} + +Printer& Printer::Print(const StringPiece& str) { + if (error_) { + return *this; + } + + auto remaining_str_begin = str.begin(); + const auto remaining_str_end = str.end(); + while (remaining_str_end != remaining_str_begin) { + // Find the next new-line. + const auto new_line_iter = std::find(remaining_str_begin, remaining_str_end, '\n'); + + // We will copy the string up until the next new-line (or end of string). + const StringPiece str_to_copy = str.substr(remaining_str_begin, new_line_iter); + if (!str_to_copy.empty()) { + if (needs_indent_) { + for (int i = 0; i < indent_level_; i++) { + if (!io::Copy(out_, " ")) { + error_ = true; + return *this; + } + } + needs_indent_ = false; + } + + if (!io::Copy(out_, str_to_copy)) { + error_ = true; + return *this; + } + } + + // If we found a new-line. + if (new_line_iter != remaining_str_end) { + if (!io::Copy(out_, "\n")) { + error_ = true; + return *this; + } + needs_indent_ = true; + // Ok to increment iterator here because we know that the '\n' character is one byte. + remaining_str_begin = new_line_iter + 1; + } else { + remaining_str_begin = new_line_iter; + } + } + return *this; +} + +void Printer::Indent() { + ++indent_level_; +} + +void Printer::Undent() { + --indent_level_; +} + +} // namespace text +} // namespace aapt diff --git a/tools/aapt2/io/BigBufferOutputStream.h b/tools/aapt2/text/Printer.h index 95113bc2132c..f399f8ea5e0f 100644 --- a/tools/aapt2/io/BigBufferOutputStream.h +++ b/tools/aapt2/text/Printer.h @@ -14,35 +14,40 @@ * limitations under the License. */ -#ifndef AAPT_IO_BIGBUFFEROUTPUTSTREAM_H -#define AAPT_IO_BIGBUFFEROUTPUTSTREAM_H +#ifndef AAPT_TEXT_PRINTER_H +#define AAPT_TEXT_PRINTER_H + +#include "android-base/macros.h" +#include "androidfw/StringPiece.h" #include "io/Io.h" -#include "util/BigBuffer.h" namespace aapt { -namespace io { +namespace text { -class BigBufferOutputStream : public OutputStream { +// An indenting Printer that helps write formatted text to the OutputStream. +class Printer { public: - inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {} - virtual ~BigBufferOutputStream() = default; - - bool Next(void** data, size_t* size) override; - - void BackUp(size_t count) override; + explicit Printer(::aapt::io::OutputStream* out) : out_(out) { + } - size_t ByteCount() const override; + Printer& Print(const ::android::StringPiece& str); + Printer& Println(const ::android::StringPiece& str); + Printer& Println(); - bool HadError() const override; + void Indent(); + void Undent(); private: - DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream); + DISALLOW_COPY_AND_ASSIGN(Printer); - BigBuffer* buffer_; + ::aapt::io::OutputStream* out_; + int indent_level_ = 0; + bool needs_indent_ = false; + bool error_ = false; }; -} // namespace io +} // namespace text } // namespace aapt -#endif // AAPT_IO_BIGBUFFEROUTPUTSTREAM_H +#endif // AAPT_TEXT_PRINTER_H diff --git a/tools/aapt2/text/Printer_test.cpp b/tools/aapt2/text/Printer_test.cpp new file mode 100644 index 000000000000..58beae73c9f2 --- /dev/null +++ b/tools/aapt2/text/Printer_test.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 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 "text/Printer.h" + +#include "io/StringStream.h" +#include "test/Test.h" + +using ::aapt::io::StringOutputStream; +using ::android::StringPiece; +using ::testing::StrEq; + +namespace aapt { +namespace text { + +TEST(PrinterTest, PrintsToStreamWithIndents) { + std::string result; + StringOutputStream out(&result); + Printer printer(&out); + + printer.Print("Hello"); + out.Flush(); + EXPECT_THAT(result, StrEq("Hello")); + + printer.Println(); + out.Flush(); + EXPECT_THAT(result, StrEq("Hello\n")); + + // This shouldn't print anything yet. + printer.Indent(); + out.Flush(); + EXPECT_THAT(result, StrEq("Hello\n")); + + // Now we should see the indent. + printer.Print("world!"); + out.Flush(); + EXPECT_THAT(result, StrEq("Hello\n world!")); + + printer.Println(" What a\nlovely day."); + out.Flush(); + EXPECT_THAT(result, StrEq("Hello\n world! What a\n lovely day.\n")); + + // This shouldn't print anything yet. + printer.Undent(); + out.Flush(); + EXPECT_THAT(result, StrEq("Hello\n world! What a\n lovely day.\n")); + + printer.Println("Isn't it?"); + out.Flush(); + EXPECT_THAT(result, StrEq("Hello\n world! What a\n lovely day.\nIsn't it?\n")); +} + +} // namespace text +} // namespace aapt diff --git a/tools/aapt2/text/Unicode_test.cpp b/tools/aapt2/text/Unicode_test.cpp index a8e797cf3d17..16bc2e84224a 100644 --- a/tools/aapt2/text/Unicode_test.cpp +++ b/tools/aapt2/text/Unicode_test.cpp @@ -63,6 +63,7 @@ TEST(UnicodeTest, IsValidResourceEntryName) { EXPECT_FALSE(IsValidResourceEntryName("Føø/Bar")); EXPECT_FALSE(IsValidResourceEntryName("Føø:Bar")); EXPECT_FALSE(IsValidResourceEntryName("Føø;Bar")); + EXPECT_FALSE(IsValidResourceEntryName("0_resource_name_obfuscated")); } } // namespace text diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp index bf8dc4d727f7..5a8ff0926483 100644 --- a/tools/aapt2/util/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -34,7 +34,7 @@ #ifdef _WIN32 // Windows includes. -#include <direct.h> +#include <windows.h> #endif using ::android::FileMap; @@ -46,21 +46,29 @@ using ::android::base::unique_fd; namespace aapt { namespace file { -FileType GetFileType(const std::string& path) { -// TODO(adamlesinski): I'd like to move this to ::android::base::utf8 but Windows does some macro -// trickery with 'stat' and things don't override very well. #ifdef _WIN32 +FileType GetFileType(const std::string& path) { std::wstring path_utf16; if (!::android::base::UTF8PathToWindowsLongPath(path.c_str(), &path_utf16)) { return FileType::kNonexistant; } - struct _stat64 sb; - int result = _wstat64(path_utf16.c_str(), &sb); + DWORD result = GetFileAttributesW(path_utf16.c_str()); + if (result == INVALID_FILE_ATTRIBUTES) { + return FileType::kNonexistant; + } + + if (result & FILE_ATTRIBUTE_DIRECTORY) { + return FileType::kDirectory; + } + + // Too many types to consider, just let open fail later. + return FileType::kRegular; +} #else +FileType GetFileType(const std::string& path) { struct stat sb; int result = stat(path.c_str(), &sb); -#endif if (result == -1) { if (errno == ENOENT || errno == ENOTDIR) { @@ -91,6 +99,7 @@ FileType GetFileType(const std::string& path) { return FileType::kUnknown; } } +#endif bool mkdirs(const std::string& path) { constexpr const mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP; diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index 7c1c1ad6bb4d..e42145dff47e 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -504,11 +504,10 @@ bool Tokenizer::iterator::operator!=(const iterator& rhs) const { return !(*this == rhs); } -Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok, - bool end) +Tokenizer::iterator::iterator(const StringPiece& s, char sep, const StringPiece& tok, bool end) : str_(s), separator_(sep), token_(tok), end_(end) {} -Tokenizer::Tokenizer(StringPiece str, char sep) +Tokenizer::Tokenizer(const StringPiece& str, char sep) : begin_(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)), end_(str, sep, StringPiece(str.end(), 0), true) {} diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index f12746fe4bfc..7c949b90c10a 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -221,7 +221,7 @@ class Tokenizer { private: friend class Tokenizer; - iterator(android::StringPiece s, char sep, android::StringPiece tok, bool end); + iterator(const android::StringPiece& s, char sep, const android::StringPiece& tok, bool end); android::StringPiece str_; char separator_; @@ -229,7 +229,7 @@ class Tokenizer { bool end_; }; - Tokenizer(android::StringPiece str, char sep); + Tokenizer(const android::StringPiece& str, char sep); iterator begin() const { return begin_; diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp index cc664a5de722..602a902bfc3e 100644 --- a/tools/aapt2/xml/XmlActionExecutor.cpp +++ b/tools/aapt2/xml/XmlActionExecutor.cpp @@ -16,6 +16,8 @@ #include "xml/XmlActionExecutor.h" +using ::android::StringPiece; + namespace aapt { namespace xml { @@ -46,8 +48,8 @@ static void PrintElementToDiagMessage(const Element* el, DiagMessage* msg) { *msg << el->name << ">"; } -bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag, - Element* el) const { +bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, std::vector<StringPiece>* bread_crumb, + SourcePathDiagnostics* diag, Element* el) const { bool error = false; for (const ActionFuncWithDiag& action : actions_) { error |= !action(el, diag); @@ -57,15 +59,21 @@ bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, SourcePathDiagnostic if (child_el->namespace_uri.empty()) { std::map<std::string, XmlNodeAction>::const_iterator iter = map_.find(child_el->name); if (iter != map_.end()) { - error |= !iter->second.Execute(policy, diag, child_el); + // Use the iterator's copy of the element name, because the element may be modified. + bread_crumb->push_back(iter->first); + error |= !iter->second.Execute(policy, bread_crumb, diag, child_el); + bread_crumb->pop_back(); continue; } if (policy == XmlActionExecutorPolicy::kWhitelist) { DiagMessage error_msg(child_el->line_number); - error_msg << "unknown element "; + error_msg << "unexpected element "; PrintElementToDiagMessage(child_el, &error_msg); - error_msg << " found"; + error_msg << " found in "; + for (const StringPiece& element : *bread_crumb) { + error_msg << "<" << element << ">"; + } diag->Error(error_msg); error = true; } @@ -90,14 +98,15 @@ bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy, IDiagnostics* di if (el->namespace_uri.empty()) { std::map<std::string, XmlNodeAction>::const_iterator iter = map_.find(el->name); if (iter != map_.end()) { - return iter->second.Execute(policy, &source_diag, el); + std::vector<StringPiece> bread_crumb; + bread_crumb.push_back(iter->first); + return iter->second.Execute(policy, &bread_crumb, &source_diag, el); } if (policy == XmlActionExecutorPolicy::kWhitelist) { DiagMessage error_msg(el->line_number); - error_msg << "unknown element "; + error_msg << "unexpected root element "; PrintElementToDiagMessage(el, &error_msg); - error_msg << " found"; source_diag.Error(error_msg); return false; } diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h index 1d70045b3023..df70100e882a 100644 --- a/tools/aapt2/xml/XmlActionExecutor.h +++ b/tools/aapt2/xml/XmlActionExecutor.h @@ -40,56 +40,46 @@ enum class XmlActionExecutorPolicy { kWhitelist, }; -/** - * Contains the actions to perform at this XML node. This is a recursive data - * structure that - * holds XmlNodeActions for child XML nodes. - */ +// Contains the actions to perform at this XML node. This is a recursive data structure that +// holds XmlNodeActions for child XML nodes. class XmlNodeAction { public: using ActionFuncWithDiag = std::function<bool(Element*, SourcePathDiagnostics*)>; using ActionFunc = std::function<bool(Element*)>; - /** - * Find or create a child XmlNodeAction that will be performed for the child - * element with the name `name`. - */ - XmlNodeAction& operator[](const std::string& name) { return map_[name]; } + // Find or create a child XmlNodeAction that will be performed for the child element with the + // name `name`. + XmlNodeAction& operator[](const std::string& name) { + return map_[name]; + } - /** - * Add an action to be performed at this XmlNodeAction. - */ + // Add an action to be performed at this XmlNodeAction. void Action(ActionFunc f); void Action(ActionFuncWithDiag); private: friend class XmlActionExecutor; - bool Execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag, Element* el) const; + bool Execute(XmlActionExecutorPolicy policy, std::vector<::android::StringPiece>* bread_crumb, + SourcePathDiagnostics* diag, Element* el) const; std::map<std::string, XmlNodeAction> map_; std::vector<ActionFuncWithDiag> actions_; }; -/** - * Allows the definition of actions to execute at specific XML elements defined - * by their - * hierarchy. - */ +// Allows the definition of actions to execute at specific XML elements defined by their hierarchy. class XmlActionExecutor { public: XmlActionExecutor() = default; - /** - * Find or create a root XmlNodeAction that will be performed for the root XML - * element with the name `name`. - */ - XmlNodeAction& operator[](const std::string& name) { return map_[name]; } + // Find or create a root XmlNodeAction that will be performed for the root XML element with the + // name `name`. + XmlNodeAction& operator[](const std::string& name) { + return map_[name]; + } - /** - * Execute the defined actions for this XmlResource. - * Returns true if all actions return true, otherwise returns false. - */ + // Execute the defined actions for this XmlResource. + // Returns true if all actions return true, otherwise returns false. bool Execute(XmlActionExecutorPolicy policy, IDiagnostics* diag, XmlResource* doc) const; private: diff --git a/tools/aapt2/xml/XmlActionExecutor_test.cpp b/tools/aapt2/xml/XmlActionExecutor_test.cpp index 0fe7ab05ceb2..d39854e5fe4e 100644 --- a/tools/aapt2/xml/XmlActionExecutor_test.cpp +++ b/tools/aapt2/xml/XmlActionExecutor_test.cpp @@ -56,9 +56,13 @@ TEST(XmlActionExecutorTest, FailsWhenUndefinedHierarchyExists) { XmlActionExecutor executor; executor["manifest"]["application"]; - std::unique_ptr<XmlResource> doc = - test::BuildXmlDom("<manifest><application /><activity /></manifest>"); + std::unique_ptr<XmlResource> doc; StdErrDiagnostics diag; + + doc = test::BuildXmlDom("<manifest><application /><activity /></manifest>"); + ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kWhitelist, &diag, doc.get())); + + doc = test::BuildXmlDom("<manifest><application><activity /></application></manifest>"); ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kWhitelist, &diag, doc.get())); } diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp index 1d122db9990f..fddb6b8c5d87 100644 --- a/tools/aapt2/xml/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -215,8 +215,8 @@ std::unique_ptr<XmlResource> Inflate(InputStream* in, IDiagnostics* diag, const return {}; } } - return util::make_unique<XmlResource>(ResourceFile{{}, {}, source}, StringPool{}, - std::move(stack.root)); + return util::make_unique<XmlResource>(ResourceFile{{}, {}, ResourceFile::Type::kUnknown, source}, + StringPool{}, std::move(stack.root)); } static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPool* out_pool) { @@ -236,24 +236,29 @@ static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPoo attr.name = util::Utf16ToUtf8(StringPiece16(str16, len)); } + uint32_t res_id = parser->getAttributeNameResID(i); + if (res_id > 0) { + attr.compiled_attribute = AaptAttribute(::aapt::Attribute(), {res_id}); + } + str16 = parser->getAttributeStringValue(i, &len); if (str16) { attr.value = util::Utf16ToUtf8(StringPiece16(str16, len)); + } else { + android::Res_value res_value; + if (parser->getAttributeValue(i, &res_value) > 0) { + attr.compiled_value = ResourceUtils::ParseBinaryResValue( + ResourceType::kAnim, {}, parser->getStrings(), res_value, out_pool); + } } - android::Res_value res_value; - if (parser->getAttributeValue(i, &res_value) > 0) { - attr.compiled_value = ResourceUtils::ParseBinaryResValue( - ResourceType::kAnim, {}, parser->getStrings(), res_value, out_pool); - } el->attributes.push_back(std::move(attr)); } } } -std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag, - const Source& source) { +std::unique_ptr<XmlResource> Inflate(const void* data, size_t len, std::string* out_error) { // We import the android namespace because on Windows NO_ERROR is a macro, not // an enum, which causes errors when qualifying it with android:: using namespace android; @@ -264,7 +269,10 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnos std::unique_ptr<Element> pending_element; ResXMLTree tree; - if (tree.setTo(data, data_len) != NO_ERROR) { + if (tree.setTo(data, len) != NO_ERROR) { + if (out_error != nullptr) { + *out_error = "failed to initialize ResXMLTree"; + } return {}; } @@ -355,6 +363,27 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnos return util::make_unique<XmlResource>(ResourceFile{}, std::move(string_pool), std::move(root)); } +std::unique_ptr<XmlResource> XmlResource::Clone() const { + std::unique_ptr<XmlResource> cloned = util::make_unique<XmlResource>(file); + if (root != nullptr) { + cloned->root = root->CloneElement([&](const xml::Element& src, xml::Element* dst) { + dst->attributes.reserve(src.attributes.size()); + for (const xml::Attribute& attr : src.attributes) { + xml::Attribute cloned_attr; + cloned_attr.name = attr.name; + cloned_attr.namespace_uri = attr.namespace_uri; + cloned_attr.value = attr.value; + cloned_attr.compiled_attribute = attr.compiled_attribute; + if (attr.compiled_value != nullptr) { + cloned_attr.compiled_value.reset(attr.compiled_value->Clone(&cloned->string_pool)); + } + dst->attributes.push_back(std::move(cloned_attr)); + } + }); + } + return cloned; +} + Element* FindRootElement(Node* node) { if (node == nullptr) { return nullptr; @@ -377,12 +406,7 @@ void Element::InsertChild(size_t index, std::unique_ptr<Node> child) { } Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) { - for (auto& attr : attributes) { - if (ns == attr.namespace_uri && name == attr.name) { - return &attr; - } - } - return nullptr; + return const_cast<Attribute*>(static_cast<const Element*>(this)->FindAttribute(ns, name)); } const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) const { @@ -394,21 +418,42 @@ const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece return nullptr; } +Attribute* Element::FindOrCreateAttribute(const StringPiece& ns, const StringPiece& name) { + Attribute* attr = FindAttribute(ns, name); + if (attr == nullptr) { + attributes.push_back(Attribute{ns.to_string(), name.to_string()}); + attr = &attributes.back(); + } + return attr; +} + Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) { return FindChildWithAttribute(ns, name, {}, {}, {}); } +const Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) const { + return FindChildWithAttribute(ns, name, {}, {}, {}); +} + Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name, const StringPiece& attr_ns, const StringPiece& attr_name, const StringPiece& attr_value) { - for (auto& child : children) { - if (Element* el = NodeCast<Element>(child.get())) { + return const_cast<Element*>(static_cast<const Element*>(this)->FindChildWithAttribute( + ns, name, attr_ns, attr_name, attr_value)); +} + +const Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name, + const StringPiece& attr_ns, + const StringPiece& attr_name, + const StringPiece& attr_value) const { + for (const auto& child : children) { + if (const Element* el = NodeCast<Element>(child.get())) { if (ns == el->namespace_uri && name == el->name) { if (attr_ns.empty() && attr_name.empty()) { return el; } - Attribute* attr = el->FindAttribute(attr_ns, attr_name); + const Attribute* attr = el->FindAttribute(attr_ns, attr_name); if (attr && attr_value == attr->value) { return el; } @@ -455,6 +500,12 @@ void Element::Accept(Visitor* visitor) { visitor->AfterVisitElement(this); } +void Element::Accept(ConstVisitor* visitor) const { + visitor->BeforeVisitElement(this); + visitor->Visit(this); + visitor->AfterVisitElement(this); +} + std::unique_ptr<Node> Text::Clone(const ElementCloneFunc&) const { auto t = util::make_unique<Text>(); t->comment = comment; @@ -468,6 +519,10 @@ void Text::Accept(Visitor* visitor) { visitor->Visit(this); } +void Text::Accept(ConstVisitor* visitor) const { + visitor->Visit(this); +} + void PackageAwareVisitor::BeforeVisitElement(Element* el) { std::vector<PackageDecl> decls; for (const NamespaceDecl& decl : el->namespace_decls) { @@ -482,10 +537,9 @@ void PackageAwareVisitor::AfterVisitElement(Element* el) { package_decls_.pop_back(); } -Maybe<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias( - const StringPiece& alias, const StringPiece& local_package) const { +Maybe<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias(const StringPiece& alias) const { if (alias.empty()) { - return ExtractedPackage{local_package.to_string(), false /* private */}; + return ExtractedPackage{{}, false /*private*/}; } const auto rend = package_decls_.rend(); @@ -496,7 +550,7 @@ Maybe<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias( const PackageDecl& decl = *iter2; if (alias == decl.prefix) { if (decl.package.package.empty()) { - return ExtractedPackage{local_package.to_string(), decl.package.private_namespace}; + return ExtractedPackage{{}, decl.package.private_namespace}; } return decl.package; } diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h index 154224381626..8f3829611f30 100644 --- a/tools/aapt2/xml/XmlDom.h +++ b/tools/aapt2/xml/XmlDom.h @@ -35,6 +35,7 @@ namespace xml { class Element; class Visitor; +class ConstVisitor; // Base class for all XML nodes. class Node { @@ -47,6 +48,7 @@ class Node { std::string comment; virtual void Accept(Visitor* visitor) = 0; + virtual void Accept(ConstVisitor* visitor) const = 0; using ElementCloneFunc = std::function<void(const Element&, Element*)>; @@ -98,11 +100,23 @@ class Element : public Node { Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name); const Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name) const; + Attribute* FindOrCreateAttribute(const android::StringPiece& ns, + const android::StringPiece& name); + Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name); + const Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name) const; + Element* FindChildWithAttribute(const android::StringPiece& ns, const android::StringPiece& name, const android::StringPiece& attr_ns, const android::StringPiece& attr_name, const android::StringPiece& attr_value); + + const Element* FindChildWithAttribute(const android::StringPiece& ns, + const android::StringPiece& name, + const android::StringPiece& attr_ns, + const android::StringPiece& attr_name, + const android::StringPiece& attr_value) const; + std::vector<Element*> GetChildElements(); // Due to overriding of subtypes not working with unique_ptr, define a convenience Clone method @@ -112,6 +126,7 @@ class Element : public Node { std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) const override; void Accept(Visitor* visitor) override; + void Accept(ConstVisitor* visitor) const override; }; // A Text (CDATA) XML node. Can not have any children. @@ -122,6 +137,7 @@ class Text : public Node { std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) const override; void Accept(Visitor* visitor) override; + void Accept(ConstVisitor* visitor) const override; }; // An XML resource with a source, name, and XML tree. @@ -135,16 +151,16 @@ class XmlResource { StringPool string_pool; std::unique_ptr<xml::Element> root; + + std::unique_ptr<XmlResource> Clone() const; }; // Inflates an XML DOM from an InputStream, logging errors to the logger. -// Returns the root node on success, or nullptr on failure. std::unique_ptr<XmlResource> Inflate(io::InputStream* in, IDiagnostics* diag, const Source& source); -// Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger. -// Returns the root node on success, or nullptr on failure. -std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag, - const Source& source); +// Inflates an XML DOM from a binary ResXMLTree. +std::unique_ptr<XmlResource> Inflate(const void* data, size_t len, + std::string* out_error = nullptr); Element* FindRootElement(Node* node); @@ -180,13 +196,44 @@ class Visitor { friend class Element; }; +class ConstVisitor { + public: + virtual ~ConstVisitor() = default; + + virtual void Visit(const Element* el) { + VisitChildren(el); + } + + virtual void Visit(const Text* text) { + } + + protected: + ConstVisitor() = default; + + void VisitChildren(const Element* el) { + for (const auto& child : el->children) { + child->Accept(this); + } + } + + virtual void BeforeVisitElement(const Element* el) { + } + + virtual void AfterVisitElement(const Element* el) { + } + + private: + DISALLOW_COPY_AND_ASSIGN(ConstVisitor); + + friend class Element; +}; + // An XML DOM visitor that will record the package name for a namespace prefix. class PackageAwareVisitor : public Visitor, public IPackageDeclStack { public: using Visitor::Visit; - Maybe<ExtractedPackage> TransformPackageAlias( - const android::StringPiece& alias, const android::StringPiece& local_package) const override; + Maybe<ExtractedPackage> TransformPackageAlias(const android::StringPiece& alias) const override; protected: PackageAwareVisitor() = default; @@ -208,19 +255,19 @@ class PackageAwareVisitor : public Visitor, public IPackageDeclStack { namespace internal { // Base class that overrides the default behaviour and does not descend into child nodes. -class NodeCastBase : public Visitor { +class NodeCastBase : public ConstVisitor { public: - void Visit(Element* el) override { + void Visit(const Element* el) override { } - void Visit(Text* el) override { + void Visit(const Text* el) override { } protected: NodeCastBase() = default; - void BeforeVisitElement(Element* el) override { + void BeforeVisitElement(const Element* el) override { } - void AfterVisitElement(Element* el) override { + void AfterVisitElement(const Element* el) override { } private: @@ -234,9 +281,9 @@ class NodeCastImpl : public NodeCastBase { NodeCastImpl() = default; - T* value = nullptr; + const T* value = nullptr; - void Visit(T* v) override { + void Visit(const T* v) override { value = v; } @@ -247,12 +294,17 @@ class NodeCastImpl : public NodeCastBase { } // namespace internal template <typename T> -T* NodeCast(Node* node) { +const T* NodeCast(const Node* node) { internal::NodeCastImpl<T> visitor; node->Accept(&visitor); return visitor.value; } +template <typename T> +T* NodeCast(Node* node) { + return const_cast<T*>(NodeCast<T>(static_cast<const T*>(node))); +} + } // namespace xml } // namespace aapt diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp index b501cfd4b8b9..e5012d67163d 100644 --- a/tools/aapt2/xml/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -18,8 +18,8 @@ #include <string> -#include "flatten/XmlFlattener.h" -#include "io/StringInputStream.h" +#include "format/binary/XmlFlattener.h" +#include "io/StringStream.h" #include "test/Test.h" using ::aapt::io::StringInputStream; @@ -70,8 +70,7 @@ TEST(XmlDomTest, BinaryInflate) { ASSERT_TRUE(flattener.Consume(context.get(), doc.get())); auto block = util::Copy(buffer); - std::unique_ptr<XmlResource> new_doc = - Inflate(block.get(), buffer.size(), context->GetDiagnostics(), Source("test.xml")); + std::unique_ptr<XmlResource> new_doc = Inflate(block.get(), buffer.size(), nullptr); ASSERT_THAT(new_doc, NotNull()); EXPECT_THAT(new_doc->root->name, StrEq("Layout")); @@ -117,19 +116,14 @@ class TestVisitor : public PackageAwareVisitor { void Visit(Element* el) override { if (el->name == "View1") { - EXPECT_THAT(TransformPackageAlias("one", "local"), - Eq(make_value(ExtractedPackage{"com.one", false}))); + EXPECT_THAT(TransformPackageAlias("one"), Eq(make_value(ExtractedPackage{"com.one", false}))); } else if (el->name == "View2") { - EXPECT_THAT(TransformPackageAlias("one", "local"), - Eq(make_value(ExtractedPackage{"com.one", false}))); - EXPECT_THAT(TransformPackageAlias("two", "local"), - Eq(make_value(ExtractedPackage{"com.two", false}))); + EXPECT_THAT(TransformPackageAlias("one"), Eq(make_value(ExtractedPackage{"com.one", false}))); + EXPECT_THAT(TransformPackageAlias("two"), Eq(make_value(ExtractedPackage{"com.two", false}))); } else if (el->name == "View3") { - EXPECT_THAT(TransformPackageAlias("one", "local"), - Eq(make_value(ExtractedPackage{"com.one", false}))); - EXPECT_THAT(TransformPackageAlias("two", "local"), - Eq(make_value(ExtractedPackage{"com.two", false}))); - EXPECT_THAT(TransformPackageAlias("three", "local"), + EXPECT_THAT(TransformPackageAlias("one"), Eq(make_value(ExtractedPackage{"com.one", false}))); + EXPECT_THAT(TransformPackageAlias("two"), Eq(make_value(ExtractedPackage{"com.two", false}))); + EXPECT_THAT(TransformPackageAlias("three"), Eq(make_value(ExtractedPackage{"com.three", false}))); } } @@ -143,7 +137,6 @@ TEST(XmlDomTest, PackageAwareXmlVisitor) { </View2> </View1>)"); - Debug::DumpXml(doc.get()); TestVisitor visitor; doc->root->Accept(&visitor); } diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp index 30bdc507303b..402e5a459f4e 100644 --- a/tools/aapt2/xml/XmlPullParser.cpp +++ b/tools/aapt2/xml/XmlPullParser.cpp @@ -141,17 +141,16 @@ const std::string& XmlPullParser::namespace_uri() const { return event_queue_.front().data2; } -Maybe<ExtractedPackage> XmlPullParser::TransformPackageAlias( - const StringPiece& alias, const StringPiece& local_package) const { +Maybe<ExtractedPackage> XmlPullParser::TransformPackageAlias(const StringPiece& alias) const { if (alias.empty()) { - return ExtractedPackage{local_package.to_string(), false /* private */}; + return ExtractedPackage{{}, false /*private*/}; } const auto end_iter = package_aliases_.rend(); for (auto iter = package_aliases_.rbegin(); iter != end_iter; ++iter) { if (alias == iter->prefix) { if (iter->package.package.empty()) { - return ExtractedPackage{local_package.to_string(), iter->package.private_namespace}; + return ExtractedPackage{{}, iter->package.private_namespace}; } return iter->package; } diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h index a00caa139061..63db66f0b2b7 100644 --- a/tools/aapt2/xml/XmlPullParser.h +++ b/tools/aapt2/xml/XmlPullParser.h @@ -119,8 +119,7 @@ class XmlPullParser : public IPackageDeclStack { * If xmlns:app="http://schemas.android.com/apk/res-auto", then * 'package' will be set to 'defaultPackage'. */ - Maybe<ExtractedPackage> TransformPackageAlias( - const android::StringPiece& alias, const android::StringPiece& local_package) const override; + Maybe<ExtractedPackage> TransformPackageAlias(const android::StringPiece& alias) const override; // // Remaining methods are for retrieving information about attributes diff --git a/tools/aapt2/xml/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp index 681d9d48173f..5304bded85a5 100644 --- a/tools/aapt2/xml/XmlPullParser_test.cpp +++ b/tools/aapt2/xml/XmlPullParser_test.cpp @@ -18,43 +18,49 @@ #include "androidfw/StringPiece.h" -#include "io/StringInputStream.h" +#include "io/StringStream.h" #include "test/Test.h" using ::aapt::io::StringInputStream; using ::android::StringPiece; +using ::testing::Eq; +using ::testing::StrEq; + +using Event = ::aapt::xml::XmlPullParser::Event; namespace aapt { +namespace xml { TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) { std::string str = R"(<?xml version="1.0" encoding="utf-8"?> <a><b><c xmlns:a="http://schema.org"><d/></c><e/></b></a>)"; StringInputStream input(str); - xml::XmlPullParser parser(&input); + XmlPullParser parser(&input); const size_t depth_outer = parser.depth(); - ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_outer)); + ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_outer)); - EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event()); - EXPECT_EQ(StringPiece("a"), StringPiece(parser.element_name())); + EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement)); + EXPECT_THAT(parser.element_name(), StrEq("a")); const size_t depth_a = parser.depth(); - ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_a)); - EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event()); - EXPECT_EQ(StringPiece("b"), StringPiece(parser.element_name())); + ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_a)); + EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement)); + EXPECT_THAT(parser.element_name(), StrEq("b")); const size_t depth_b = parser.depth(); - ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b)); - EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event()); - EXPECT_EQ(StringPiece("c"), StringPiece(parser.element_name())); + ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_b)); + EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement)); + EXPECT_THAT(parser.element_name(), StrEq("c")); - ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b)); - EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event()); - EXPECT_EQ(StringPiece("e"), StringPiece(parser.element_name())); + ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_b)); + EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement)); + EXPECT_THAT(parser.element_name(), StrEq("e")); - ASSERT_FALSE(xml::XmlPullParser::NextChildNode(&parser, depth_outer)); - EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.event()); + ASSERT_FALSE(XmlPullParser::NextChildNode(&parser, depth_outer)); + EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kEndDocument)); } +} // namespace xml } // namespace aapt diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp index fb8cee8b5634..0a622b2bd336 100644 --- a/tools/aapt2/xml/XmlUtil.cpp +++ b/tools/aapt2/xml/XmlUtil.cpp @@ -16,20 +16,20 @@ #include "xml/XmlUtil.h" +#include <algorithm> #include <string> #include "util/Maybe.h" #include "util/Util.h" +#include "xml/XmlDom.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { namespace xml { -std::string BuildPackageNamespace(const StringPiece& package, - bool private_reference) { - std::string result = - private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix; +std::string BuildPackageNamespace(const StringPiece& package, bool private_reference) { + std::string result = private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix; result.append(package.data(), package.size()); return result; } @@ -39,8 +39,7 @@ Maybe<ExtractedPackage> ExtractPackageFromNamespace( if (util::StartsWith(namespace_uri, kSchemaPublicPrefix)) { StringPiece schema_prefix = kSchemaPublicPrefix; StringPiece package = namespace_uri; - package = package.substr(schema_prefix.size(), - package.size() - schema_prefix.size()); + package = package.substr(schema_prefix.size(), package.size() - schema_prefix.size()); if (package.empty()) { return {}; } @@ -49,8 +48,7 @@ Maybe<ExtractedPackage> ExtractPackageFromNamespace( } else if (util::StartsWith(namespace_uri, kSchemaPrivatePrefix)) { StringPiece schema_prefix = kSchemaPrivatePrefix; StringPiece package = namespace_uri; - package = package.substr(schema_prefix.size(), - package.size() - schema_prefix.size()); + package = package.substr(schema_prefix.size(), package.size() - schema_prefix.size()); if (package.empty()) { return {}; } @@ -62,23 +60,47 @@ Maybe<ExtractedPackage> ExtractPackageFromNamespace( return {}; } -void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack, - const StringPiece& local_package, - Reference* in_ref) { +void ResolvePackage(const IPackageDeclStack* decl_stack, Reference* in_ref) { if (in_ref->name) { if (Maybe<ExtractedPackage> transformed_package = - decl_stack->TransformPackageAlias(in_ref->name.value().package, - local_package)) { + decl_stack->TransformPackageAlias(in_ref->name.value().package)) { ExtractedPackage& extracted_package = transformed_package.value(); in_ref->name.value().package = std::move(extracted_package.package); // If the reference was already private (with a * prefix) and the - // namespace is public, - // we keep the reference private. + // namespace is public, we keep the reference private. in_ref->private_reference |= extracted_package.private_namespace; } } } +namespace { + +class ToolsNamespaceRemover : public Visitor { + public: + using Visitor::Visit; + + void Visit(Element* el) override { + auto new_end = + std::remove_if(el->namespace_decls.begin(), el->namespace_decls.end(), + [](const NamespaceDecl& decl) -> bool { return decl.uri == kSchemaTools; }); + el->namespace_decls.erase(new_end, el->namespace_decls.end()); + + auto new_attr_end = std::remove_if( + el->attributes.begin(), el->attributes.end(), + [](const Attribute& attr) -> bool { return attr.namespace_uri == kSchemaTools; }); + el->attributes.erase(new_attr_end, el->attributes.end()); + + Visitor::Visit(el); + } +}; + +} // namespace + +void StripAndroidStudioAttributes(Element* el) { + ToolsNamespaceRemover remover; + el->Accept(&remover); +} + } // namespace xml } // namespace aapt diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h index 866b6dcd7a88..592a604f87a6 100644 --- a/tools/aapt2/xml/XmlUtil.h +++ b/tools/aapt2/xml/XmlUtil.h @@ -35,7 +35,7 @@ constexpr const char* kSchemaAapt = "http://schemas.android.com/aapt"; // Result of extracting a package name from a namespace URI declaration. struct ExtractedPackage { // The name of the package. This can be the empty string, which means that the package - // should be assumed to be the package being compiled. + // should be assumed to be the same as the CallSite it was defined in. std::string package; // True if the package's private namespace was declared. This means that private resources @@ -51,8 +51,8 @@ struct ExtractedPackage { // http://schemas.android.com/apk/res/<package> or // http://schemas.android.com/apk/prv/res/<package> // -// Special case: if namespaceUri is http://schemas.android.com/apk/res-auto, -// returns an empty package name. +// Special case: if namespaceUri is http://schemas.android.com/apk/res-auto, returns an empty +// package name. Maybe<ExtractedPackage> ExtractPackageFromNamespace(const std::string& namespace_uri); // Returns an XML Android namespace for the given package of the form: @@ -63,21 +63,26 @@ Maybe<ExtractedPackage> ExtractPackageFromNamespace(const std::string& namespace std::string BuildPackageNamespace(const android::StringPiece& package, bool private_reference = false); -// Interface representing a stack of XML namespace declarations. When looking up the package -// for a namespace prefix, the stack is checked from top to bottom. +// Interface representing a stack of XML namespace declarations. When looking up the package for a +// namespace prefix, the stack is checked from top to bottom. struct IPackageDeclStack { virtual ~IPackageDeclStack() = default; // Returns an ExtractedPackage struct if the alias given corresponds with a package declaration. virtual Maybe<ExtractedPackage> TransformPackageAlias( - const android::StringPiece& alias, const android::StringPiece& local_package) const = 0; + const android::StringPiece& alias) const = 0; }; // Helper function for transforming the original Reference inRef to a fully qualified reference // via the IPackageDeclStack. This will also mark the Reference as private if the namespace of the // package declaration was private. -void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack, - const android::StringPiece& local_package, Reference* in_ref); +void ResolvePackage(const IPackageDeclStack* decl_stack, Reference* in_ref); + +class Element; + +// Strips out any attributes in the http://schemas.android.com/tools namespace, which is owned by +// Android Studio and should not make it to the final APK. +void StripAndroidStudioAttributes(Element* el); } // namespace xml } // namespace aapt diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py index 77c1c24b17eb..421e54558df4 100644 --- a/tools/apilint/apilint.py +++ b/tools/apilint/apilint.py @@ -72,6 +72,9 @@ class Field(): self.ident = self.raw.replace(" deprecated ", " ") + def __hash__(self): + return hash(self.raw) + def __repr__(self): return self.raw @@ -110,6 +113,9 @@ class Method(): ident = ident[:ident.index(" throws ")] self.ident = ident + def __hash__(self): + return hash(self.raw) + def __repr__(self): return self.raw @@ -145,6 +151,9 @@ class Class(): self.name = self.fullname[self.fullname.rindex(".")+1:] + def __hash__(self): + return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods))) + def __repr__(self): return self.raw @@ -256,6 +265,14 @@ def error(clazz, detail, rule, msg): _fail(clazz, detail, True, rule, msg) +noticed = {} + +def notice(clazz): + global noticed + + noticed[clazz.fullname] = hash(clazz) + + def verify_constants(clazz): """All static final constants must be FOO_NAME style.""" if re.match("android\.R\.[a-z]+", clazz.fullname): return @@ -923,11 +940,14 @@ def verify_callback_handlers(clazz): for f in found.values(): takes_handler = False + takes_exec = False for m in by_name[f.name]: if "android.os.Handler" in m.args: takes_handler = True - if not takes_handler: - warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Handler") + if "java.util.concurrent.Executor" in m.args: + takes_exec = True + if not takes_exec: + warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor") def verify_context_first(clazz): @@ -951,7 +971,7 @@ def verify_listener_last(clazz): for a in m.args: if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"): found = True - elif found and a != "android.os.Handler": + elif found and a != "android.os.Handler" and a != "java.util.concurrent.Executor": warn(clazz, m, "M3", "Listeners should always be at end of argument list") @@ -1127,8 +1147,97 @@ def verify_closable(clazz): return +def verify_member_name_not_kotlin_keyword(clazz): + """Prevent method names which are keywords in Kotlin.""" + + # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords + # This list does not include Java keywords as those are already impossible to use. + keywords = [ + 'as', + 'fun', + 'in', + 'is', + 'object', + 'typealias', + 'val', + 'var', + 'when', + ] + + for m in clazz.methods: + if m.name in keywords: + error(clazz, m, None, "Method name must not be a Kotlin keyword") + for f in clazz.fields: + if f.name in keywords: + error(clazz, f, None, "Field name must not be a Kotlin keyword") + + +def verify_method_name_not_kotlin_operator(clazz): + """Warn about method names which become operators in Kotlin.""" + + binary = set() + + def unique_binary_op(m, op): + if op in binary: + error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op)) + binary.add(op) + + for m in clazz.methods: + if 'static' in m.split: + continue + + # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators + if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0: + warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin") + + # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements + if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void': + # This only applies if the return type is the same or a subtype of the enclosing class, but we have no + # practical way of checking that relationship here. + warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin") + + # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic + if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1: + warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin") + unique_binary_op(m, m.name) + + # https://kotlinlang.org/docs/reference/operator-overloading.html#in + if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean': + warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin") + + # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed + if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1): + warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin") + + # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke + if m.name == 'invoke': + warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin") + + # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments + if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \ + and len(m.args) == 1 \ + and m.typ == 'void': + warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin") + unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix + + +def verify_collections_over_arrays(clazz): + """Warn that [] should be Collections.""" + + safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"] + for m in clazz.methods: + if m.typ.endswith("[]") and m.typ not in safe: + warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array") + for arg in m.args: + if arg.endswith("[]") and arg not in safe: + warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array") + + def examine_clazz(clazz): """Find all style issues in the given class.""" + + notice(clazz) + if clazz.pkg.name.startswith("java"): return if clazz.pkg.name.startswith("junit"): return if clazz.pkg.name.startswith("org.apache"): return @@ -1166,7 +1275,7 @@ def examine_clazz(clazz): verify_manager(clazz) verify_boxed(clazz) verify_static_utils(clazz) - verify_overload_args(clazz) + # verify_overload_args(clazz) verify_callback_handlers(clazz) verify_context_first(clazz) verify_listener_last(clazz) @@ -1178,14 +1287,18 @@ def examine_clazz(clazz): verify_error(clazz) verify_units(clazz) verify_closable(clazz) + verify_member_name_not_kotlin_keyword(clazz) + verify_method_name_not_kotlin_operator(clazz) + verify_collections_over_arrays(clazz) def examine_stream(stream): """Find all style issues in the given API stream.""" - global failures + global failures, noticed failures = {} + noticed = {} _parse_stream(stream, examine_clazz) - return failures + return (failures, noticed) def examine_api(api): @@ -1262,6 +1375,8 @@ if __name__ == "__main__": help="Disable terminal colors") parser.add_argument("--allow-google", action='store_const', const=True, help="Allow references to Google") + parser.add_argument("--show-noticed", action='store_const', const=True, + help="Show API changes noticed") args = vars(parser.parse_args()) if args['no_color']: @@ -1274,16 +1389,21 @@ if __name__ == "__main__": previous_file = args['previous.txt'] with current_file as f: - cur_fail = examine_stream(f) + cur_fail, cur_noticed = examine_stream(f) if not previous_file is None: with previous_file as f: - prev_fail = examine_stream(f) + prev_fail, prev_noticed = examine_stream(f) # ignore errors from previous API level for p in prev_fail: if p in cur_fail: del cur_fail[p] + # ignore classes unchanged from previous API level + for k, v in prev_noticed.iteritems(): + if k in cur_noticed and v == cur_noticed[k]: + del cur_noticed[k] + """ # NOTE: disabled because of memory pressure # look for compatibility issues @@ -1295,7 +1415,15 @@ if __name__ == "__main__": print """ - print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True))) - for f in sorted(cur_fail): - print cur_fail[f] + if args['show_noticed'] and len(cur_noticed) != 0: + print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True))) + for f in sorted(cur_noticed.keys()): + print f print + + if len(cur_fail) != 0: + print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True))) + for f in sorted(cur_fail): + print cur_fail[f] + print + sys.exit(77) diff --git a/tools/apilint/apilint_sha.sh b/tools/apilint/apilint_sha.sh new file mode 100755 index 000000000000..2a45b10392d7 --- /dev/null +++ b/tools/apilint/apilint_sha.sh @@ -0,0 +1,4 @@ +#!/bin/bash +if git show --name-only --pretty=format: $1 | grep api/ > /dev/null; then + python tools/apilint/apilint.py <(git show $1:api/current.txt) <(git show $1^:api/current.txt) +fi diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp index ef3ccc541589..5a9ab22719cb 100644 --- a/tools/bit/make.cpp +++ b/tools/bit/make.cpp @@ -167,7 +167,7 @@ read_modules(const string& buildOut, const string& device, map<string,Module>* r for (ssize_t i = module.classes.size() - 1; i >= 0; i--) { string cl = module.classes[i]; if (!(cl == "JAVA_LIBRARIES" || cl == "EXECUTABLES" || cl == "SHARED_LIBRARIES" - || cl == "APPS")) { + || cl == "APPS" || cl == "NATIVE_TESTS")) { module.classes.erase(module.classes.begin() + i); } } diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py index c6ad4c2aa396..15d39fdfd6ea 100755 --- a/tools/fonts/fontchain_linter.py +++ b/tools/fonts/fontchain_linter.py @@ -13,6 +13,7 @@ EMOJI_VS = 0xFE0F LANG_TO_SCRIPT = { 'as': 'Beng', + 'be': 'Cyrl', 'bg': 'Cyrl', 'bn': 'Beng', 'cu': 'Cyrl', @@ -33,6 +34,7 @@ LANG_TO_SCRIPT = { 'ja': 'Jpan', 'kn': 'Knda', 'ko': 'Kore', + 'la': 'Latn', 'ml': 'Mlym', 'mn': 'Cyrl', 'mr': 'Deva', @@ -161,11 +163,14 @@ def assert_font_supports_all_of_chars(font, chars): 'U+%04X was not found in %s' % (char, font)) -def assert_font_supports_none_of_chars(font, chars): +def assert_font_supports_none_of_chars(font, chars, fallbackName): best_cmap = get_best_cmap(font) for char in chars: - assert char not in best_cmap, ( - 'U+%04X was found in %s' % (char, font)) + if fallbackName: + assert char not in best_cmap, 'U+%04X was found in %s' % (char, font) + else: + assert char not in best_cmap, ( + 'U+%04X was found in %s in fallback %s' % (char, font, fallbackName)) def assert_font_supports_all_sequences(font, sequences): @@ -194,19 +199,21 @@ def check_hyphens(hyphens_dir): class FontRecord(object): - def __init__(self, name, scripts, variant, weight, style, font): + def __init__(self, name, scripts, variant, weight, style, fallback_for, font): self.name = name self.scripts = scripts self.variant = variant self.weight = weight self.style = style + self.fallback_for = fallback_for self.font = font def parse_fonts_xml(fonts_xml_path): - global _script_to_font_map, _fallback_chain + global _script_to_font_map, _fallback_chains, _all_fonts _script_to_font_map = collections.defaultdict(set) - _fallback_chain = [] + _fallback_chains = {} + _all_fonts = [] tree = ElementTree.parse(fonts_xml_path) families = tree.findall('family') # Minikin supports up to 254 but users can place their own font at the first @@ -223,10 +230,17 @@ def parse_fonts_xml(fonts_xml_path): 'No variant expected for LGC font %s.' % name) assert langs is None, ( 'No language expected for LGC fonts %s.' % name) + assert name not in _fallback_chains, 'Duplicated name entry %s' % name + _fallback_chains[name] = [] else: assert variant in {None, 'elegant', 'compact'}, ( 'Unexpected value for variant: %s' % variant) + for family in families: + name = family.get('name') + variant = family.get('variant') + langs = family.get('lang') + if langs: langs = langs.split() scripts = {lang_to_script(lang) for lang in langs} @@ -245,17 +259,36 @@ def parse_fonts_xml(fonts_xml_path): assert style in {'normal', 'italic'}, ( 'Unknown style "%s"' % style) + fallback_for = child.get('fallbackFor') + + assert not name or not fallback_for, ( + 'name and fallbackFor cannot be present at the same time') + assert not fallback_for or fallback_for in _fallback_chains, ( + 'Unknown fallback name: %s' % fallback_for) + index = child.get('index') if index: index = int(index) - _fallback_chain.append(FontRecord( + record = FontRecord( name, frozenset(scripts), variant, weight, style, - (font_file, index))) + fallback_for, + (font_file, index)) + + _all_fonts.append(record) + + if not fallback_for: + if not name or name == 'sans-serif': + for _, fallback in _fallback_chains.iteritems(): + fallback.append(record) + else: + _fallback_chains[name].append(record) + else: + _fallback_chains[fallback_for].append(record) if name: # non-empty names are used for default LGC fonts map_scripts = {'Latn', 'Grek', 'Cyrl'} @@ -272,7 +305,7 @@ def check_emoji_coverage(all_emoji, equivalent_emoji): def get_emoji_font(): emoji_fonts = [ - record.font for record in _fallback_chain + record.font for record in _all_fonts if 'Zsye' in record.scripts] assert len(emoji_fonts) == 1, 'There are %d emoji fonts.' % len(emoji_fonts) return emoji_fonts[0] @@ -316,35 +349,36 @@ def check_emoji_font_coverage(emoji_font, all_emoji, equivalent_emoji): def check_emoji_defaults(default_emoji): missing_text_chars = _emoji_properties['Emoji'] - default_emoji - emoji_font_seen = False - for record in _fallback_chain: - if 'Zsye' in record.scripts: - emoji_font_seen = True - # No need to check the emoji font - continue - # For later fonts, we only check them if they have a script - # defined, since the defined script may get them to a higher - # score even if they appear after the emoji font. However, - # we should skip checking the text symbols font, since - # symbol fonts should be able to override the emoji display - # style when 'Zsym' is explicitly specified by the user. - if emoji_font_seen and (not record.scripts or 'Zsym' in record.scripts): - continue + for name, fallback_chain in _fallback_chains.iteritems(): + emoji_font_seen = False + for record in fallback_chain: + if 'Zsye' in record.scripts: + emoji_font_seen = True + # No need to check the emoji font + continue + # For later fonts, we only check them if they have a script + # defined, since the defined script may get them to a higher + # score even if they appear after the emoji font. However, + # we should skip checking the text symbols font, since + # symbol fonts should be able to override the emoji display + # style when 'Zsym' is explicitly specified by the user. + if emoji_font_seen and (not record.scripts or 'Zsym' in record.scripts): + continue - # Check default emoji-style characters - assert_font_supports_none_of_chars(record.font, sorted(default_emoji)) + # Check default emoji-style characters + assert_font_supports_none_of_chars(record.font, sorted(default_emoji), name) - # Mark default text-style characters appearing in fonts above the emoji - # font as seen - if not emoji_font_seen: - missing_text_chars -= set(get_best_cmap(record.font)) + # Mark default text-style characters appearing in fonts above the emoji + # font as seen + if not emoji_font_seen: + missing_text_chars -= set(get_best_cmap(record.font)) - # Noto does not have monochrome glyphs for Unicode 7.0 wingdings and - # webdings yet. - missing_text_chars -= _chars_by_age['7.0'] - assert missing_text_chars == set(), ( - 'Text style version of some emoji characters are missing: ' + - repr(missing_text_chars)) + # Noto does not have monochrome glyphs for Unicode 7.0 wingdings and + # webdings yet. + missing_text_chars -= _chars_by_age['7.0'] + assert missing_text_chars == set(), ( + 'Text style version of some emoji characters are missing: ' + + repr(missing_text_chars)) # Setting reverse to true returns a dictionary that maps the values to sets of @@ -624,8 +658,19 @@ def compute_expected_emoji(): return all_emoji, default_emoji, equivalent_emoji +def check_compact_only_fallback(): + for name, fallback_chain in _fallback_chains.iteritems(): + for record in fallback_chain: + if record.variant == 'compact': + same_script_elegants = [x for x in fallback_chain + if x.scripts == record.scripts and x.variant == 'elegant'] + assert same_script_elegants, ( + '%s must be in elegant of %s as fallback of "%s" too' % ( + record.font, record.scripts, record.fallback_for),) + + def check_vertical_metrics(): - for record in _fallback_chain: + for record in _all_fonts: if record.name in ['sans-serif', 'sans-serif-condensed']: font = open_font(record.font) assert font['head'].yMax == 2163 and font['head'].yMin == -555, ( @@ -644,11 +689,12 @@ def check_vertical_metrics(): def check_cjk_punctuation(): cjk_scripts = {'Hans', 'Hant', 'Jpan', 'Kore'} cjk_punctuation = range(0x3000, 0x301F + 1) - for record in _fallback_chain: - if record.scripts.intersection(cjk_scripts): - # CJK font seen. Stop checking the rest of the fonts. - break - assert_font_supports_none_of_chars(record.font, cjk_punctuation) + for name, fallback_chain in _fallback_chains.iteritems(): + for record in fallback_chain: + if record.scripts.intersection(cjk_scripts): + # CJK font seen. Stop checking the rest of the fonts. + break + assert_font_supports_none_of_chars(record.font, cjk_punctuation, name) def main(): @@ -659,6 +705,8 @@ def main(): fonts_xml_path = path.join(target_out, 'etc', 'fonts.xml') parse_fonts_xml(fonts_xml_path) + check_compact_only_fallback() + check_vertical_metrics() hyphens_dir = path.join(target_out, 'usr', 'hyphen-data') diff --git a/tools/incident_report/generic_message.h b/tools/incident_report/generic_message.h index df3f7b22dfc7..7c4ad34e35c3 100644 --- a/tools/incident_report/generic_message.h +++ b/tools/incident_report/generic_message.h @@ -25,7 +25,7 @@ using namespace std; /** * Class to represent a protobuf Message, where we don't actually * know what any of the fields are, just their type codes. In other - * words, this loslessly stores a parsed protobuf object without + * words, this losslessly stores a parsed protobuf object without * having the .proto file that generated it. */ class GenericMessage diff --git a/tools/incident_report/main.cpp b/tools/incident_report/main.cpp index 1d8809f6f603..bd1b973c7bdf 100644 --- a/tools/incident_report/main.cpp +++ b/tools/incident_report/main.cpp @@ -45,8 +45,9 @@ static bool read_length_delimited(CodedInputStream* in, uint32 fieldId, Descriptor const* descriptor, GenericMessage* message) { - uint32 size; + uint32_t size; if (!in->ReadVarint32(&size)) { + fprintf(stderr, "Fail to read size of %s\n", descriptor->name().c_str()); return false; } @@ -68,6 +69,9 @@ read_length_delimited(CodedInputStream* in, uint32 fieldId, Descriptor const* de message->addString(fieldId, str); return true; } else { + fprintf(stderr, "Fail to read string of field %s, expect size %d, read %lu\n", + field->full_name().c_str(), size, str.size()); + fprintf(stderr, "String read \"%s\"\n", str.c_str()); return false; } } else if (type == FieldDescriptor::TYPE_BYTES) { @@ -97,6 +101,8 @@ read_message(CodedInputStream* in, Descriptor const* descriptor, GenericMessage* message->addInt64(fieldId, value64); break; } else { + fprintf(stderr, "bad VARINT: 0x%x (%d) at index %d of field %s\n", + tag, tag, in->CurrentPosition(), descriptor->name().c_str()); return false; } case WireFormatLite::WIRETYPE_FIXED64: @@ -104,10 +110,14 @@ read_message(CodedInputStream* in, Descriptor const* descriptor, GenericMessage* message->addInt64(fieldId, value64); break; } else { + fprintf(stderr, "bad VARINT: 0x%x (%d) at index %d of field %s\n", + tag, tag, in->CurrentPosition(), descriptor->name().c_str()); return false; } case WireFormatLite::WIRETYPE_LENGTH_DELIMITED: if (!read_length_delimited(in, fieldId, descriptor, message)) { + fprintf(stderr, "bad LENGTH_DELIMITED: 0x%x (%d) at index %d of field %s\n", + tag, tag, in->CurrentPosition(), descriptor->name().c_str()); return false; } break; @@ -116,11 +126,13 @@ read_message(CodedInputStream* in, Descriptor const* descriptor, GenericMessage* message->addInt32(fieldId, value32); break; } else { + fprintf(stderr, "bad FIXED32: 0x%x (%d) at index %d of field %s\n", + tag, tag, in->CurrentPosition(), descriptor->name().c_str()); return false; } default: - fprintf(stderr, "bad tag: 0x%x (%d) at index %d\n", tag, tag, - in->CurrentPosition()); + fprintf(stderr, "bad tag: 0x%x (%d) at index %d of field %s\n", tag, tag, + in->CurrentPosition(), descriptor->name().c_str()); return false; } } @@ -130,7 +142,6 @@ read_message(CodedInputStream* in, Descriptor const* descriptor, GenericMessage* static void print_value(Out* out, FieldDescriptor const* field, GenericMessage::Node const& node) { - uint32_t val32; FieldDescriptor::Type type = field->type(); switch (node.type) { @@ -146,29 +157,32 @@ print_value(Out* out, FieldDescriptor const* field, GenericMessage::Node const& out->printf("%f", *(float*)&node.value32); break; default: - out->printf("(unexpected value %d (0x%x)", node.value32, node.value32); + out->printf("(unexpected type %d: value32 %d (0x%x)", + type, node.value32, node.value32); break; } break; case GenericMessage::TYPE_VALUE64: switch (type) { - case FieldDescriptor::TYPE_FIXED64: - case FieldDescriptor::TYPE_SFIXED64: case FieldDescriptor::TYPE_DOUBLE: out->printf("%f", *(double*)&node.value64); break; + // Int32s here were added with addInt64 from a WIRETYPE_VARINT, + // even if the definition is for a 32 bit int. case FieldDescriptor::TYPE_SINT32: case FieldDescriptor::TYPE_INT32: - val32 = (uint32_t)node.value32; - out->printf("%d", val32); + out->printf("%d", node.value64); break; case FieldDescriptor::TYPE_INT64: - case FieldDescriptor::TYPE_UINT32: - val32 = (uint32_t)node.value32; - out->printf("%u", val32); + case FieldDescriptor::TYPE_SINT64: + case FieldDescriptor::TYPE_SFIXED64: + out->printf("%lld", node.value64); break; + case FieldDescriptor::TYPE_UINT32: case FieldDescriptor::TYPE_UINT64: - case FieldDescriptor::TYPE_SINT64: + case FieldDescriptor::TYPE_FIXED64: + out->printf("%u", node.value64); + break; case FieldDescriptor::TYPE_BOOL: if (node.value64) { out->printf("true"); @@ -177,8 +191,16 @@ print_value(Out* out, FieldDescriptor const* field, GenericMessage::Node const& } break; case FieldDescriptor::TYPE_ENUM: + if (field->enum_type()->FindValueByNumber((int)node.value64) == NULL) { + out->printf("%lld", (int) node.value64); + } else { + out->printf("%s", field->enum_type()->FindValueByNumber((int)node.value64) + ->name().c_str()); + } + break; default: - out->printf("(unexpected value %ld (0x%x))", node.value64, node.value64); + out->printf("(unexpected type %d: value64 %lld (0x%x))", + type, node.value64, node.value64); break; } break; @@ -213,22 +235,13 @@ print_message(Out* out, Descriptor const* descriptor, GenericMessage const* mess out->printf("%s=", field->name().c_str()); if (repeated) { if (it.first != it.second) { - out->printf("["); - if (type == FieldDescriptor::TYPE_MESSAGE - || type == FieldDescriptor::TYPE_STRING - || type == FieldDescriptor::TYPE_BYTES) { - out->printf("\n"); - } + out->printf("[\n"); out->indent(); for (GenericMessage::const_iterator_pair it = message->find(fieldId); it.first != it.second; it.first++) { print_value(out, field, it.first->second); - if (type == FieldDescriptor::TYPE_MESSAGE - || type == FieldDescriptor::TYPE_STRING - || type == FieldDescriptor::TYPE_BYTES) { - out->printf("\n"); - } + out->printf("\n"); } out->dedent(); @@ -297,7 +310,7 @@ static int adb_incident_workaround(const char* adbSerial, const vector<string>& sections) { const int maxAllowedSize = 20 * 1024 * 1024; // 20MB - uint8_t* buffer = (uint8_t*)malloc(maxAllowedSize); + unique_ptr<uint8_t[]> buffer(new uint8_t[maxAllowedSize]); for (vector<string>::const_iterator it=sections.begin(); it!=sections.end(); it++) { Descriptor const* descriptor = IncidentProto::descriptor(); @@ -324,7 +337,7 @@ adb_incident_workaround(const char* adbSerial, const vector<string>& sections) } id = field->number(); } - + int pfd[2]; if (pipe(pfd) != 0) { fprintf(stderr, "pipe failed: %s\n", strerror(errno)); @@ -363,7 +376,7 @@ adb_incident_workaround(const char* adbSerial, const vector<string>& sections) size_t size = 0; while (size < maxAllowedSize) { - ssize_t amt = read(pfd[0], buffer + size, maxAllowedSize - size); + ssize_t amt = read(pfd[0], buffer.get() + size, maxAllowedSize - size); if (amt == 0) { break; } else if (amt == -1) { @@ -390,7 +403,7 @@ adb_incident_workaround(const char* adbSerial, const vector<string>& sections) fprintf(stderr, "write error: %s\n", strerror(err)); return 1; } - err = write_all(STDOUT_FILENO, buffer, size); + err = write_all(STDOUT_FILENO, buffer.get(), size); if (err != 0) { fprintf(stderr, "write error: %s\n", strerror(err)); return 1; @@ -401,7 +414,6 @@ adb_incident_workaround(const char* adbSerial, const vector<string>& sections) } } - free(buffer); return 0; } diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp index 15f622cf9461..765079507513 100644 --- a/tools/incident_section_gen/main.cpp +++ b/tools/incident_section_gen/main.cpp @@ -17,27 +17,96 @@ #include <frameworks/base/core/proto/android/os/incident.pb.h> - #include <map> +#include <set> +#include <string> +using namespace android; using namespace android::os; using namespace google::protobuf; using namespace google::protobuf::io; using namespace google::protobuf::internal; using namespace std; -int -main(int, const char**) -{ - map<string,FieldDescriptor const*> sections; - int N; +/** + * Implementation details: + * This binary auto generates .cpp files for incident and incidentd. + * + * When argument "incident" is specified, it generates incident_section.cpp file. + * + * When argument "incidentd" is specified, it generates section_list.cpp file. + * + * In section_list.cpp file, it generates a SECTION_LIST array and a PRIVACY_POLICY_LIST array. + * For SECTION_LIST, it generates Section.h classes only for proto fields with section option enabled. + * For PRIVACY_POLICY_LIST, it generates Privacy.h classes only for proto fields with privacy option enabled. + * + * For Privacy struct, it is possible to have self recursion definitions since protobuf is defining "classes" + * So the logic to handle it becomes very complicated when Privacy tag of a message contains a list of Privacies + * of its sub-messages. The code also handles multiple depth of self recursion fields. + * + * For example here is a one level self recursion message WindowManager: + * message WindowState { + * string state = 1 [(privacy).dest = LOCAL]; + * int32 display_id = 2; + * repeated WindowState child_windows = 3; + * } + * + * message WindowManager { + * WindowState my_window = 1; + * } + * + * When generating Privacy options for WindowManager, this tool will generate cpp syntax source code: + * + * #include "section_list.h" + * ... + * Privacy WindowState__state { 1, 9, NULL, LOCAL, NULL }; // first two integers are values for field id and proto type. + * Privacy WindowState__child_windows { 3, 11, NULL, UNSET, NULL }; // reserved for WindowState_LIST + * Privacy* WindowState__MSG__UNSET[] = { + * &WindowState_state, + * // display id is default, nothing is generated. + * &WindowState_child_windows, + * NULL // terminator of the array + * }; + * Privacy WindowState__my_window { 1, 11, WindowState__MSG__UNSET, UNSET, NULL }; + * + * createList() { + * ... + * WindowState_child_windows.children = WindowState__MSG_UNSET; // point to its own definition after the list is defined. + * ... + * } + * + * const Privacy** PRIVACY_POLICY_LIST = createList(); + * const int PRIVACY_POLICY_COUNT = 1; + * + * Privacy Value Inheritance rules: + * 1. Both field and message can be tagged with DESTINATION: LOCAL(L), EXPLICIT(E), AUTOMATIC(A). + * 2. Primitives inherits containing message's tag unless defined explicitly. + * 3. Containing message's tag doesn't apply to message fields, even when unset (in this case, uses its default message tag). + * 4. Message field tag overrides its default message tag. + * 5. UNSET tag defaults to EXPLICIT. + */ - printf("// Auto generated file. Do not modify\n"); - printf("\n"); - printf("#include \"incident_sections.h\"\n"); +// The assignments will be called when constructs PRIVACY_POLICY_LIST, has to be global variable +vector<string> gSelfRecursionAssignments; + +static inline void emptyline() { printf("\n"); +} - Descriptor const* descriptor = IncidentProto::descriptor(); +static void generateHead(const char* header) { + printf("// Auto generated file. Do not modify\n"); + emptyline(); + printf("#include \"%s.h\"\n", header); + emptyline(); +} + +// ======================== incident_sections ============================= +static bool generateIncidentSectionsCpp(Descriptor const* descriptor) +{ + generateHead("incident_sections"); + + map<string,FieldDescriptor const*> sections; + int N; N = descriptor->field_count(); for (int i=0; i<N; i++) { const FieldDescriptor* field = descriptor->field(i); @@ -63,5 +132,370 @@ main(int, const char**) printf("const int INCIDENT_SECTION_COUNT = %d;\n", N); - return 0; + return true; +} + +// ========================= section_list =================================== +static void splitAndPrint(const string& args) { + size_t base = 0; + size_t found; + while (true) { + found = args.find_first_of(" ", base); + if (found != base) { + string arg = args.substr(base, found - base); + printf(" \"%s\",", arg.c_str()); + } + if (found == args.npos) break; + base = found + 1; + } +} + +static string replaceAll(const string& fieldName, const char oldC, const string& newS) { + if (fieldName.find_first_of(oldC) == fieldName.npos) return fieldName.c_str(); + size_t pos = 0, idx = 0; + char* res = new char[fieldName.size() * newS.size() + 1]; // assign a larger buffer + while (pos != fieldName.size()) { + char cur = fieldName[pos++]; + if (cur != oldC) { + res[idx++] = cur; + continue; + } + + for (size_t i=0; i<newS.size(); i++) { + res[idx++] = newS[i]; + } + } + res[idx] = '\0'; + string result(res); + delete [] res; + return result; +} + +static inline void printPrivacy(const string& name, const FieldDescriptor* field, const string& children, + const Destination dest, const string& patterns, const string& comments = "") { + printf("Privacy %s = { %d, %d, %s, %d, %s };%s\n", name.c_str(), field->number(), field->type(), + children.c_str(), dest, patterns.c_str(), comments.c_str()); +} + +// Get Custom Options ================================================================================ +static inline SectionFlags getSectionFlags(const FieldDescriptor* field) { + return field->options().GetExtension(section); +} + +static inline PrivacyFlags getPrivacyFlags(const FieldDescriptor* field) { + return field->options().GetExtension(privacy); +} + +static inline PrivacyFlags getPrivacyFlags(const Descriptor* descriptor) { + return descriptor->options().GetExtension(msg_privacy); +} + +// Get Destinations =================================================================================== +static inline Destination getMessageDest(const Descriptor* descriptor, const Destination overridden) { + return overridden != DEST_UNSET ? overridden : getPrivacyFlags(descriptor).dest(); +} + +// Returns field's own dest, when it is a message field, uses its message default tag if unset. +static inline Destination getFieldDest(const FieldDescriptor* field) { + Destination fieldDest = getPrivacyFlags(field).dest(); + return field->type() != FieldDescriptor::TYPE_MESSAGE ? fieldDest : + getMessageDest(field->message_type(), fieldDest); +} + +// Get Names =========================================================================================== +static inline string getFieldName(const FieldDescriptor* field) { + // replace . with double underscores to avoid name conflicts since fields use snake naming convention + return replaceAll(field->full_name(), '.', "__"); +} + + +static inline string getMessageName(const Descriptor* descriptor, const Destination overridden) { + // replace . with one underscore since messages use camel naming convention + return replaceAll(descriptor->full_name(), '.', "_") + "__MSG__" + + to_string(getMessageDest(descriptor, overridden)); +} + +// IsDefault ============================================================================================ +// Returns true if a field is default. Default is defined as this field has same dest as its containing message. +// For message fields, it only looks at its field tag and own default mesaage tag, doesn't recursively go deeper. +static inline bool isDefaultField(const FieldDescriptor* field, const Destination containerDest) { + Destination fieldDest = getFieldDest(field); + if (field->type() != FieldDescriptor::TYPE_MESSAGE) { + return fieldDest == containerDest || (fieldDest == DEST_UNSET); + } else { + return fieldDest == containerDest || + (containerDest == DEST_UNSET && fieldDest == DEST_EXPLICIT) || + (containerDest == DEST_EXPLICIT && fieldDest == DEST_UNSET); + } +} + +static bool isDefaultMessageImpl(const Descriptor* descriptor, const Destination dest, set<string>* parents) { + const int N = descriptor->field_count(); + const Destination messageDest = getMessageDest(descriptor, dest); + parents->insert(descriptor->full_name()); + for (int i=0; i<N; ++i) { + const FieldDescriptor* field = descriptor->field(i); + const Destination fieldDest = getFieldDest(field); + // If current field is not default, return false immediately + if (!isDefaultField(field, messageDest)) return false; + switch (field->type()) { + case FieldDescriptor::TYPE_MESSAGE: + // if self recursion, don't go deep. + if (parents->find(field->message_type()->full_name()) != parents->end()) break; + // if is a default message, just continue + if (isDefaultMessageImpl(field->message_type(), fieldDest, parents)) break; + // sub message is not default, so this message is always not default + return false; + case FieldDescriptor::TYPE_STRING: + if (getPrivacyFlags(field).patterns_size() != 0) return false; + default: + continue; + } + } + parents->erase(descriptor->full_name()); + return true; +} + +// Recursively look at if this message is default, meaning all its fields and sub-messages +// can be described by the same dest. +static bool isDefaultMessage(const Descriptor* descriptor, const Destination dest) { + set<string> parents; + return isDefaultMessageImpl(descriptor, dest, &parents); +} + +// =============================================================================================================== +static bool numberInOrder(const FieldDescriptor* f1, const FieldDescriptor* f2) { + return f1->number() < f2->number(); +} + +// field numbers are possibly out of order, sort them here. +static vector<const FieldDescriptor*> sortFields(const Descriptor* descriptor) { + vector<const FieldDescriptor*> fields; + fields.reserve(descriptor->field_count()); + for (int i=0; i<descriptor->field_count(); i++) { + fields.push_back(descriptor->field(i)); + } + std::sort(fields.begin(), fields.end(), numberInOrder); + return fields; +} + +// This function looks for privacy tags of a message type and recursively its sub-messages. +// It generates Privacy objects for each non-default fields including non-default sub-messages. +// And if the message has Privacy objects generated, it returns a list of them. +// Returns false if the descriptor doesn't have any non default privacy flags set, including its submessages +static bool generatePrivacyFlags(const Descriptor* descriptor, const Destination overridden, + map<string, bool> &variableNames, set<string>* parents) { + const string messageName = getMessageName(descriptor, overridden); + const Destination messageDest = getMessageDest(descriptor, overridden); + + if (variableNames.find(messageName) != variableNames.end()) { + bool hasDefault = variableNames[messageName]; + return !hasDefault; // if has default, then don't generate privacy flags. + } + // insert the message type name so sub-message will figure out if self-recursion occurs + parents->insert(messageName); + + // sort fields based on number, iterate though them and generate sub flags first + vector<const FieldDescriptor*> fieldsInOrder = sortFields(descriptor); + bool hasDefaultFlags[fieldsInOrder.size()]; + for (size_t i=0; i<fieldsInOrder.size(); i++) { + const FieldDescriptor* field = fieldsInOrder[i]; + const string fieldName = getFieldName(field); + const Destination fieldDest = getFieldDest(field); + + if (variableNames.find(fieldName) != variableNames.end()) { + hasDefaultFlags[i] = variableNames[fieldName]; + continue; + } + hasDefaultFlags[i] = isDefaultField(field, messageDest); + + string fieldMessageName; + PrivacyFlags p = getPrivacyFlags(field); + switch (field->type()) { + case FieldDescriptor::TYPE_MESSAGE: + fieldMessageName = getMessageName(field->message_type(), fieldDest); + if (parents->find(fieldMessageName) != parents->end()) { // Self-Recursion proto definition + if (hasDefaultFlags[i]) { + hasDefaultFlags[i] = isDefaultMessage(field->message_type(), fieldDest); + } + if (!hasDefaultFlags[i]) { + printPrivacy(fieldName, field, "NULL", fieldDest, "NULL", + " // self recursion field of " + fieldMessageName); + // generate the assignment and used to construct createList function later on. + gSelfRecursionAssignments.push_back(fieldName + ".children = " + fieldMessageName); + } + } else if (generatePrivacyFlags(field->message_type(), p.dest(), variableNames, parents)) { + if (variableNames.find(fieldName) == variableNames.end()) { + printPrivacy(fieldName, field, fieldMessageName, fieldDest, "NULL"); + } + hasDefaultFlags[i] = false; + } else if (!hasDefaultFlags[i]) { + printPrivacy(fieldName, field, "NULL", fieldDest, "NULL"); + } + break; + case FieldDescriptor::TYPE_STRING: + if (p.patterns_size() != 0) { // if patterns are specified + if (hasDefaultFlags[i]) break; + printf("const char* %s_patterns[] = {\n", fieldName.c_str()); + for (int j=0; j<p.patterns_size(); j++) { + // generated string needs to escape backslash too, duplicate it to allow escape again. + printf(" \"%s\",\n", replaceAll(p.patterns(j), '\\', "\\\\").c_str()); + } + printf(" NULL };\n"); + printPrivacy(fieldName, field, "NULL", fieldDest, fieldName + "_patterns"); + break; + } + // else treat string field as primitive field and goes to default + default: + if (!hasDefaultFlags[i]) printPrivacy(fieldName, field, "NULL", fieldDest, "NULL"); + } + // Don't generate a variable twice + if (!hasDefaultFlags[i]) variableNames[fieldName] = false; + } + + bool allDefaults = true; + for (size_t i=0; i<fieldsInOrder.size(); i++) { + allDefaults &= hasDefaultFlags[i]; + } + + parents->erase(messageName); // erase the message type name when exit the message. + variableNames[messageName] = allDefaults; // store the privacy tags of the message here to avoid overhead. + + if (allDefaults) return false; + + emptyline(); + int policyCount = 0; + printf("Privacy* %s[] = {\n", messageName.c_str()); + for (size_t i=0; i<fieldsInOrder.size(); i++) { + const FieldDescriptor* field = fieldsInOrder[i]; + if (hasDefaultFlags[i]) continue; + printf(" &%s,\n", getFieldName(field).c_str()); + policyCount++; + } + printf(" NULL };\n"); + emptyline(); + return true; +} + +static bool generateSectionListCpp(Descriptor const* descriptor) { + generateHead("section_list"); + + // generates SECTION_LIST + printf("// Generate SECTION_LIST.\n\n"); + + printf("const Section* SECTION_LIST[] = {\n"); + for (int i=0; i<descriptor->field_count(); i++) { + const FieldDescriptor* field = descriptor->field(i); + + if (field->type() != FieldDescriptor::TYPE_MESSAGE) { + continue; + } + const SectionFlags s = getSectionFlags(field); + switch (s.type()) { + case SECTION_NONE: + continue; + case SECTION_FILE: + printf(" new FileSection(%d, \"%s\"),\n", field->number(), s.args().c_str()); + break; + case SECTION_COMMAND: + printf(" new CommandSection(%d,", field->number()); + splitAndPrint(s.args()); + printf(" NULL),\n"); + break; + case SECTION_DUMPSYS: + printf(" new DumpsysSection(%d,", field->number()); + splitAndPrint(s.args()); + printf(" NULL),\n"); + break; + } + } + printf(" NULL };\n"); + + emptyline(); + printf("// =============================================================================\n"); + emptyline(); + + // generates PRIVACY_POLICY_LIST + printf("// Generate PRIVACY_POLICY_LIST.\n\n"); + map<string, bool> variableNames; + set<string> parents; + vector<const FieldDescriptor*> fieldsInOrder = sortFields(descriptor); + bool skip[fieldsInOrder.size()]; + const Destination incidentDest = getPrivacyFlags(descriptor).dest(); + + for (size_t i=0; i<fieldsInOrder.size(); i++) { + const FieldDescriptor* field = fieldsInOrder[i]; + const string fieldName = getFieldName(field); + const Destination fieldDest = getFieldDest(field); + const string fieldMessageName = getMessageName(field->message_type(), fieldDest); + + skip[i] = true; + + if (field->type() != FieldDescriptor::TYPE_MESSAGE) { + continue; + } + // generate privacy flags for each section. + if (generatePrivacyFlags(field->message_type(), fieldDest, variableNames, &parents)) { + printPrivacy(fieldName, field, fieldMessageName, fieldDest, "NULL"); + } else if (isDefaultField(field, incidentDest)) { + continue; // don't create a new privacy if the value is default. + } else { + printPrivacy(fieldName, field, "NULL", fieldDest, "NULL"); + } + skip[i] = false; + } + + // generate final PRIVACY_POLICY_LIST + emptyline(); + int policyCount = 0; + if (gSelfRecursionAssignments.empty()) { + printf("Privacy* privacyArray[] = {\n"); + for (size_t i=0; i<fieldsInOrder.size(); i++) { + if (skip[i]) continue; + printf(" &%s,\n", getFieldName(fieldsInOrder[i]).c_str()); + policyCount++; + } + printf("};\n\n"); + printf("const Privacy** PRIVACY_POLICY_LIST = const_cast<const Privacy**>(privacyArray);\n\n"); + printf("const int PRIVACY_POLICY_COUNT = %d;\n", policyCount); + } else { + for (size_t i=0; i<fieldsInOrder.size(); i++) { + if (!skip[i]) policyCount++; + } + + printf("static const Privacy** createList() {\n"); + for (size_t i=0; i<gSelfRecursionAssignments.size(); ++i) { + printf(" %s;\n", gSelfRecursionAssignments[i].c_str()); + } + printf(" Privacy** privacyArray = (Privacy**)malloc(%d * sizeof(Privacy**));\n", policyCount); + policyCount = 0; // reset + for (size_t i=0; i<fieldsInOrder.size(); i++) { + if (skip[i]) continue; + printf(" privacyArray[%d] = &%s;\n", policyCount++, getFieldName(fieldsInOrder[i]).c_str()); + } + printf(" return const_cast<const Privacy**>(privacyArray);\n"); + printf("}\n\n"); + printf("const Privacy** PRIVACY_POLICY_LIST = createList();\n\n"); + printf("const int PRIVACY_POLICY_COUNT = %d;\n", policyCount); + } + return true; +} + +// ================================================================================ +int main(int argc, char const *argv[]) +{ + if (argc != 2) return 1; + const char* module = argv[1]; + + Descriptor const* descriptor = IncidentProto::descriptor(); + + if (strcmp(module, "incident") == 0) { + return !generateIncidentSectionsCpp(descriptor); + } + if (strcmp(module, "incidentd") == 0 ) { + return !generateSectionListCpp(descriptor); + } + + // return failure if not called by the whitelisted modules + return 1; } diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp index 6dd6059dcc4f..5f81a2eeb130 100644 --- a/tools/locked_region_code_injection/Android.bp +++ b/tools/locked_region_code_injection/Android.bp @@ -1,4 +1,4 @@ -java_library_host { +java_binary_host { name: "lockedregioncodeinjection", manifest: "manifest.txt", srcs: ["src/**/*.java"], diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp new file mode 100644 index 000000000000..468864b49d3f --- /dev/null +++ b/tools/stats_log_api_gen/Android.bp @@ -0,0 +1,106 @@ +// +// Copyright (C) 2017 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. +// + +// ========================================================== +// Build the host executable: stats-log-api-gen +// ========================================================== +cc_binary_host { + name: "stats-log-api-gen", + srcs: [ + "Collation.cpp", + "main.cpp", + ], + cflags: [ + "-Wall", + "-Werror", + ], + + shared_libs: [ + "libstats_proto_host", + "libprotobuf-cpp-full", + ], + + proto: { + type: "full", + }, +} + +// ========================================================== +// Build the host test executable: stats-log-api-gen +// ========================================================== +cc_test_host { + name: "stats-log-api-gen-test", + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + "-g", + "-DUNIT_TEST", + ], + srcs: [ + "Collation.cpp", + "test_collation.cpp", + "test.proto", + ], + + static_libs: [ + "libgmock_host", + ], + + shared_libs: [ + "libstats_proto_host", + ], + + proto: { + type: "full", + }, +} + +// ========================================================== +// Native library +// ========================================================== +genrule { + name: "statslog.h", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog.h", + out: [ + "statslog.h", + ], +} + +genrule { + name: "statslog.cpp", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog.cpp", + out: [ + "statslog.cpp", + ], +} + +cc_library_shared { + name: "libstatslog", + generated_sources: ["statslog.cpp"], + generated_headers: ["statslog.h"], + cflags: [ + "-Wall", + "-Werror", + ], + export_generated_headers: ["statslog.h"], + shared_libs: [ + "liblog", + ], +} + diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp new file mode 100644 index 000000000000..f76196dd2a19 --- /dev/null +++ b/tools/stats_log_api_gen/Collation.cpp @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2017, 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 "Collation.h" + +#include <stdio.h> +#include <map> + +namespace android { +namespace stats_log_api_gen { + +using google::protobuf::EnumDescriptor; +using google::protobuf::FieldDescriptor; +using google::protobuf::FileDescriptor; +using google::protobuf::SourceLocation; +using std::map; + + +// +// AtomDecl class +// + +AtomDecl::AtomDecl() + :code(0), + name() +{ +} + +AtomDecl::AtomDecl(const AtomDecl& that) + :code(that.code), + name(that.name), + message(that.message), + fields(that.fields) +{ +} + +AtomDecl::AtomDecl(int c, const string& n, const string& m) + :code(c), + name(n), + message(m) +{ +} + +AtomDecl::~AtomDecl() +{ +} + + +/** + * Print an error message for a FieldDescriptor, including the file name and line number. + */ +static void +print_error(const FieldDescriptor* field, const char* format, ...) +{ + const Descriptor* message = field->containing_type(); + const FileDescriptor* file = message->file(); + + SourceLocation loc; + if (field->GetSourceLocation(&loc)) { + // TODO: this will work if we can figure out how to pass --include_source_info to protoc + fprintf(stderr, "%s:%d: ", file->name().c_str(), loc.start_line); + } else { + fprintf(stderr, "%s: ", file->name().c_str()); + } + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end (args); +} + +/** + * Convert a protobuf type into a java type. + */ +static java_type_t +java_type(const FieldDescriptor* field) +{ + int protoType = field->type(); + switch (protoType) { + case FieldDescriptor::TYPE_DOUBLE: + return JAVA_TYPE_DOUBLE; + case FieldDescriptor::TYPE_FLOAT: + return JAVA_TYPE_FLOAT; + case FieldDescriptor::TYPE_INT64: + return JAVA_TYPE_LONG; + case FieldDescriptor::TYPE_UINT64: + return JAVA_TYPE_LONG; + case FieldDescriptor::TYPE_INT32: + return JAVA_TYPE_INT; + case FieldDescriptor::TYPE_FIXED64: + return JAVA_TYPE_LONG; + case FieldDescriptor::TYPE_FIXED32: + return JAVA_TYPE_INT; + case FieldDescriptor::TYPE_BOOL: + return JAVA_TYPE_BOOLEAN; + case FieldDescriptor::TYPE_STRING: + return JAVA_TYPE_STRING; + case FieldDescriptor::TYPE_GROUP: + return JAVA_TYPE_UNKNOWN; + case FieldDescriptor::TYPE_MESSAGE: + // TODO: not the final package name + if (field->message_type()->full_name() == "android.os.statsd.WorkSource") { + return JAVA_TYPE_WORK_SOURCE; + } else { + return JAVA_TYPE_OBJECT; + } + case FieldDescriptor::TYPE_BYTES: + return JAVA_TYPE_BYTE_ARRAY; + case FieldDescriptor::TYPE_UINT32: + return JAVA_TYPE_INT; + case FieldDescriptor::TYPE_ENUM: + return JAVA_TYPE_ENUM; + case FieldDescriptor::TYPE_SFIXED32: + return JAVA_TYPE_INT; + case FieldDescriptor::TYPE_SFIXED64: + return JAVA_TYPE_LONG; + case FieldDescriptor::TYPE_SINT32: + return JAVA_TYPE_INT; + case FieldDescriptor::TYPE_SINT64: + return JAVA_TYPE_LONG; + default: + return JAVA_TYPE_UNKNOWN; + } +} + +/** + * Gather the info about the atoms. + */ +int +collate_atoms(const Descriptor* descriptor, Atoms* atoms) +{ + int errorCount = 0; + const bool dbg = false; + + for (int i=0; i<descriptor->field_count(); i++) { + const FieldDescriptor* atomField = descriptor->field(i); + + if (dbg) { + printf(" %s (%d)\n", atomField->name().c_str(), atomField->number()); + } + + // StatsEvent only has one oneof, which contains only messages. Don't allow other types. + if (atomField->type() != FieldDescriptor::TYPE_MESSAGE) { + print_error(atomField, + "Bad type for atom. StatsEvent can only have message type fields: %s\n", + atomField->name().c_str()); + errorCount++; + continue; + } + + const Descriptor* atom = atomField->message_type(); + + // Build a sorted list of the fields. Descriptor has them in source file order. + map<int,const FieldDescriptor*> fields; + for (int j=0; j<atom->field_count(); j++) { + const FieldDescriptor* field = atom->field(j); + fields[field->number()] = field; + } + + // Check that the parameters start at 1 and go up sequentially. + int expectedNumber = 1; + for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin(); + it != fields.end(); it++) { + const int number = it->first; + const FieldDescriptor* field = it->second; + if (number != expectedNumber) { + print_error(field, "Fields must be numbered consecutively starting at 1:" + " '%s' is %d but should be %d\n", + field->name().c_str(), number, expectedNumber); + errorCount++; + expectedNumber = number; + continue; + } + expectedNumber++; + } + + // Check that only allowed types are present. Remove any invalid ones. + for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin(); + it != fields.end(); it++) { + const FieldDescriptor* field = it->second; + + java_type_t javaType = java_type(field); + + if (javaType == JAVA_TYPE_UNKNOWN) { + print_error(field, "Unkown type for field: %s\n", field->name().c_str()); + errorCount++; + continue; + } else if (javaType == JAVA_TYPE_OBJECT) { + // Allow WorkSources, but only at position 1. + print_error(field, "Message type not allowed for field: %s\n", + field->name().c_str()); + errorCount++; + continue; + } else if (javaType == JAVA_TYPE_BYTE_ARRAY) { + print_error(field, "Raw bytes type not allowed for field: %s\n", + field->name().c_str()); + errorCount++; + continue; + } + } + + // Check that if there's a WorkSource, it's at position 1. + for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin(); + it != fields.end(); it++) { + int number = it->first; + if (number != 1) { + const FieldDescriptor* field = it->second; + java_type_t javaType = java_type(field); + if (javaType == JAVA_TYPE_WORK_SOURCE) { + print_error(field, "WorkSource fields must have field id 1, in message: '%s'\n", + atom->name().c_str()); + errorCount++; + } + } + } + + AtomDecl atomDecl(atomField->number(), atomField->name(), atom->name()); + + // Build the type signature and the atom data. + vector<java_type_t> signature; + for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin(); + it != fields.end(); it++) { + const FieldDescriptor* field = it->second; + java_type_t javaType = java_type(field); + + AtomField atField(field->name(), javaType); + if (javaType == JAVA_TYPE_ENUM) { + // All enums are treated as ints when it comes to function signatures. + signature.push_back(JAVA_TYPE_INT); + const EnumDescriptor* enumDescriptor = field->enum_type(); + for (int i = 0; i < enumDescriptor->value_count(); i++) { + atField.enumValues[enumDescriptor->value(i)->number()] = + enumDescriptor->value(i)->name().c_str(); + } + } else { + signature.push_back(javaType); + } + atomDecl.fields.push_back(atField); + } + + atoms->signatures.insert(signature); + atoms->decls.insert(atomDecl); + } + + if (dbg) { + printf("signatures = [\n"); + for (set<vector<java_type_t>>::const_iterator it = atoms->signatures.begin(); + it != atoms->signatures.end(); it++) { + printf(" "); + for (vector<java_type_t>::const_iterator jt = it->begin(); jt != it->end(); jt++) { + printf(" %d", (int)*jt); + } + printf("\n"); + } + printf("]\n"); + } + + return errorCount; +} + +} // namespace stats_log_api_gen +} // namespace android diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h new file mode 100644 index 000000000000..2f840d7f4469 --- /dev/null +++ b/tools/stats_log_api_gen/Collation.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017, 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 ANDROID_STATS_LOG_API_GEN_COLLATION_H +#define ANDROID_STATS_LOG_API_GEN_COLLATION_H + + +#include <google/protobuf/descriptor.h> + +#include <set> +#include <vector> +#include <map> + +namespace android { +namespace stats_log_api_gen { + +using std::map; +using std::set; +using std::string; +using std::vector; +using google::protobuf::Descriptor; + +/** + * The types for atom parameters. + */ +typedef enum { + JAVA_TYPE_UNKNOWN = 0, + + JAVA_TYPE_WORK_SOURCE = 1, + JAVA_TYPE_BOOLEAN = 2, + JAVA_TYPE_INT = 3, + JAVA_TYPE_LONG = 4, + JAVA_TYPE_FLOAT = 5, + JAVA_TYPE_DOUBLE = 6, + JAVA_TYPE_STRING = 7, + JAVA_TYPE_ENUM = 8, + + JAVA_TYPE_OBJECT = -1, + JAVA_TYPE_BYTE_ARRAY = -2, +} java_type_t; + + +/** + * The name and type for an atom field. + */ +struct AtomField { + string name; + java_type_t javaType; + + // If the field is of type enum, the following map contains the list of enum values. + map<int /* numeric value */, string /* value name */> enumValues; + + inline AtomField() :name(), javaType(JAVA_TYPE_UNKNOWN) {} + inline AtomField(const AtomField& that) :name(that.name), + javaType(that.javaType), + enumValues(that.enumValues) {} + inline AtomField(string n, java_type_t jt) :name(n), javaType(jt) {} + inline ~AtomField() {} +}; + +/** + * The name and code for an atom. + */ +struct AtomDecl { + int code; + string name; + + string message; + vector<AtomField> fields; + + AtomDecl(); + AtomDecl(const AtomDecl& that); + AtomDecl(int code, const string& name, const string& message); + ~AtomDecl(); + + inline bool operator<(const AtomDecl& that) const { + return (code == that.code) ? (name < that.name) : (code < that.code); + } +}; + +struct Atoms { + set<vector<java_type_t>> signatures; + set<AtomDecl> decls; +}; + +/** + * Gather the information about the atoms. Returns the number of errors. + */ +int collate_atoms(const Descriptor* descriptor, Atoms* atoms); + +} // namespace stats_log_api_gen +} // namespace android + + +#endif // ANDROID_STATS_LOG_API_GEN_COLLATION_H diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp new file mode 100644 index 000000000000..423d0285e96f --- /dev/null +++ b/tools/stats_log_api_gen/main.cpp @@ -0,0 +1,653 @@ + + +#include "Collation.h" + +#include "frameworks/base/cmds/statsd/src/atoms.pb.h" + +#include <set> +#include <vector> + +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +using namespace google::protobuf; +using namespace std; + +namespace android { +namespace stats_log_api_gen { + +const int PULL_ATOM_START_ID = 1000; + +int maxPushedAtomId = 2; + +using android::os::statsd::Atom; + +// TODO: Support WorkSources + +/** + * Turn lower and camel case into upper case with underscores. + */ +static string +make_constant_name(const string& str) +{ + string result; + const int N = str.size(); + bool underscore_next = false; + for (int i=0; i<N; i++) { + char c = str[i]; + if (c >= 'A' && c <= 'Z') { + if (underscore_next) { + result += '_'; + underscore_next = false; + } + } else if (c >= 'a' && c <= 'z') { + c = 'A' + c - 'a'; + underscore_next = true; + } else if (c == '_') { + underscore_next = false; + } + result += c; + } + return result; +} + +static const char* +cpp_type_name(java_type_t type) +{ + switch (type) { + case JAVA_TYPE_BOOLEAN: + return "bool"; + case JAVA_TYPE_INT: + case JAVA_TYPE_ENUM: + return "int32_t"; + case JAVA_TYPE_LONG: + return "int64_t"; + case JAVA_TYPE_FLOAT: + return "float"; + case JAVA_TYPE_DOUBLE: + return "double"; + case JAVA_TYPE_STRING: + return "char const*"; + default: + return "UNKNOWN"; + } +} + +static const char* +java_type_name(java_type_t type) +{ + switch (type) { + case JAVA_TYPE_BOOLEAN: + return "boolean"; + case JAVA_TYPE_INT: + case JAVA_TYPE_ENUM: + return "int"; + case JAVA_TYPE_LONG: + return "long"; + case JAVA_TYPE_FLOAT: + return "float"; + case JAVA_TYPE_DOUBLE: + return "double"; + case JAVA_TYPE_STRING: + return "java.lang.String"; + default: + return "UNKNOWN"; + } +} + +static int +write_stats_log_cpp(FILE* out, const Atoms& atoms) +{ + // Print prelude + fprintf(out, "// This file is autogenerated\n"); + fprintf(out, "\n"); + + fprintf(out, "#include <log/log_event_list.h>\n"); + fprintf(out, "#include <log/log.h>\n"); + fprintf(out, "#include <statslog.h>\n"); + fprintf(out, "\n"); + + fprintf(out, "namespace android {\n"); + fprintf(out, "namespace util {\n"); + fprintf(out, "// the single event tag id for all stats logs\n"); + fprintf(out, "const static int kStatsEventTag = 1937006964;\n"); + + // Print write methods + fprintf(out, "\n"); + for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); + signature != atoms.signatures.end(); signature++) { + int argIndex; + + fprintf(out, "void\n"); + fprintf(out, "stats_write(int32_t code"); + argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); + argIndex++; + } + fprintf(out, ")\n"); + + fprintf(out, "{\n"); + argIndex = 1; + fprintf(out, " android_log_event_list event(kStatsEventTag);\n"); + fprintf(out, " event << code;\n"); + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + if (*arg == JAVA_TYPE_STRING) { + fprintf(out, " if (arg%d == NULL) {\n", argIndex); + fprintf(out, " arg%d = \"\";\n", argIndex); + fprintf(out, " }\n"); + } + fprintf(out, " event << arg%d;\n", argIndex); + argIndex++; + } + + fprintf(out, " event.write(LOG_ID_STATS);\n"); + fprintf(out, "}\n"); + fprintf(out, "\n"); + } + + // Print footer + fprintf(out, "\n"); + fprintf(out, "} // namespace util\n"); + fprintf(out, "} // namespace android\n"); + + return 0; +} + + +static int +write_stats_log_header(FILE* out, const Atoms& atoms) +{ + // Print prelude + fprintf(out, "// This file is autogenerated\n"); + fprintf(out, "\n"); + fprintf(out, "#pragma once\n"); + fprintf(out, "\n"); + fprintf(out, "#include <stdint.h>\n"); + fprintf(out, "\n"); + + fprintf(out, "namespace android {\n"); + fprintf(out, "namespace util {\n"); + fprintf(out, "\n"); + fprintf(out, "/*\n"); + fprintf(out, " * API For logging statistics events.\n"); + fprintf(out, " */\n"); + fprintf(out, "\n"); + fprintf(out, "/**\n"); + fprintf(out, " * Constants for atom codes.\n"); + fprintf(out, " */\n"); + fprintf(out, "enum {\n"); + + size_t i = 0; + // Print constants + for (set<AtomDecl>::const_iterator atom = atoms.decls.begin(); + atom != atoms.decls.end(); atom++) { + string constant = make_constant_name(atom->name); + fprintf(out, "\n"); + fprintf(out, " /**\n"); + fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str()); + fprintf(out, " * Usage: stats_write(StatsLog.%s", constant.c_str()); + for (vector<AtomField>::const_iterator field = atom->fields.begin(); + field != atom->fields.end(); field++) { + fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str()); + } + fprintf(out, ");\n"); + fprintf(out, " */\n"); + char const* const comma = (i == atoms.decls.size() - 1) ? "" : ","; + fprintf(out, " %s = %d%s\n", constant.c_str(), atom->code, comma); + if (atom->code < PULL_ATOM_START_ID && atom->code > maxPushedAtomId) { + maxPushedAtomId = atom->code; + } + i++; + } + fprintf(out, "\n"); + fprintf(out, "};\n"); + fprintf(out, "\n"); + + fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n", + maxPushedAtomId); + + // Print write methods + fprintf(out, "//\n"); + fprintf(out, "// Write methods\n"); + fprintf(out, "//\n"); + for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); + signature != atoms.signatures.end(); signature++) { + fprintf(out, "void stats_write(int32_t code "); + int argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); + argIndex++; + } + fprintf(out, ");\n"); + } + + fprintf(out, "\n"); + fprintf(out, "} // namespace util\n"); + fprintf(out, "} // namespace android\n"); + + return 0; +} + +static int +write_stats_log_java(FILE* out, const Atoms& atoms) +{ + // Print prelude + fprintf(out, "// This file is autogenerated\n"); + fprintf(out, "\n"); + fprintf(out, "package android.util;\n"); + fprintf(out, "\n"); + fprintf(out, "\n"); + fprintf(out, "/**\n"); + fprintf(out, " * API For logging statistics events.\n"); + fprintf(out, " * @hide\n"); + fprintf(out, " */\n"); + fprintf(out, "public final class StatsLog {\n"); + fprintf(out, " // Constants for atom codes.\n"); + + // Print constants for the atom codes. + for (set<AtomDecl>::const_iterator atom = atoms.decls.begin(); + atom != atoms.decls.end(); atom++) { + string constant = make_constant_name(atom->name); + fprintf(out, "\n"); + fprintf(out, " /**\n"); + fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str()); + fprintf(out, " * Usage: StatsLog.write(StatsLog.%s", constant.c_str()); + for (vector<AtomField>::const_iterator field = atom->fields.begin(); + field != atom->fields.end(); field++) { + fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str()); + } + fprintf(out, ");\n"); + fprintf(out, " */\n"); + fprintf(out, " public static final int %s = %d;\n", constant.c_str(), atom->code); + } + fprintf(out, "\n"); + + // Print constants for the enum values. + fprintf(out, " // Constants for enum values.\n\n"); + for (set<AtomDecl>::const_iterator atom = atoms.decls.begin(); + atom != atoms.decls.end(); atom++) { + for (vector<AtomField>::const_iterator field = atom->fields.begin(); + field != atom->fields.end(); field++) { + if (field->javaType == JAVA_TYPE_ENUM) { + fprintf(out, " // Values for %s.%s\n", atom->message.c_str(), field->name.c_str()); + for (map<int, string>::const_iterator value = field->enumValues.begin(); + value != field->enumValues.end(); value++) { + fprintf(out, " public static final int %s__%s__%s = %d;\n", + make_constant_name(atom->message).c_str(), + make_constant_name(field->name).c_str(), + make_constant_name(value->second).c_str(), + value->first); + } + fprintf(out, "\n"); + } + } + } + + // Print write methods + fprintf(out, " // Write methods\n"); + for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); + signature != atoms.signatures.end(); signature++) { + fprintf(out, " public static native void write(int code"); + int argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex); + argIndex++; + } + fprintf(out, ");\n"); + } + + fprintf(out, "}\n"); + + return 0; +} + +static const char* +jni_type_name(java_type_t type) +{ + switch (type) { + case JAVA_TYPE_BOOLEAN: + return "jboolean"; + case JAVA_TYPE_INT: + case JAVA_TYPE_ENUM: + return "jint"; + case JAVA_TYPE_LONG: + return "jlong"; + case JAVA_TYPE_FLOAT: + return "jfloat"; + case JAVA_TYPE_DOUBLE: + return "jdouble"; + case JAVA_TYPE_STRING: + return "jstring"; + default: + return "UNKNOWN"; + } +} + +static string +jni_function_name(const vector<java_type_t>& signature) +{ + string result("StatsLog_write"); + for (vector<java_type_t>::const_iterator arg = signature.begin(); + arg != signature.end(); arg++) { + switch (*arg) { + case JAVA_TYPE_BOOLEAN: + result += "_boolean"; + break; + case JAVA_TYPE_INT: + case JAVA_TYPE_ENUM: + result += "_int"; + break; + case JAVA_TYPE_LONG: + result += "_long"; + break; + case JAVA_TYPE_FLOAT: + result += "_float"; + break; + case JAVA_TYPE_DOUBLE: + result += "_double"; + break; + case JAVA_TYPE_STRING: + result += "_String"; + break; + default: + result += "_UNKNOWN"; + break; + } + } + return result; +} + +static const char* +java_type_signature(java_type_t type) +{ + switch (type) { + case JAVA_TYPE_BOOLEAN: + return "Z"; + case JAVA_TYPE_INT: + case JAVA_TYPE_ENUM: + return "I"; + case JAVA_TYPE_LONG: + return "J"; + case JAVA_TYPE_FLOAT: + return "F"; + case JAVA_TYPE_DOUBLE: + return "D"; + case JAVA_TYPE_STRING: + return "Ljava/lang/String;"; + default: + return "UNKNOWN"; + } +} + +static string +jni_function_signature(const vector<java_type_t>& signature) +{ + string result("(I"); + for (vector<java_type_t>::const_iterator arg = signature.begin(); + arg != signature.end(); arg++) { + result += java_type_signature(*arg); + } + result += ")V"; + return result; +} + +static int +write_stats_log_jni(FILE* out, const Atoms& atoms) +{ + // Print prelude + fprintf(out, "// This file is autogenerated\n"); + fprintf(out, "\n"); + + fprintf(out, "#include <statslog.h>\n"); + fprintf(out, "\n"); + fprintf(out, "#include <nativehelper/JNIHelp.h>\n"); + fprintf(out, "#include \"core_jni_helpers.h\"\n"); + fprintf(out, "#include \"jni.h\"\n"); + fprintf(out, "\n"); + fprintf(out, "#define UNUSED __attribute__((__unused__))\n"); + fprintf(out, "\n"); + + fprintf(out, "namespace android {\n"); + fprintf(out, "\n"); + + // Print write methods + for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); + signature != atoms.signatures.end(); signature++) { + int argIndex; + + fprintf(out, "static void\n"); + fprintf(out, "%s(JNIEnv* env, jobject clazz UNUSED, jint code", + jni_function_name(*signature).c_str()); + argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + fprintf(out, ", %s arg%d", jni_type_name(*arg), argIndex); + argIndex++; + } + fprintf(out, ")\n"); + + fprintf(out, "{\n"); + + // Prepare strings + argIndex = 1; + bool hadString = false; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + if (*arg == JAVA_TYPE_STRING) { + fprintf(out, " const char* str%d;\n", argIndex); + fprintf(out, " if (arg%d != NULL) {\n", argIndex); + fprintf(out, " str%d = env->GetStringUTFChars(arg%d, NULL);\n", + argIndex, argIndex); + fprintf(out, " } else {\n"); + fprintf(out, " str%d = NULL;\n", argIndex); + fprintf(out, " }\n"); + hadString = true; + } + argIndex++; + } + + // Emit this to quiet the unused parameter warning if there were no strings. + if (!hadString) { + fprintf(out, " (void)env;\n"); + } + + // stats_write call + argIndex = 1; + fprintf(out, " android::util::stats_write(code"); + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + const char* argName = (*arg == JAVA_TYPE_STRING) ? "str" : "arg"; + fprintf(out, ", (%s)%s%d", cpp_type_name(*arg), argName, argIndex); + argIndex++; + } + fprintf(out, ");\n"); + + // Clean up strings + argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + if (*arg == JAVA_TYPE_STRING) { + fprintf(out, " if (str%d != NULL) {\n", argIndex); + fprintf(out, " env->ReleaseStringUTFChars(arg%d, str%d);\n", + argIndex, argIndex); + fprintf(out, " }\n"); + } + argIndex++; + } + + fprintf(out, "}\n"); + fprintf(out, "\n"); + } + + // Print registration function table + fprintf(out, "/*\n"); + fprintf(out, " * JNI registration.\n"); + fprintf(out, " */\n"); + fprintf(out, "static const JNINativeMethod gRegisterMethods[] = {\n"); + for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); + signature != atoms.signatures.end(); signature++) { + fprintf(out, " { \"write\", \"%s\", (void*)%s },\n", + jni_function_signature(*signature).c_str(), + jni_function_name(*signature).c_str()); + } + fprintf(out, "};\n"); + fprintf(out, "\n"); + + // Print registration function + fprintf(out, "int register_android_util_StatsLog(JNIEnv* env) {\n"); + fprintf(out, " return RegisterMethodsOrDie(\n"); + fprintf(out, " env,\n"); + fprintf(out, " \"android/util/StatsLog\",\n"); + fprintf(out, " gRegisterMethods, NELEM(gRegisterMethods));\n"); + fprintf(out, "}\n"); + + fprintf(out, "\n"); + fprintf(out, "} // namespace android\n"); + + return 0; +} + + +static void +print_usage() +{ + fprintf(stderr, "usage: stats-log-api-gen OPTIONS\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "OPTIONS\n"); + fprintf(stderr, " --cpp FILENAME the header file to output\n"); + fprintf(stderr, " --header FILENAME the cpp file to output\n"); + fprintf(stderr, " --help this message\n"); + fprintf(stderr, " --java FILENAME the java file to output\n"); + fprintf(stderr, " --jni FILENAME the jni file to output\n"); +} + +/** + * Do the argument parsing and execute the tasks. + */ +static int +run(int argc, char const*const* argv) +{ + string cppFilename; + string headerFilename; + string javaFilename; + string jniFilename; + + int index = 1; + while (index < argc) { + if (0 == strcmp("--help", argv[index])) { + print_usage(); + return 0; + } else if (0 == strcmp("--cpp", argv[index])) { + index++; + if (index >= argc) { + print_usage(); + return 1; + } + cppFilename = argv[index]; + } else if (0 == strcmp("--header", argv[index])) { + index++; + if (index >= argc) { + print_usage(); + return 1; + } + headerFilename = argv[index]; + } else if (0 == strcmp("--java", argv[index])) { + index++; + if (index >= argc) { + print_usage(); + return 1; + } + javaFilename = argv[index]; + } else if (0 == strcmp("--jni", argv[index])) { + index++; + if (index >= argc) { + print_usage(); + return 1; + } + jniFilename = argv[index]; + } + index++; + } + + if (cppFilename.size() == 0 + && headerFilename.size() == 0 + && javaFilename.size() == 0 + && jniFilename.size() == 0) { + print_usage(); + return 1; + } + + // Collate the parameters + Atoms atoms; + int errorCount = collate_atoms(Atom::descriptor(), &atoms); + if (errorCount != 0) { + return 1; + } + + // Write the .cpp file + if (cppFilename.size() != 0) { + FILE* out = fopen(cppFilename.c_str(), "w"); + if (out == NULL) { + fprintf(stderr, "Unable to open file for write: %s\n", cppFilename.c_str()); + return 1; + } + errorCount = android::stats_log_api_gen::write_stats_log_cpp(out, atoms); + fclose(out); + } + + // Write the .h file + if (headerFilename.size() != 0) { + FILE* out = fopen(headerFilename.c_str(), "w"); + if (out == NULL) { + fprintf(stderr, "Unable to open file for write: %s\n", headerFilename.c_str()); + return 1; + } + errorCount = android::stats_log_api_gen::write_stats_log_header(out, atoms); + fclose(out); + } + + // Write the .java file + if (javaFilename.size() != 0) { + FILE* out = fopen(javaFilename.c_str(), "w"); + if (out == NULL) { + fprintf(stderr, "Unable to open file for write: %s\n", javaFilename.c_str()); + return 1; + } + errorCount = android::stats_log_api_gen::write_stats_log_java(out, atoms); + fclose(out); + } + + // Write the jni file + if (jniFilename.size() != 0) { + FILE* out = fopen(jniFilename.c_str(), "w"); + if (out == NULL) { + fprintf(stderr, "Unable to open file for write: %s\n", jniFilename.c_str()); + return 1; + } + errorCount = android::stats_log_api_gen::write_stats_log_jni(out, atoms); + fclose(out); + } + + return 0; +} + +} +} + +/** + * Main. + */ +int +main(int argc, char const*const* argv) +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + + return android::stats_log_api_gen::run(argc, argv); +} diff --git a/tools/stats_log_api_gen/test.proto b/tools/stats_log_api_gen/test.proto new file mode 100644 index 000000000000..66861588a008 --- /dev/null +++ b/tools/stats_log_api_gen/test.proto @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2017 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. + */ + +syntax = "proto2"; + +import "frameworks/base/cmds/statsd/src/atoms.proto"; + +package android.stats_log_api_gen; + +message IntAtom { + optional int32 field1 = 1; +} + +message AnotherIntAtom { + optional int32 field1 = 1; +} + +message OutOfOrderAtom { + optional int32 field2 = 2; + optional int32 field1 = 1; +} + +enum AnEnum { + VALUE0 = 0; + VALUE1 = 1; +} + +message AllTypesAtom { + optional android.os.statsd.WorkSource attribution = 1; + optional double double_field = 2; + optional float float_field = 3; + optional int64 int64_field = 4; + optional uint64 uint64_field = 5; + optional int32 int32_field = 6; + optional fixed64 fixed64_field = 7; + optional fixed32 fixed32_field = 8; + optional bool bool_field = 9; + optional string string_field = 10; + optional uint32 uint32_field = 11; + optional AnEnum enum_field = 12; + optional sfixed32 sfixed32_field = 13; + optional sfixed64 sfixed64_field = 14; + optional sint32 sint32_field = 15; + optional sint64 sint64_field = 16; +} + +message Event { + oneof event { + OutOfOrderAtom out_of_order_atom = 2; + IntAtom int_atom = 1; + AnotherIntAtom another_int_atom = 3; + AllTypesAtom all_types_atom = 4; + } +} + +message BadTypesAtom { + optional IntAtom bad_int_atom = 1; + optional bytes bad_bytes = 2; +} + +message BadTypesEvent { + oneof event { + BadTypesAtom bad_types_atom = 1; + } +} + +message BadSkippedFieldSingleAtom { + optional int32 field2 = 2; +} + +message BadSkippedFieldSingle { + oneof event { + BadSkippedFieldSingleAtom bad = 1; + } +} + +message BadSkippedFieldMultipleAtom { + optional int32 field1 = 1; + optional int32 field3 = 3; + optional int32 field5 = 5; +} + +message BadSkippedFieldMultiple { + oneof event { + BadSkippedFieldMultipleAtom bad = 1; + } +} + +message BadWorkSourcePositionAtom { + optional int32 field1 = 1; + optional android.os.statsd.WorkSource attribution = 2; +} + +message BadWorkSourcePosition { + oneof event { + BadWorkSourcePositionAtom bad = 1; + } +} + diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp new file mode 100644 index 000000000000..073f2cfce6a8 --- /dev/null +++ b/tools/stats_log_api_gen/test_collation.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2017, 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 <gtest/gtest.h> + +#include "frameworks/base/tools/stats_log_api_gen/test.pb.h" +#include "Collation.h" + +#include <stdio.h> + +namespace android { +namespace stats_log_api_gen { + +using std::map; +using std::set; +using std::vector; + +/** + * Return whether the set contains a vector of the elements provided. + */ +static bool +set_contains_vector(const set<vector<java_type_t>>& s, int count, ...) +{ + va_list args; + vector<java_type_t> v; + + va_start(args, count); + for (int i=0; i<count; i++) { + v.push_back((java_type_t)va_arg(args, int)); + } + va_end(args); + + return s.find(v) != s.end(); +} + +/** + * Expect that the provided set contains the elements provided. + */ +#define EXPECT_SET_CONTAINS_SIGNATURE(s, ...) \ + do { \ + int count = sizeof((int[]){__VA_ARGS__})/sizeof(int); \ + EXPECT_TRUE(set_contains_vector(s, count, __VA_ARGS__)); \ + } while(0) + +/** Expects that the provided atom has no enum values for any field. */ +#define EXPECT_NO_ENUM_FIELD(atom) \ + do { \ + for (vector<AtomField>::const_iterator field = atom->fields.begin(); \ + field != atom->fields.end(); field++) { \ + EXPECT_TRUE(field->enumValues.empty()); \ + } \ + } while(0) + +/** Expects that exactly one specific field has expected enum values. */ +#define EXPECT_HAS_ENUM_FIELD(atom, field_name, values) \ + do { \ + for (vector<AtomField>::const_iterator field = atom->fields.begin(); \ + field != atom->fields.end(); field++) { \ + if (field->name == field_name) { \ + EXPECT_EQ(field->enumValues, values); \ + } else { \ + EXPECT_TRUE(field->enumValues.empty()); \ + } \ + } \ + } while(0) + + +/** + * Test a correct collation, with all the types. + */ +TEST(CollationTest, CollateStats) { + Atoms atoms; + int errorCount = collate_atoms(Event::descriptor(), &atoms); + + EXPECT_EQ(0, errorCount); + EXPECT_EQ(3ul, atoms.signatures.size()); + + // IntAtom, AnotherIntAtom + EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures, JAVA_TYPE_INT); + + // OutOfOrderAtom + EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures, JAVA_TYPE_INT, JAVA_TYPE_INT); + + // AllTypesAtom + EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures, + JAVA_TYPE_WORK_SOURCE, // WorkSource + JAVA_TYPE_DOUBLE, // double + JAVA_TYPE_FLOAT, // float + JAVA_TYPE_LONG, // int64 + JAVA_TYPE_LONG, // uint64 + JAVA_TYPE_INT, // int32 + JAVA_TYPE_LONG, // fixed64 + JAVA_TYPE_INT, // fixed32 + JAVA_TYPE_BOOLEAN, // bool + JAVA_TYPE_STRING, // string + JAVA_TYPE_INT, // uint32 + JAVA_TYPE_INT, // AnEnum + JAVA_TYPE_INT, // sfixed32 + JAVA_TYPE_LONG, // sfixed64 + JAVA_TYPE_INT, // sint32 + JAVA_TYPE_LONG // sint64 + ); + + set<AtomDecl>::const_iterator atom = atoms.decls.begin(); + EXPECT_EQ(1, atom->code); + EXPECT_EQ("int_atom", atom->name); + EXPECT_EQ("IntAtom", atom->message); + EXPECT_NO_ENUM_FIELD(atom); + atom++; + + EXPECT_EQ(2, atom->code); + EXPECT_EQ("out_of_order_atom", atom->name); + EXPECT_EQ("OutOfOrderAtom", atom->message); + EXPECT_NO_ENUM_FIELD(atom); + atom++; + + EXPECT_EQ(3, atom->code); + EXPECT_EQ("another_int_atom", atom->name); + EXPECT_EQ("AnotherIntAtom", atom->message); + EXPECT_NO_ENUM_FIELD(atom); + atom++; + + EXPECT_EQ(4, atom->code); + EXPECT_EQ("all_types_atom", atom->name); + EXPECT_EQ("AllTypesAtom", atom->message); + map<int, string> enumValues; + enumValues[0] = "VALUE0"; + enumValues[1] = "VALUE1"; + EXPECT_HAS_ENUM_FIELD(atom, "enum_field", enumValues); + atom++; + + EXPECT_TRUE(atom == atoms.decls.end()); +} + +/** + * Test that event class that contains stuff other than the atoms is rejected. + */ +TEST(CollationTest, NonMessageTypeFails) { + Atoms atoms; + int errorCount = collate_atoms(IntAtom::descriptor(), &atoms); + + EXPECT_EQ(1, errorCount); +} + +/** + * Test that atoms that have non-primitive types are rejected. + */ +TEST(CollationTest, FailOnBadTypes) { + Atoms atoms; + int errorCount = collate_atoms(BadTypesEvent::descriptor(), &atoms); + + EXPECT_EQ(2, errorCount); +} + +/** + * Test that atoms that skip field numbers (in the first position) are rejected. + */ +TEST(CollationTest, FailOnSkippedFieldsSingle) { + Atoms atoms; + int errorCount = collate_atoms(BadSkippedFieldSingle::descriptor(), &atoms); + + EXPECT_EQ(1, errorCount); +} + +/** + * Test that atoms that skip field numbers (not in the first position, and multiple + * times) are rejected. + */ +TEST(CollationTest, FailOnSkippedFieldsMultiple) { + Atoms atoms; + int errorCount = collate_atoms(BadSkippedFieldMultiple::descriptor(), &atoms); + + EXPECT_EQ(2, errorCount); +} + +/** + * Test that atoms that have a WorkSource not in the first position are rejected. + */ +TEST(CollationTest, FailBadWorkSourcePosition) { + Atoms atoms; + int errorCount = collate_atoms(BadWorkSourcePosition::descriptor(), &atoms); + + EXPECT_EQ(1, errorCount); +} + +} // namespace stats_log_api_gen +} // namespace android diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp index 24068e9ffe92..4e9391db9a5b 100644 --- a/tools/streaming_proto/Android.bp +++ b/tools/streaming_proto/Android.bp @@ -17,13 +17,56 @@ // ========================================================== // Build the host executable: protoc-gen-javastream // ========================================================== -cc_binary_host { - name: "protoc-gen-javastream", +cc_defaults { + name: "protoc-gen-stream-defaults", srcs: [ "Errors.cpp", + "stream_proto_utils.cpp", "string_utils.cpp", - "main.cpp", + ], + cflags: [ + "-Wall", + "-Werror", ], shared_libs: ["libprotoc"], } + +cc_library { + name: "streamingflags", + host_supported: true, + proto: { + export_proto_headers: true, + include_dirs: ["external/protobuf/src"], + }, + + target: { + host: { + proto: { + type: "full", + }, + srcs: [ + "stream.proto", + ], + }, + }, +} + +cc_binary_host { + name: "protoc-gen-javastream", + srcs: [ + "java/main.cpp", + ], + + defaults: ["protoc-gen-stream-defaults"], +} + +cc_binary_host { + name: "protoc-gen-cppstream", + srcs: [ + "cpp/main.cpp", + ], + + defaults: ["protoc-gen-stream-defaults"], + static_libs: ["streamingflags"], +} diff --git a/tools/streaming_proto/Errors.cpp b/tools/streaming_proto/Errors.cpp index 91c6b9245de0..0cd9037dcb55 100644 --- a/tools/streaming_proto/Errors.cpp +++ b/tools/streaming_proto/Errors.cpp @@ -3,7 +3,7 @@ #include <stdlib.h> namespace android { -namespace javastream_proto { +namespace stream_proto { Errors ERRORS; @@ -82,6 +82,6 @@ Errors::HasErrors() const return m_errors.size() > 0; } -} // namespace javastream_proto +} // namespace stream_proto } // namespace android diff --git a/tools/streaming_proto/Errors.h b/tools/streaming_proto/Errors.h index 109195a20b06..f14bbfd55b5f 100644 --- a/tools/streaming_proto/Errors.h +++ b/tools/streaming_proto/Errors.h @@ -4,7 +4,7 @@ #include <vector> namespace android { -namespace javastream_proto { +namespace stream_proto { using namespace std; @@ -44,5 +44,5 @@ extern const string UNKNOWN_FILE; extern const int UNKNOWN_LINE; -} // namespace javastream_proto +} // namespace stream_proto } // namespace android diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp new file mode 100644 index 000000000000..477902065f99 --- /dev/null +++ b/tools/streaming_proto/cpp/main.cpp @@ -0,0 +1,212 @@ +#include "Errors.h" +#include "stream_proto_utils.h" +#include "string_utils.h" + +#include <frameworks/base/tools/streaming_proto/stream.pb.h> + +#include <iomanip> +#include <iostream> +#include <sstream> + +using namespace android::stream_proto; +using namespace google::protobuf::io; +using namespace std; + +static string +make_filename(const FileDescriptorProto& file_descriptor) +{ + return file_descriptor.name() + ".h"; +} + +static inline bool +should_generate_enums_mapping(const EnumDescriptorProto& enu) +{ + return enu.options().GetExtension(stream_enum).enable_enums_mapping(); +} + +static void +write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent) +{ + const int N = enu.value_size(); + text << indent << "// enum " << enu.name() << endl; + for (int i=0; i<N; i++) { + const EnumValueDescriptorProto& value = enu.value(i); + text << indent << "const int " + << make_constant_name(value.name()) + << " = " << value.number() << ";" << endl; + } + + if (should_generate_enums_mapping(enu)) { + string name = make_constant_name(enu.name()); + string prefix = name + "_"; + text << indent << "const int _ENUM_" << name << "_COUNT = " << N << ";" << endl; + text << indent << "const char* _ENUM_" << name << "_NAMES[" << N << "] = {" << endl; + for (int i=0; i<N; i++) { + text << indent << INDENT << "\"" << stripPrefix(enu.value(i).name(), prefix) << "\"," << endl; + } + text << indent << "};" << endl; + text << indent << "const int _ENUM_" << name << "_VALUES[" << N << "] = {" << endl; + for (int i=0; i<N; i++) { + text << indent << INDENT << make_constant_name(enu.value(i).name()) << "," << endl; + } + text << indent << "};" << endl; + } + + text << endl; +} + +static void +write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent) +{ + string optional_comment = field.label() == FieldDescriptorProto::LABEL_OPTIONAL + ? "optional " : ""; + string repeated_comment = field.label() == FieldDescriptorProto::LABEL_REPEATED + ? "repeated " : ""; + string proto_type = get_proto_type(field); + string packed_comment = field.options().packed() + ? " [packed=true]" : ""; + text << indent << "// " << optional_comment << repeated_comment << proto_type << ' ' + << field.name() << " = " << field.number() << packed_comment << ';' << endl; + + text << indent << "const uint64_t " << make_constant_name(field.name()) << " = 0x"; + + ios::fmtflags fmt(text.flags()); + text << setfill('0') << setw(16) << hex << get_field_id(field); + text.flags(fmt); + + text << "LL;" << endl; + + text << endl; +} + +static inline bool +should_generate_fields_mapping(const DescriptorProto& message) +{ + return message.options().GetExtension(stream_msg).enable_fields_mapping(); +} + +static void +write_message(stringstream& text, const DescriptorProto& message, const string& indent) +{ + int N; + const string indented = indent + INDENT; + + text << indent << "// message " << message.name() << endl; + text << indent << "namespace " << message.name() << " {" << endl; + + // Enums + N = message.enum_type_size(); + for (int i=0; i<N; i++) { + write_enum(text, message.enum_type(i), indented); + } + + // Nested classes + N = message.nested_type_size(); + for (int i=0; i<N; i++) { + write_message(text, message.nested_type(i), indented); + } + + // Fields + N = message.field_size(); + for (int i=0; i<N; i++) { + write_field(text, message.field(i), indented); + } + + if (should_generate_fields_mapping(message)) { + N = message.field_size(); + text << indented << "const int _FIELD_COUNT = " << N << ";" << endl; + text << indented << "const char* _FIELD_NAMES[" << N << "] = {" << endl; + for (int i=0; i<N; i++) { + text << indented << INDENT << "\"" << message.field(i).name() << "\"," << endl; + } + text << indented << "};" << endl; + text << indented << "const uint64_t _FIELD_IDS[" << N << "] = {" << endl; + for (int i=0; i<N; i++) { + text << indented << INDENT << make_constant_name(message.field(i).name()) << "," << endl; + } + text << indented << "};" << endl << endl; + } + + text << indent << "} //" << message.name() << endl; + text << endl; +} + +static void +write_header_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor) +{ + stringstream text; + + text << "// Generated by protoc-gen-cppstream. DO NOT MODIFY." << endl; + text << "// source: " << file_descriptor.name() << endl << endl; + + string header = "ANDROID_" + replace_string(file_descriptor.name(), '/', '_'); + header = replace_string(header, '.', '_') + "_stream_h"; + header = make_constant_name(header); + + text << "#ifndef " << header << endl; + text << "#define " << header << endl; + text << endl; + + vector<string> namespaces = split(file_descriptor.package(), '.'); + for (vector<string>::iterator it = namespaces.begin(); it != namespaces.end(); it++) { + text << "namespace " << *it << " {" << endl; + } + text << endl; + + size_t N; + N = file_descriptor.enum_type_size(); + for (size_t i=0; i<N; i++) { + write_enum(text, file_descriptor.enum_type(i), ""); + } + + N = file_descriptor.message_type_size(); + for (size_t i=0; i<N; i++) { + write_message(text, file_descriptor.message_type(i), ""); + } + + for (vector<string>::iterator it = namespaces.begin(); it != namespaces.end(); it++) { + text << "} // " << *it << endl; + } + + text << endl; + text << "#endif // " << header << endl; + + CodeGeneratorResponse::File* file_response = response->add_file(); + file_response->set_name(make_filename(file_descriptor)); + file_response->set_content(text.str()); +} + +int main(int argc, char const *argv[]) +{ + (void)argc; + (void)argv; + + GOOGLE_PROTOBUF_VERIFY_VERSION; + + CodeGeneratorRequest request; + CodeGeneratorResponse response; + + // Read the request + request.ParseFromIstream(&cin); + + // Build the files we need. + const int N = request.proto_file_size(); + for (int i=0; i<N; i++) { + const FileDescriptorProto& file_descriptor = request.proto_file(i); + if (should_generate_for_file(request, file_descriptor.name())) { + write_header_file(&response, file_descriptor); + } + } + + // If we had errors, don't write the response. Print the errors and exit. + if (ERRORS.HasErrors()) { + ERRORS.Print(); + return 1; + } + + // If we didn't have errors, write the response and exit happily. + response.SerializeToOstream(&cout); + + /* code */ + return 0; +} diff --git a/tools/streaming_proto/main.cpp b/tools/streaming_proto/java/main.cpp index 5b4ba04b2b58..c9c50a561a04 100644 --- a/tools/streaming_proto/main.cpp +++ b/tools/streaming_proto/java/main.cpp @@ -1,63 +1,17 @@ #include "Errors.h" - +#include "stream_proto_utils.h" #include "string_utils.h" -#include "google/protobuf/compiler/plugin.pb.h" -#include "google/protobuf/io/zero_copy_stream_impl.h" -#include "google/protobuf/text_format.h" - #include <stdio.h> #include <iomanip> #include <iostream> #include <sstream> #include <map> -using namespace android::javastream_proto; -using namespace google::protobuf; -using namespace google::protobuf::compiler; +using namespace android::stream_proto; using namespace google::protobuf::io; using namespace std; -const int FIELD_TYPE_SHIFT = 32; -const uint64_t FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT; -const uint64_t FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT; - -const int FIELD_COUNT_SHIFT = 40; -const uint64_t FIELD_COUNT_SINGLE = 1L << FIELD_COUNT_SHIFT; -const uint64_t FIELD_COUNT_REPEATED = 2L << FIELD_COUNT_SHIFT; -const uint64_t FIELD_COUNT_PACKED = 5L << FIELD_COUNT_SHIFT; - - -/** - * See if this is the file for this request, and not one of the imported ones. - */ -static bool -should_generate_for_file(const CodeGeneratorRequest& request, const string& file) -{ - const int N = request.file_to_generate_size(); - for (int i=0; i<N; i++) { - if (request.file_to_generate(i) == file) { - return true; - } - } - return false; -} - /** * If the descriptor gives us a class name, use that. Otherwise make one up from * the filename of the .proto file. @@ -112,7 +66,7 @@ make_file_name(const FileDescriptorProto& file_descriptor, const string& class_n static string indent_more(const string& indent) { - return indent + " "; + return indent + INDENT; } /** @@ -133,130 +87,6 @@ write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& ind } /** - * Get the string name for a field. - */ -static string -get_proto_type(const FieldDescriptorProto& field) -{ - switch (field.type()) { - case FieldDescriptorProto::TYPE_DOUBLE: - return "double"; - case FieldDescriptorProto::TYPE_FLOAT: - return "float"; - case FieldDescriptorProto::TYPE_INT64: - return "int64"; - case FieldDescriptorProto::TYPE_UINT64: - return "uint64"; - case FieldDescriptorProto::TYPE_INT32: - return "int32"; - case FieldDescriptorProto::TYPE_FIXED64: - return "fixed64"; - case FieldDescriptorProto::TYPE_FIXED32: - return "fixed32"; - case FieldDescriptorProto::TYPE_BOOL: - return "bool"; - case FieldDescriptorProto::TYPE_STRING: - return "string"; - case FieldDescriptorProto::TYPE_GROUP: - return "group<unsupported!>"; - case FieldDescriptorProto::TYPE_MESSAGE: - return field.type_name(); - case FieldDescriptorProto::TYPE_BYTES: - return "bytes"; - case FieldDescriptorProto::TYPE_UINT32: - return "uint32"; - case FieldDescriptorProto::TYPE_ENUM: - return field.type_name(); - case FieldDescriptorProto::TYPE_SFIXED32: - return "sfixed32"; - case FieldDescriptorProto::TYPE_SFIXED64: - return "sfixed64"; - case FieldDescriptorProto::TYPE_SINT32: - return "sint32"; - case FieldDescriptorProto::TYPE_SINT64: - return "sint64"; - default: - // won't happen - return "void"; - } -} - -static uint64_t -get_field_id(const FieldDescriptorProto& field) -{ - // Number - uint64_t result = (uint32_t)field.number(); - - // Type - switch (field.type()) { - case FieldDescriptorProto::TYPE_DOUBLE: - result |= FIELD_TYPE_DOUBLE; - break; - case FieldDescriptorProto::TYPE_FLOAT: - result |= FIELD_TYPE_FLOAT; - break; - case FieldDescriptorProto::TYPE_INT64: - result |= FIELD_TYPE_INT64; - break; - case FieldDescriptorProto::TYPE_UINT64: - result |= FIELD_TYPE_UINT64; - break; - case FieldDescriptorProto::TYPE_INT32: - result |= FIELD_TYPE_INT32; - break; - case FieldDescriptorProto::TYPE_FIXED64: - result |= FIELD_TYPE_FIXED64; - break; - case FieldDescriptorProto::TYPE_FIXED32: - result |= FIELD_TYPE_FIXED32; - break; - case FieldDescriptorProto::TYPE_BOOL: - result |= FIELD_TYPE_BOOL; - break; - case FieldDescriptorProto::TYPE_STRING: - result |= FIELD_TYPE_STRING; - break; - case FieldDescriptorProto::TYPE_MESSAGE: - result |= FIELD_TYPE_OBJECT; - break; - case FieldDescriptorProto::TYPE_BYTES: - result |= FIELD_TYPE_BYTES; - break; - case FieldDescriptorProto::TYPE_UINT32: - result |= FIELD_TYPE_UINT32; - break; - case FieldDescriptorProto::TYPE_ENUM: - result |= FIELD_TYPE_ENUM; - break; - case FieldDescriptorProto::TYPE_SFIXED32: - result |= FIELD_TYPE_SFIXED32; - break; - case FieldDescriptorProto::TYPE_SFIXED64: - result |= FIELD_TYPE_SFIXED64; - break; - case FieldDescriptorProto::TYPE_SINT32: - result |= FIELD_TYPE_SINT32; - break; - case FieldDescriptorProto::TYPE_SINT64: - result |= FIELD_TYPE_SINT64; - break; - default: - ; - } - - // Count - if (field.options().packed()) { - result |= FIELD_COUNT_PACKED; - } else if (field.label() == FieldDescriptorProto::LABEL_REPEATED) { - result |= FIELD_COUNT_REPEATED; - } else { - result |= FIELD_COUNT_SINGLE; - } - - return result; -} - -/** * Write a field. */ static void diff --git a/tools/streaming_proto/stream.proto b/tools/streaming_proto/stream.proto new file mode 100644 index 000000000000..c08120986cc7 --- /dev/null +++ b/tools/streaming_proto/stream.proto @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017 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. + */ + +syntax = "proto2"; + +import "google/protobuf/descriptor.proto"; + +package android.stream_proto; + +// This option tells streaming proto plugin to compile .proto files with extra features. +message MessageOptions { + // creates a mapping of field names of the message to its field ids + optional bool enable_fields_mapping = 1; +} + +extend google.protobuf.MessageOptions { + // Flags used by streaming proto plugins + optional MessageOptions stream_msg = 126856794; +} + +message EnumOptions { + // creates a mapping of enum names to its values, strip its prefix enum type for each value + optional bool enable_enums_mapping = 1; +} + +extend google.protobuf.EnumOptions { + // Flags used by streaming proto plugins + optional EnumOptions stream_enum = 126856794; +} diff --git a/tools/streaming_proto/stream_proto_utils.cpp b/tools/streaming_proto/stream_proto_utils.cpp new file mode 100644 index 000000000000..fb2d98d45b0e --- /dev/null +++ b/tools/streaming_proto/stream_proto_utils.cpp @@ -0,0 +1,100 @@ +#include "stream_proto_utils.h" + +namespace android { +namespace stream_proto { + +/** + * Position of the field type in a (long long) fieldId. + */ +const uint64_t FIELD_TYPE_SHIFT = 32; + +// +// FieldId flags for whether the field is single, repeated or packed. +// TODO: packed is not supported yet. +// +const uint64_t FIELD_COUNT_SHIFT = 40; +const uint64_t FIELD_COUNT_SINGLE = 1ULL << FIELD_COUNT_SHIFT; +const uint64_t FIELD_COUNT_REPEATED = 2ULL << FIELD_COUNT_SHIFT; +const uint64_t FIELD_COUNT_PACKED = 5ULL << FIELD_COUNT_SHIFT; + +uint64_t +get_field_id(const FieldDescriptorProto& field) +{ + // Number + uint64_t result = (uint32_t)field.number(); + + // Type + result |= (uint64_t)field.type() << FIELD_TYPE_SHIFT; + + // Count + if (field.options().packed()) { + result |= FIELD_COUNT_PACKED; + } else if (field.label() == FieldDescriptorProto::LABEL_REPEATED) { + result |= FIELD_COUNT_REPEATED; + } else { + result |= FIELD_COUNT_SINGLE; + } + + return result; +} + +string +get_proto_type(const FieldDescriptorProto& field) +{ + switch (field.type()) { + case FieldDescriptorProto::TYPE_DOUBLE: + return "double"; + case FieldDescriptorProto::TYPE_FLOAT: + return "float"; + case FieldDescriptorProto::TYPE_INT64: + return "int64"; + case FieldDescriptorProto::TYPE_UINT64: + return "uint64"; + case FieldDescriptorProto::TYPE_INT32: + return "int32"; + case FieldDescriptorProto::TYPE_FIXED64: + return "fixed64"; + case FieldDescriptorProto::TYPE_FIXED32: + return "fixed32"; + case FieldDescriptorProto::TYPE_BOOL: + return "bool"; + case FieldDescriptorProto::TYPE_STRING: + return "string"; + case FieldDescriptorProto::TYPE_GROUP: + return "group<unsupported!>"; + case FieldDescriptorProto::TYPE_MESSAGE: + return field.type_name(); + case FieldDescriptorProto::TYPE_BYTES: + return "bytes"; + case FieldDescriptorProto::TYPE_UINT32: + return "uint32"; + case FieldDescriptorProto::TYPE_ENUM: + return field.type_name(); + case FieldDescriptorProto::TYPE_SFIXED32: + return "sfixed32"; + case FieldDescriptorProto::TYPE_SFIXED64: + return "sfixed64"; + case FieldDescriptorProto::TYPE_SINT32: + return "sint32"; + case FieldDescriptorProto::TYPE_SINT64: + return "sint64"; + default: + // won't happen + return "void"; + } +} + +bool +should_generate_for_file(const CodeGeneratorRequest& request, const string& file) +{ + const int N = request.file_to_generate_size(); + for (int i=0; i<N; i++) { + if (request.file_to_generate(i) == file) { + return true; + } + } + return false; +} + +} // stream_proto +} // android diff --git a/tools/streaming_proto/stream_proto_utils.h b/tools/streaming_proto/stream_proto_utils.h new file mode 100644 index 000000000000..5297eccba849 --- /dev/null +++ b/tools/streaming_proto/stream_proto_utils.h @@ -0,0 +1,29 @@ +#include <stdint.h> + +#include "google/protobuf/compiler/plugin.pb.h" +#include "google/protobuf/io/zero_copy_stream_impl.h" + +namespace android { +namespace stream_proto { + +using namespace google::protobuf; +using namespace google::protobuf::compiler; +using namespace std; + +/** + * Get encoded field id from a field. + */ +uint64_t get_field_id(const FieldDescriptorProto& field); + +/** + * Get the string name for a field. + */ +string get_proto_type(const FieldDescriptorProto& field); + +/** + * See if this is the file for this request, and not one of the imported ones. + */ +bool should_generate_for_file(const CodeGeneratorRequest& request, const string& file); + +} // stream_proto +} // android diff --git a/tools/streaming_proto/string_utils.cpp b/tools/streaming_proto/string_utils.cpp index cc738c4c108e..607d820033ff 100644 --- a/tools/streaming_proto/string_utils.cpp +++ b/tools/streaming_proto/string_utils.cpp @@ -3,7 +3,7 @@ #include <iostream> namespace android { -namespace javastream_proto { +namespace stream_proto { using namespace std; @@ -89,7 +89,37 @@ replace_string(const string& str, const char replace, const char with) return result; } -} // namespace javastream_proto +vector<string> +split(const string& str, const char delimiter) +{ + vector<string> result; + size_t base = 0, found = 0; + while (true) { + found = str.find_first_of(delimiter, base); + if (found != base) { + string part = str.substr(base, found - base); + if (!part.empty()) { + result.push_back(part); + } + } + if (found == str.npos) break; + base = found + 1; + } + return result; +} + +string +stripPrefix(const string& str, const string& prefix) +{ + if (str.size() <= prefix.size()) return str; + size_t i = 0, len = prefix.size(); + for (; i<len; i++) { + if (str[i] != prefix[i]) return str; + } + return str.substr(i); +} + +} // namespace stream_proto } // namespace android diff --git a/tools/streaming_proto/string_utils.h b/tools/streaming_proto/string_utils.h index ffe83ca99704..315b27531afd 100644 --- a/tools/streaming_proto/string_utils.h +++ b/tools/streaming_proto/string_utils.h @@ -1,10 +1,14 @@ #include <string> +#include <vector> namespace android { -namespace javastream_proto { +namespace stream_proto { using namespace std; +// Indent +const string INDENT = " "; + /** * Capitalizes the string, removes underscores and makes the next letter * capitalized, and makes the letter following numbers capitalized. @@ -22,11 +26,20 @@ string make_constant_name(const string& str); string file_base_name(const string& str); /** - * Replace all occurances of 'replace' with 'with'. + * Replaces all occurances of 'replace' with 'with'. */ string replace_string(const string& str, const char replace, const char with); +/** + * Splits a string to parts by delimiter. + */ +vector<string> split(const string& str, const char delimiter); + +/** + * Returns the rest of str if it has prefix, otherwise return all. + */ +string stripPrefix(const string& str, const string& prefix); -} // namespace javastream_proto +} // namespace stream_proto } // namespace android diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp index 5b45c551c930..bbfcba6272b2 100644 --- a/tools/validatekeymaps/Main.cpp +++ b/tools/validatekeymaps/Main.cpp @@ -20,13 +20,15 @@ #include <utils/PropertyMap.h> #include <utils/String8.h> +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> using namespace android; -static const char* gProgName = "validatekeymaps"; +static const char* kProgName = "validatekeymaps"; +static bool gQuiet = false; enum FileType { FILETYPE_UNKNOWN, @@ -36,15 +38,32 @@ enum FileType { FILETYPE_INPUTDEVICECONFIGURATION, }; +static void log(const char* fmt, ...) { + if (gQuiet) { + return; + } + va_list args; + va_start(args, fmt); + vfprintf(stdout, fmt, args); + va_end(args); +} + +static void error(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} static void usage() { - fprintf(stderr, "Keymap Validation Tool\n\n"); - fprintf(stderr, "Usage:\n"); - fprintf(stderr, - " %s [*.kl] [*.kcm] [*.idc] [virtualkeys.*] [...]\n" + error("Keymap Validation Tool\n\n"); + error("Usage:\n"); + error( + " %s [-q] [*.kl] [*.kcm] [*.idc] [virtualkeys.*] [...]\n" " Validates the specified key layouts, key character maps, \n" - " input device configurations, or virtual key definitions.\n\n", - gProgName); + " input device configurations, or virtual key definitions.\n\n" + " -q Quiet; do not write anything to standard out.\n", + kProgName); } static FileType getFileType(const char* filename) { @@ -69,19 +88,19 @@ static FileType getFileType(const char* filename) { } static bool validateFile(const char* filename) { - fprintf(stdout, "Validating file '%s'...\n", filename); + log("Validating file '%s'...\n", filename); FileType fileType = getFileType(filename); switch (fileType) { case FILETYPE_UNKNOWN: - fprintf(stderr, "Supported file types: *.kl, *.kcm, virtualkeys.*\n\n"); + error("Supported file types: *.kl, *.kcm, virtualkeys.*\n\n"); return false; case FILETYPE_KEYLAYOUT: { sp<KeyLayoutMap> map; status_t status = KeyLayoutMap::load(String8(filename), &map); if (status) { - fprintf(stderr, "Error %d parsing key layout file.\n\n", status); + error("Error %d parsing key layout file.\n\n", status); return false; } break; @@ -92,7 +111,7 @@ static bool validateFile(const char* filename) { status_t status = KeyCharacterMap::load(String8(filename), KeyCharacterMap::FORMAT_ANY, &map); if (status) { - fprintf(stderr, "Error %d parsing key character map file.\n\n", status); + error("Error %d parsing key character map file.\n\n", status); return false; } break; @@ -102,7 +121,7 @@ static bool validateFile(const char* filename) { PropertyMap* map; status_t status = PropertyMap::load(String8(filename), &map); if (status) { - fprintf(stderr, "Error %d parsing input device configuration file.\n\n", status); + error("Error %d parsing input device configuration file.\n\n", status); return false; } delete map; @@ -113,7 +132,7 @@ static bool validateFile(const char* filename) { VirtualKeyMap* map; status_t status = VirtualKeyMap::load(String8(filename), &map); if (status) { - fprintf(stderr, "Error %d parsing virtual key definition file.\n\n", status); + error("Error %d parsing virtual key definition file.\n\n", status); return false; } delete map; @@ -121,7 +140,7 @@ static bool validateFile(const char* filename) { } } - fputs("No errors.\n\n", stdout); + log("No errors.\n\n"); return true; } @@ -133,15 +152,19 @@ int main(int argc, const char** argv) { int result = 0; for (int i = 1; i < argc; i++) { + if (i == 1 && !strcmp(argv[1], "-q")) { + gQuiet = true; + continue; + } if (!validateFile(argv[i])) { result = 1; } } if (result) { - fputs("Failed!\n", stderr); + error("Failed!\n"); } else { - fputs("Success.\n", stdout); + log("Success.\n"); } return result; } |