summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/aapt2/Android.bp3
-rw-r--r--tools/aapt2/AppInfo.h5
-rw-r--r--tools/aapt2/Flags.h71
-rw-r--r--tools/aapt2/Main.cpp180
-rw-r--r--tools/aapt2/ResourceParser.cpp62
-rw-r--r--tools/aapt2/ResourceParser.h4
-rw-r--r--tools/aapt2/ResourceParser_test.cpp54
-rw-r--r--tools/aapt2/ResourceUtils.cpp15
-rw-r--r--tools/aapt2/ResourceUtils.h6
-rw-r--r--tools/aapt2/ResourceUtils_test.cpp23
-rw-r--r--tools/aapt2/StringPool.cpp2
-rw-r--r--tools/aapt2/StringPool_test.cpp19
-rw-r--r--tools/aapt2/cmd/Command.cpp (renamed from tools/aapt2/Flags.cpp)123
-rw-r--r--tools/aapt2/cmd/Command.h90
-rw-r--r--tools/aapt2/cmd/Compile.cpp96
-rw-r--r--tools/aapt2/cmd/Compile.h58
-rw-r--r--tools/aapt2/cmd/Compile_test.cpp5
-rw-r--r--tools/aapt2/cmd/Convert.cpp50
-rw-r--r--tools/aapt2/cmd/Convert.h54
-rw-r--r--tools/aapt2/cmd/Diff.cpp18
-rw-r--r--tools/aapt2/cmd/Diff.h35
-rw-r--r--tools/aapt2/cmd/Dump.cpp38
-rw-r--r--tools/aapt2/cmd/Dump.h54
-rw-r--r--tools/aapt2/cmd/Link.cpp355
-rw-r--r--tools/aapt2/cmd/Link.h279
-rw-r--r--tools/aapt2/cmd/Optimize.cpp223
-rw-r--r--tools/aapt2/cmd/Optimize.h124
-rw-r--r--tools/aapt2/cmd/Util.cpp40
-rw-r--r--tools/aapt2/cmd/Util.h12
-rw-r--r--tools/aapt2/cmd/Util_test.cpp48
-rw-r--r--tools/aapt2/java/JavaClassGenerator.cpp22
-rw-r--r--tools/aapt2/java/JavaClassGenerator_test.cpp67
-rw-r--r--tools/aapt2/java/ProguardRules.cpp125
-rw-r--r--tools/aapt2/java/ProguardRules.h34
-rw-r--r--tools/aapt2/java/ProguardRules_test.cpp145
-rw-r--r--tools/aapt2/jni/aapt2_jni.cpp11
-rw-r--r--tools/aapt2/link/ManifestFixer.cpp11
-rw-r--r--tools/aapt2/link/ManifestFixer.h4
-rw-r--r--tools/aapt2/link/ManifestFixer_test.cpp246
-rw-r--r--tools/aapt2/link/NoDefaultResourceRemover.cpp50
-rw-r--r--tools/aapt2/link/NoDefaultResourceRemover_test.cpp60
-rw-r--r--tools/aapt2/link/TableMerger.cpp23
-rw-r--r--tools/aapt2/link/TableMerger.h2
-rw-r--r--tools/aapt2/link/TableMerger_test.cpp74
-rw-r--r--tools/aapt2/optimize/MultiApkGenerator.cpp31
-rw-r--r--tools/aapt2/optimize/ResourceFilter.cpp43
-rw-r--r--tools/aapt2/optimize/ResourceFilter.h42
-rw-r--r--tools/aapt2/optimize/ResourceFilter_test.cpp74
-rw-r--r--tools/aapt2/util/Util.cpp47
-rw-r--r--tools/aapt2/util/Util.h3
-rw-r--r--tools/aapt2/xml/XmlPullParser.cpp17
-rw-r--r--tools/aapt2/xml/XmlPullParser.h10
-rw-r--r--tools/apilint/apilint.py107
-rwxr-xr-xtools/apilint/apilint_stats.sh7
-rw-r--r--tools/incident_section_gen/main.cpp17
-rw-r--r--tools/stringslint/stringslint.py99
56 files changed, 2525 insertions, 992 deletions
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 48cfc4453d8f..2ecf25b751b6 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -15,6 +15,7 @@
//
toolSources = [
+ "cmd/Command.cpp",
"cmd/Compile.cpp",
"cmd/Convert.cpp",
"cmd/Diff.cpp",
@@ -111,6 +112,7 @@ cc_library_host_static {
"link/XmlReferenceLinker.cpp",
"optimize/MultiApkGenerator.cpp",
"optimize/ResourceDeduper.cpp",
+ "optimize/ResourceFilter.cpp",
"optimize/VersionCollapser.cpp",
"process/SymbolTable.cpp",
"split/TableSplitter.cpp",
@@ -123,7 +125,6 @@ cc_library_host_static {
"ConfigDescription.cpp",
"Debug.cpp",
"DominatorTree.cpp",
- "Flags.cpp",
"java/AnnotationProcessor.cpp",
"java/ClassDefinition.cpp",
"java/JavaClassGenerator.cpp",
diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h
index d6f599520d71..75123537116f 100644
--- a/tools/aapt2/AppInfo.h
+++ b/tools/aapt2/AppInfo.h
@@ -31,9 +31,12 @@ struct AppInfo {
// The app's minimum SDK version, if it is defined.
Maybe<int> min_sdk_version;
- // The app's version code, if it is defined.
+ // The app's version code (the lower 32 bits of the long version code), if it is defined.
Maybe<uint32_t> version_code;
+ // The app's version code major (the upper 32 bits of the long version code), if it is defined.
+ Maybe<uint32_t> version_code_major;
+
// The app's revision code, if it is defined.
Maybe<uint32_t> revision_code;
diff --git a/tools/aapt2/Flags.h b/tools/aapt2/Flags.h
deleted file mode 100644
index 3b3ae710dc7b..000000000000
--- a/tools/aapt2/Flags.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2015 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_FLAGS_H
-#define AAPT_FLAGS_H
-
-#include <functional>
-#include <ostream>
-#include <string>
-#include <unordered_set>
-#include <vector>
-
-#include "androidfw/StringPiece.h"
-
-#include "util/Maybe.h"
-
-namespace aapt {
-
-class Flags {
- public:
- Flags& RequiredFlag(const android::StringPiece& name, const android::StringPiece& description,
- std::string* value);
- Flags& RequiredFlagList(const android::StringPiece& name, const android::StringPiece& description,
- std::vector<std::string>* value);
- Flags& OptionalFlag(const android::StringPiece& name, const android::StringPiece& description,
- Maybe<std::string>* value);
- Flags& OptionalFlagList(const android::StringPiece& name, const android::StringPiece& description,
- std::vector<std::string>* value);
- Flags& OptionalFlagList(const android::StringPiece& name, const android::StringPiece& description,
- std::unordered_set<std::string>* value);
- Flags& OptionalSwitch(const android::StringPiece& name, const android::StringPiece& description,
- bool* value);
-
- void Usage(const android::StringPiece& command, std::ostream* out);
-
- bool Parse(const android::StringPiece& command, const std::vector<android::StringPiece>& args,
- std::ostream* outError);
-
- const std::vector<std::string>& GetArgs();
-
- private:
- struct Flag {
- std::string name;
- std::string description;
- std::function<bool(const android::StringPiece& value)> action;
- bool required;
- size_t num_args;
-
- bool parsed;
- };
-
- std::vector<Flag> flags_;
- std::vector<std::string> args_;
-};
-
-} // namespace aapt
-
-#endif // AAPT_FLAGS_H
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 808b29cfd844..23903c9e05f3 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -29,6 +29,13 @@
#include "androidfw/StringPiece.h"
#include "Diagnostics.h"
+#include "cmd/Command.h"
+#include "cmd/Compile.h"
+#include "cmd/Convert.h"
+#include "cmd/Diff.h"
+#include "cmd/Dump.h"
+#include "cmd/Link.h"
+#include "cmd/Optimize.h"
#include "util/Files.h"
#include "util/Util.h"
@@ -43,114 +50,121 @@ static const char* sMajorVersion = "2";
// Update minor version whenever a feature or flag is added.
static const char* sMinorVersion = "19";
-static void PrintVersion() {
- std::cerr << StringPrintf("Android Asset Packaging Tool (aapt) %s:%s", sMajorVersion,
- sMinorVersion)
- << std::endl;
-}
-
-static void PrintUsage() {
- std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|convert|version] ..." << std::endl;
-}
+/** Prints the version information of AAPT2. */
+class VersionCommand : public Command {
+ public:
+ explicit VersionCommand() : Command("version") {
+ SetDescription("Prints the version of aapt.");
+ }
-extern int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics);
-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) {
- if (command == "compile" || command == "c") {
- return Compile(args, diagnostics);
- } else if (command == "link" || command == "l") {
- return Link(args, diagnostics);
- } else if (command == "dump" || command == "d") {
- return Dump(args);
- } else if (command == "diff") {
- return Diff(args);
- } else if (command == "optimize") {
- return Optimize(args);
- } else if (command == "convert") {
- return Convert(args);
- } else if (command == "version") {
- PrintVersion();
+ int Action(const std::vector<std::string>& /* args */) override {
+ std::cerr << StringPrintf("Android Asset Packaging Tool (aapt) %s:%s", sMajorVersion,
+ sMinorVersion)
+ << std::endl;
return 0;
}
- diagnostics->Error(DiagMessage() << "unknown command '" << command << "'");
- return -1;
-}
+};
+
+/** The main entry point of AAPT. */
+class MainCommand : public Command {
+ public:
+ explicit MainCommand(IDiagnostics* diagnostics) : Command("aapt2"), diagnostics_(diagnostics) {
+ AddOptionalSubcommand(util::make_unique<CompileCommand>(diagnostics));
+ AddOptionalSubcommand(util::make_unique<LinkCommand>(diagnostics));
+ AddOptionalSubcommand(util::make_unique<DumpCommand>());
+ AddOptionalSubcommand(util::make_unique<DiffCommand>());
+ AddOptionalSubcommand(util::make_unique<OptimizeCommand>());
+ AddOptionalSubcommand(util::make_unique<ConvertCommand>());
+ AddOptionalSubcommand(util::make_unique<VersionCommand>());
+ }
-static void RunDaemon(IDiagnostics* diagnostics) {
- std::cout << "Ready" << std::endl;
-
- // Run in daemon mode. The first line of input is the command. This can be 'quit' which ends
- // the daemon mode. Each subsequent line is a single parameter to the command. The end of a
- // invocation is signaled by providing an empty line. At any point, an EOF signal or the
- // command 'quit' will end the daemon mode.
- while (true) {
- std::vector<std::string> raw_args;
- for (std::string line; std::getline(std::cin, line) && !line.empty();) {
- raw_args.push_back(line);
+ int Action(const std::vector<std::string>& args) override {
+ if (args.size() == 0) {
+ diagnostics_->Error(DiagMessage() << "no subcommand specified");
+ } else {
+ diagnostics_->Error(DiagMessage() << "unknown subcommand '" << args[0] << "'");
}
- if (!std::cin) {
- break;
- }
+ Usage(&std::cerr);
+ return -1;
+ }
- // An empty command does nothing.
- if (raw_args.empty()) {
- continue;
- }
+ private:
+ IDiagnostics* diagnostics_;
+};
- if (raw_args[0] == "quit") {
- break;
- }
+/*
+ * Run in daemon mode. The first line of input is the command. This can be 'quit' which ends
+ * the daemon mode. Each subsequent line is a single parameter to the command. The end of a
+ * invocation is signaled by providing an empty line. At any point, an EOF signal or the
+ * command 'quit' will end the daemon mode.
+ */
+class DaemonCommand : public Command {
+ public:
+ explicit DaemonCommand(IDiagnostics* diagnostics) : Command("daemon", "m"),
+ diagnostics_(diagnostics) {
+ SetDescription("Runs aapt in daemon mode. Each subsequent line is a single parameter to the\n"
+ "command. The end of an invocation is signaled by providing an empty line.");
+ }
- std::vector<StringPiece> args;
- args.insert(args.end(), ++raw_args.begin(), raw_args.end());
- int ret = ExecuteCommand(raw_args[0], args, diagnostics);
- if (ret != 0) {
- std::cerr << "Error" << std::endl;
+ int Action(const std::vector<std::string>& /* args */) override {
+ std::cout << "Ready" << std::endl;
+
+ while (true) {
+ std::vector<std::string> raw_args;
+ for (std::string line; std::getline(std::cin, line) && !line.empty();) {
+ raw_args.push_back(line);
+ }
+
+ if (!std::cin) {
+ break;
+ }
+
+ // An empty command does nothing.
+ if (raw_args.empty()) {
+ continue;
+ }
+
+ // End the dameon
+ if (raw_args[0] == "quit") {
+ break;
+ }
+
+ std::vector<StringPiece> args;
+ args.insert(args.end(), raw_args.begin(), raw_args.end());
+ if (MainCommand(diagnostics_).Execute(args, &std::cerr) != 0) {
+ std::cerr << "Error" << std::endl;
+ }
+ std::cerr << "Done" << std::endl;
}
- std::cerr << "Done" << std::endl;
+ std::cout << "Exiting daemon" << std::endl;
+
+ return 0;
}
- std::cout << "Exiting daemon" << std::endl;
-}
+
+ private:
+ IDiagnostics* diagnostics_;
+};
} // namespace aapt
int MainImpl(int argc, char** argv) {
- if (argc < 2) {
- std::cerr << "no command specified\n";
- aapt::PrintUsage();
+ if (argc < 1) {
return -1;
}
- argv += 1;
- argc -= 1;
-
- aapt::StdErrDiagnostics diagnostics;
-
// Collect the arguments starting after the program name and command name.
std::vector<StringPiece> args;
for (int i = 1; i < argc; i++) {
args.push_back(argv[i]);
}
- const StringPiece command(argv[0]);
- if (command != "daemon" && command != "m") {
- // Single execution.
- const int result = aapt::ExecuteCommand(command, args, &diagnostics);
- if (result < 0) {
- aapt::PrintUsage();
- }
- return result;
- }
+ // Add the daemon subcommand here so it cannot be called while executing the daemon
+ aapt::StdErrDiagnostics diagnostics;
+ auto main_command = new aapt::MainCommand(&diagnostics);
+ main_command->AddOptionalSubcommand(aapt::util::make_unique<aapt::DaemonCommand>(&diagnostics));
- aapt::RunDaemon(&diagnostics);
- return 0;
+ return main_command->Execute(args, &std::cerr);
}
int main(int argc, char** argv) {
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 7f48544c0ae4..8719a233d774 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -208,6 +208,15 @@ class SegmentNode : public Node {
}
};
+// A chunk of text in the XML string within a CDATA tags.
+class CdataSegmentNode : public SegmentNode {
+ public:
+
+ void Build(StringBuilder* builder) const override {
+ builder->AppendText(data, /* preserve_spaces */ true);
+ }
+};
+
// A tag that will be encoded into the final flattened string. Tags like <b> or <i>.
class SpanNode : public Node {
public:
@@ -244,6 +253,7 @@ bool ResourceParser::FlattenXmlSubtree(
std::vector<Node*> node_stack;
node_stack.push_back(&root);
+ bool cdata_block = false;
bool saw_span_node = false;
SegmentNode* first_segment = nullptr;
SegmentNode* last_segment = nullptr;
@@ -253,11 +263,15 @@ bool ResourceParser::FlattenXmlSubtree(
const xml::XmlPullParser::Event event = parser->event();
// First take care of any SegmentNodes that should be created.
- if (event == xml::XmlPullParser::Event::kStartElement ||
- event == xml::XmlPullParser::Event::kEndElement) {
+ if (event == xml::XmlPullParser::Event::kStartElement
+ || event == xml::XmlPullParser::Event::kEndElement
+ || event == xml::XmlPullParser::Event::kCdataStart
+ || event == xml::XmlPullParser::Event::kCdataEnd) {
if (!current_text.empty()) {
- std::unique_ptr<SegmentNode> segment_node = util::make_unique<SegmentNode>();
+ std::unique_ptr<SegmentNode> segment_node = (cdata_block)
+ ? util::make_unique<CdataSegmentNode>() : util::make_unique<SegmentNode>();
segment_node->data = std::move(current_text);
+
last_segment = node_stack.back()->AddChild(std::move(segment_node));
if (first_segment == nullptr) {
first_segment = last_segment;
@@ -333,6 +347,16 @@ bool ResourceParser::FlattenXmlSubtree(
}
} break;
+ case xml::XmlPullParser::Event::kCdataStart: {
+ cdata_block = true;
+ break;
+ }
+
+ case xml::XmlPullParser::Event::kCdataEnd: {
+ cdata_block = false;
+ break;
+ }
+
default:
// ignore.
break;
@@ -447,6 +471,9 @@ bool ResourceParser::ParseResources(xml::XmlPullParser* parser) {
parsed_resource.config = config_;
parsed_resource.source = source_.WithLine(parser->line_number());
parsed_resource.comment = std::move(comment);
+ if (options_.visibility) {
+ parsed_resource.visibility_level = options_.visibility.value();
+ }
// Extract the product name if it exists.
if (Maybe<StringPiece> maybe_product = xml::FindNonEmptyAttribute(parser, "product")) {
@@ -774,7 +801,8 @@ std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser,
if (allow_raw_value) {
// We can't parse this so return a RawString if we are allowed.
return util::make_unique<RawString>(
- table_->string_pool.MakeRef(raw_value, StringPool::Context(config_)));
+ table_->string_pool.MakeRef(util::TrimWhitespace(raw_value),
+ StringPool::Context(config_)));
}
return {};
}
@@ -836,6 +864,12 @@ bool ResourceParser::ParseString(xml::XmlPullParser* parser,
}
bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource) {
+ if (options_.visibility) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<public> tag not allowed with --visibility flag");
+ return false;
+ }
+
if (out_resource->config != ConfigDescription::DefaultConfig()) {
diag_->Warn(DiagMessage(out_resource->source)
<< "ignoring configuration '" << out_resource->config << "' for <public> tag");
@@ -878,6 +912,12 @@ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out
}
bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) {
+ if (options_.visibility) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<public-group> tag not allowed with --visibility flag");
+ return false;
+ }
+
if (out_resource->config != ConfigDescription::DefaultConfig()) {
diag_->Warn(DiagMessage(out_resource->source)
<< "ignoring configuration '" << out_resource->config
@@ -999,6 +1039,11 @@ bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser,
}
bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource) {
+ if (options_.visibility) {
+ diag_->Error(DiagMessage(out_resource->source)
+ << "<java-symbol> and <symbol> tags not allowed with --visibility flag");
+ return false;
+ }
if (out_resource->config != ConfigDescription::DefaultConfig()) {
diag_->Warn(DiagMessage(out_resource->source)
<< "ignoring configuration '" << out_resource->config << "' for <"
@@ -1070,6 +1115,9 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource
child_resource.name.entry = maybe_name.value().to_string();
child_resource.source = item_source;
child_resource.overlayable = true;
+ if (options_.visibility) {
+ child_resource.visibility_level = options_.visibility.value();
+ }
out_resource->child_resources.push_back(std::move(child_resource));
xml::XmlPullParser::SkipCurrentElement(parser);
@@ -1212,6 +1260,9 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser,
child_resource.name = symbol.symbol.name.value();
child_resource.source = item_source;
child_resource.value = util::make_unique<Id>();
+ if (options_.visibility) {
+ child_resource.visibility_level = options_.visibility.value();
+ }
out_resource->child_resources.push_back(std::move(child_resource));
symbol.symbol.SetComment(std::move(comment));
@@ -1589,6 +1640,9 @@ bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser,
child_resource.name = child_ref.name.value();
child_resource.source = item_source;
child_resource.comment = std::move(comment);
+ if (options_.visibility) {
+ child_resource.visibility_level = options_.visibility.value();
+ }
if (!ParseAttrImpl(parser, &child_resource, true)) {
error = true;
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index fb9dbd0cd0fd..68130c2512d3 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -45,6 +45,10 @@ struct ResourceParserOptions {
* warnings.
*/
bool error_on_positional_arguments = true;
+
+ // If visibility was forced, we need to use it when creating a new resource and also error if we
+ // try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags.
+ Maybe<Visibility::Level> visibility;
};
/*
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 41b4041efb7a..ee496d501113 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -497,6 +497,24 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) {
EXPECT_THAT(style->entries[0].key.name, Eq(make_value(test::ParseNameOrDie("android:attr/bar"))));
}
+TEST_F(ResourceParserTest, ParseStyleWithRawStringItem) {
+ std::string input = R"(
+ <style name="foo">
+ <item name="bar">
+ com.helloworld.AppClass
+ </item>
+ </style>)";
+ ASSERT_TRUE(TestParse(input));
+
+ Style* style = test::GetValue<Style>(&table_, "style/foo");
+ ASSERT_THAT(style, NotNull());
+ EXPECT_THAT(style->entries[0].value, NotNull());
+ RawString* value = ValueCast<RawString>(style->entries[0].value.get());
+ EXPECT_THAT(value, NotNull());
+ EXPECT_THAT(*value->value, StrEq(R"(com.helloworld.AppClass)"));
+}
+
+
TEST_F(ResourceParserTest, ParseStyleWithInferredParent) {
ASSERT_TRUE(TestParse(R"(<style name="foo.bar"/>)"));
@@ -971,4 +989,40 @@ TEST_F(ResourceParserTest, ParseIdItem) {
ASSERT_FALSE(TestParse(input));
}
+TEST_F(ResourceParserTest, ParseCData) {
+ std::string input = R"(
+ <string name="foo"><![CDATA[some text and ' apostrophe]]></string>)";
+
+ ASSERT_TRUE(TestParse(input));
+ String* output = test::GetValue<String>(&table_, "string/foo");
+ ASSERT_THAT(output, NotNull());
+ EXPECT_THAT(*output, StrValueEq("some text and ' apostrophe"));
+
+ // Double quotes should not change the state of whitespace processing
+ input = R"(<string name="foo2">Hello<![CDATA[ "</string>' ]]> World</string>)";
+ ASSERT_TRUE(TestParse(input));
+ output = test::GetValue<String>(&table_, "string/foo2");
+ ASSERT_THAT(output, NotNull());
+ EXPECT_THAT(*output, StrValueEq(std::string("Hello \"</string>' World").data()));
+
+ // Cdata blocks should not have their whitespace trimmed
+ input = R"(<string name="foo3"> <![CDATA[ text ]]> </string>)";
+ ASSERT_TRUE(TestParse(input));
+ output = test::GetValue<String>(&table_, "string/foo3");
+ ASSERT_THAT(output, NotNull());
+ EXPECT_THAT(*output, StrValueEq(std::string(" text ").data()));
+
+ input = R"(<string name="foo4"> <![CDATA[]]> </string>)";
+ ASSERT_TRUE(TestParse(input));
+ output = test::GetValue<String>(&table_, "string/foo4");
+ ASSERT_THAT(output, NotNull());
+ EXPECT_THAT(*output, StrValueEq(std::string("").data()));
+
+ input = R"(<string name="foo5"> <![CDATA[ ]]> </string>)";
+ ASSERT_TRUE(TestParse(input));
+ output = test::GetValue<String>(&table_, "string/foo5");
+ ASSERT_THAT(output, NotNull());
+ EXPECT_THAT(*output, StrValueEq(std::string(" ").data()));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 560077cc322c..c48765b7b947 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -797,16 +797,20 @@ StringBuilder::StringBuilder(bool preserve_spaces)
: preserve_spaces_(preserve_spaces), quote_(preserve_spaces) {
}
-StringBuilder& StringBuilder::AppendText(const std::string& text) {
+StringBuilder& StringBuilder::AppendText(const std::string& text, bool preserve_spaces) {
if (!error_.empty()) {
return *this;
}
+ // Enable preserving spaces if it is enabled for this append or the StringBuilder was constructed
+ // to preserve spaces
+ preserve_spaces = (preserve_spaces) ? preserve_spaces : preserve_spaces_;
+
const size_t previous_len = xml_string_.text.size();
Utf8Iterator iter(text);
while (iter.HasNext()) {
char32_t codepoint = iter.Next();
- if (!quote_ && iswspace(codepoint)) {
+ if (!preserve_spaces && !quote_ && iswspace(codepoint)) {
if (!last_codepoint_was_space_) {
// Emit a space if it's the first.
xml_string_.text += ' ';
@@ -827,7 +831,6 @@ StringBuilder& StringBuilder::AppendText(const std::string& text) {
case U't':
xml_string_.text += '\t';
break;
-
case U'n':
xml_string_.text += '\n';
break;
@@ -855,12 +858,12 @@ StringBuilder& StringBuilder::AppendText(const std::string& text) {
break;
}
}
- } else if (!preserve_spaces_ && codepoint == U'"') {
+ } else if (!preserve_spaces && codepoint == U'"') {
// Only toggle the quote state when we are not preserving spaces.
quote_ = !quote_;
- } else if (!quote_ && codepoint == U'\'') {
- // This should be escaped.
+ } else if (!preserve_spaces && !quote_ && codepoint == U'\'') {
+ // This should be escaped when we are not preserving spaces
error_ = StringPrintf("unescaped apostrophe in string\n\"%s\"", text.c_str());
return *this;
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 7af2fe06b908..410ef28ce78a 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -267,8 +267,10 @@ class StringBuilder {
// single quotations can be used without escaping them.
explicit StringBuilder(bool preserve_spaces = false);
- // Appends a chunk of text.
- StringBuilder& AppendText(const std::string& text);
+ // Appends a chunk of text. If preserve_spaces is true, whitespace removal is not performed, and
+ // single quotations can be used without escaping them for this append. Otherwise, the
+ // StringBuilder will behave as it was constructed.
+ StringBuilder& AppendText(const std::string& text, bool preserve_spaces = false);
// Starts a Span (tag) with the given name. The name is expected to be of the form:
// "tag_name;attr1=value;attr2=value;"
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index 11f3fa3bc6cd..5ce464074335 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -254,6 +254,29 @@ TEST(ResourceUtilsTest, StringBuilderUnicodeCodes) {
TEST(ResourceUtilsTest, StringBuilderPreserveSpaces) {
EXPECT_THAT(ResourceUtils::StringBuilder(true /*preserve_spaces*/).AppendText("\"").to_string(),
Eq("\""));
+
+ // Single quotes should be able to be used without escaping them when preserving spaces and the
+ // spaces should not be trimmed
+ EXPECT_THAT(ResourceUtils::StringBuilder()
+ .AppendText(" hey guys ")
+ .AppendText(" 'this is so cool' ", /* preserve_spaces */ true)
+ .AppendText(" wow ")
+ .to_string(),
+ Eq(" hey guys 'this is so cool' wow "));
+
+ // Reading a double quote while preserving spaces should not change the quote state
+ EXPECT_THAT(ResourceUtils::StringBuilder()
+ .AppendText(" hey guys ")
+ .AppendText(" \"this is so cool' ", /* preserve_spaces */ true)
+ .AppendText(" wow ")
+ .to_string(),
+ Eq(" hey guys \"this is so cool' wow "));
+ EXPECT_THAT(ResourceUtils::StringBuilder()
+ .AppendText(" hey guys\" ")
+ .AppendText(" \"this is so cool' ", /* preserve_spaces */ true)
+ .AppendText(" wow \" ")
+ .to_string(),
+ Eq(" hey guys \"this is so cool' wow "));
}
} // namespace aapt
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index b37e1fbd9693..8eabd3225d87 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -367,7 +367,7 @@ const std::string kStringTooLarge = "STRING_TOO_LARGE";
static bool EncodeString(const std::string& str, const bool utf8, BigBuffer* out,
IDiagnostics* diag) {
if (utf8) {
- const std::string& encoded = str;
+ const std::string& encoded = util::Utf8ToModifiedUtf8(str);
const ssize_t utf16_length = utf8_to_utf16_length(
reinterpret_cast<const uint8_t*>(encoded.data()), encoded.size());
CHECK(utf16_length >= 0);
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
index 4b3afe2bb962..0778564ee079 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -303,6 +303,25 @@ TEST(StringPoolTest, Flatten) {
}
}
+TEST(StringPoolTest, FlattenModifiedUTF8) {
+ using namespace android; // For NO_ERROR on Windows.
+ StdErrDiagnostics diag;
+ StringPool pool;
+ StringPool::Ref ref_a = pool.MakeRef("\xF0\x90\x90\x80"); // 𐐀 (U+10400)
+ StringPool::Ref ref_b = pool.MakeRef("foo \xF0\x90\x90\xB7 bar"); // 𐐷 (U+10437)
+ StringPool::Ref ref_c = pool.MakeRef("\xF0\x90\x90\x80\xF0\x90\x90\xB7");
+
+ BigBuffer buffer(1024);
+ StringPool::FlattenUtf8(&buffer, pool, &diag);
+ std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
+
+ // Check that the 4 byte utf-8 codepoint is encoded using 2 3 byte surrogate pairs
+ ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
+ EXPECT_THAT(util::GetString(test, 0), Eq("\xED\xA0\x81\xED\xB0\x80"));
+ EXPECT_THAT(util::GetString(test, 1), Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar"));
+ EXPECT_THAT(util::GetString(test, 2), Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7"));
+}
TEST(StringPoolTest, MaxEncodingLength) {
StdErrDiagnostics diag;
diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/cmd/Command.cpp
index 84977ab424cc..09411b95bcfe 100644
--- a/tools/aapt2/Flags.cpp
+++ b/tools/aapt2/cmd/Command.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "Flags.h"
+#include "Command.h"
#include <iomanip>
#include <iostream>
@@ -29,87 +29,113 @@ using android::StringPiece;
namespace aapt {
-Flags& Flags::RequiredFlag(const StringPiece& name,
- const StringPiece& description, std::string* value) {
+void Command::AddRequiredFlag(const StringPiece& name,
+ const StringPiece& description, std::string* value) {
auto func = [value](const StringPiece& arg) -> bool {
*value = arg.to_string();
return true;
};
flags_.push_back(Flag{name.to_string(), description.to_string(), func, true, 1, false});
- return *this;
}
-Flags& Flags::RequiredFlagList(const StringPiece& name,
- const StringPiece& description,
- std::vector<std::string>* value) {
+void Command::AddRequiredFlagList(const StringPiece& name,
+ const StringPiece& description,
+ std::vector<std::string>* value) {
auto func = [value](const StringPiece& arg) -> bool {
value->push_back(arg.to_string());
return true;
};
flags_.push_back(Flag{name.to_string(), description.to_string(), func, true, 1, false});
- return *this;
}
-Flags& Flags::OptionalFlag(const StringPiece& name,
- const StringPiece& description,
- Maybe<std::string>* value) {
+void Command::AddOptionalFlag(const StringPiece& name,
+ const StringPiece& description,
+ Maybe<std::string>* value) {
auto func = [value](const StringPiece& arg) -> bool {
*value = arg.to_string();
return true;
};
flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 1, false});
- return *this;
}
-Flags& Flags::OptionalFlagList(const StringPiece& name,
- const StringPiece& description,
- std::vector<std::string>* value) {
+void Command::AddOptionalFlagList(const StringPiece& name,
+ const StringPiece& description,
+ std::vector<std::string>* value) {
auto func = [value](const StringPiece& arg) -> bool {
value->push_back(arg.to_string());
return true;
};
flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 1, false});
- return *this;
}
-Flags& Flags::OptionalFlagList(const StringPiece& name,
- const StringPiece& description,
- std::unordered_set<std::string>* value) {
+void Command::AddOptionalFlagList(const StringPiece& name,
+ const StringPiece& description,
+ std::unordered_set<std::string>* value) {
auto func = [value](const StringPiece& arg) -> bool {
value->insert(arg.to_string());
return true;
};
flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 1, false});
- return *this;
}
-Flags& Flags::OptionalSwitch(const StringPiece& name,
- const StringPiece& description, bool* value) {
+void Command::AddOptionalSwitch(const StringPiece& name,
+ const StringPiece& description, bool* value) {
auto func = [value](const StringPiece& arg) -> bool {
*value = true;
return true;
};
flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 0, false});
- return *this;
}
-void Flags::Usage(const StringPiece& command, std::ostream* out) {
+void Command::AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand) {
+ subcommand->fullname_ = name_ + " " + subcommand->name_;
+ subcommands_.push_back(std::move(subcommand));
+}
+
+void Command::SetDescription(const android::StringPiece& description) {
+ description_ = description.to_string();
+}
+
+void Command::Usage(std::ostream* out) {
constexpr size_t kWidth = 50;
- *out << command << " [options]";
+ *out << fullname_;
+
+ if (!subcommands_.empty()) {
+ *out << " [subcommand]";
+ }
+
+ *out << " [options]";
for (const Flag& flag : flags_) {
if (flag.required) {
*out << " " << flag.name << " arg";
}
}
- *out << " files...\n\nOptions:\n";
+ *out << " files...\n";
+
+ if (!subcommands_.empty()) {
+ *out << "\nSubcommands:\n";
+ for (auto& subcommand : subcommands_) {
+ std::string argline = subcommand->name_;
+
+ // Split the description by newlines and write out the argument (which is
+ // empty after the first line) followed by the description line. This will make sure
+ // that multiline descriptions are still right justified and aligned.
+ for (StringPiece line : util::Tokenize(subcommand->description_, '\n')) {
+ *out << " " << std::setw(kWidth) << std::left << argline << line << "\n";
+ argline = " ";
+ }
+ }
+ }
+
+ *out << "\nOptions:\n";
for (const Flag& flag : flags_) {
std::string argline = flag.name;
@@ -118,10 +144,8 @@ void Flags::Usage(const StringPiece& command, std::ostream* out) {
}
// Split the description by newlines and write out the argument (which is
- // empty after
- // the first line) followed by the description line. This will make sure
- // that multiline
- // descriptions are still right justified and aligned.
+ // empty after the first line) followed by the description line. This will make sure
+ // that multiline descriptions are still right justified and aligned.
for (StringPiece line : util::Tokenize(flag.description, '\n')) {
*out << " " << std::setw(kWidth) << std::left << argline << line << "\n";
argline = " ";
@@ -132,19 +156,29 @@ void Flags::Usage(const StringPiece& command, std::ostream* out) {
out->flush();
}
-bool Flags::Parse(const StringPiece& command,
- const std::vector<StringPiece>& args,
- std::ostream* out_error) {
+int Command::Execute(const std::vector<android::StringPiece>& args, std::ostream* out_error) {
+ std::vector<std::string> file_args;
+
for (size_t i = 0; i < args.size(); i++) {
StringPiece arg = args[i];
if (*(arg.data()) != '-') {
- args_.push_back(arg.to_string());
+ // Continue parsing as the sub command if the first argument matches one of the subcommands
+ if (i == 0) {
+ for (auto& subcommand : subcommands_) {
+ if (arg == subcommand->name_ || arg==subcommand->short_name_) {
+ return subcommand->Execute(
+ std::vector<android::StringPiece>(args.begin() + 1, args.end()), out_error);
+ }
+ }
+ }
+
+ file_args.push_back(arg.to_string());
continue;
}
if (arg == "-h" || arg == "--help") {
- Usage(command, out_error);
- return false;
+ Usage(out_error);
+ return 1;
}
bool match = false;
@@ -154,7 +188,7 @@ bool Flags::Parse(const StringPiece& command,
i++;
if (i >= args.size()) {
*out_error << flag.name << " missing argument.\n\n";
- Usage(command, out_error);
+ Usage(out_error);
return false;
}
flag.action(args[i]);
@@ -169,21 +203,20 @@ bool Flags::Parse(const StringPiece& command,
if (!match) {
*out_error << "unknown option '" << arg << "'.\n\n";
- Usage(command, out_error);
- return false;
+ Usage(out_error);
+ return 1;
}
}
for (const Flag& flag : flags_) {
if (flag.required && !flag.parsed) {
*out_error << "missing required flag " << flag.name << "\n\n";
- Usage(command, out_error);
- return false;
+ Usage(out_error);
+ return 1;
}
}
- return true;
-}
-const std::vector<std::string>& Flags::GetArgs() { return args_; }
+ return Action(file_args);
+}
} // namespace aapt
diff --git a/tools/aapt2/cmd/Command.h b/tools/aapt2/cmd/Command.h
new file mode 100644
index 000000000000..71dc6fe48da1
--- /dev/null
+++ b/tools/aapt2/cmd/Command.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_COMMAND_H
+#define AAPT_COMMAND_H
+
+#include <functional>
+#include <ostream>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "androidfw/StringPiece.h"
+
+#include "util/Maybe.h"
+
+namespace aapt {
+
+class Command {
+ public:
+ explicit Command(const android::StringPiece& name) : name_(name.to_string()),
+ fullname_(name.to_string()) { }
+ explicit Command(const android::StringPiece& name, const android::StringPiece& short_name)
+ : name_(name.to_string()), short_name_(short_name.to_string()), fullname_(name.to_string()) {}
+ virtual ~Command() = default;
+
+ void AddRequiredFlag(const android::StringPiece& name, const android::StringPiece& description,
+ std::string* value);
+ void AddRequiredFlagList(const android::StringPiece& name, const android::StringPiece&
+ description, std::vector<std::string>* value);
+ void AddOptionalFlag(const android::StringPiece& name, const android::StringPiece& description,
+ Maybe<std::string>* value);
+ void AddOptionalFlagList(const android::StringPiece& name,
+ const android::StringPiece& description, std::vector<std::string>* value);
+ void AddOptionalFlagList(const android::StringPiece& name,
+ const android::StringPiece& description, std::unordered_set<std::string>* value);
+ void AddOptionalSwitch(const android::StringPiece& name, const android::StringPiece& description,
+ bool* value);
+ void AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand);
+
+ void SetDescription(const android::StringPiece& name);
+
+ /** Prints the help menu of the command. */
+ void Usage(std::ostream* out);
+
+ /**
+ * Parses the command line arguments, sets the flag variable values, and runs the action of
+ * the command. If the arguments fail to parse to the command and its subcommands, then the action
+ * will not be run and the usage will be printed instead.
+ **/
+ int Execute(const std::vector<android::StringPiece>& args, std::ostream* outError);
+
+ /** The action to preform when the command is executed. */
+ virtual int Action(const std::vector<std::string>& args) = 0;
+
+ private:
+ struct Flag {
+ std::string name;
+ std::string description;
+ std::function<bool(const android::StringPiece& value)> action;
+ bool required;
+ size_t num_args;
+
+ bool parsed;
+ };
+
+ std::string description_;
+ std::string name_;
+ std::string short_name_;
+ std::string fullname_;
+ std::vector<Flag> flags_;
+ std::vector<std::unique_ptr<Command>> subcommands_;
+};
+
+} // namespace aapt
+
+#endif // AAPT_COMMAND_H
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index ab8a4b77a89d..36b5578d1deb 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-#include <dirent.h>
+#include "Compile.h"
+#include <dirent.h>
#include <string>
#include "android-base/errors.h"
@@ -27,9 +28,9 @@
#include "ConfigDescription.h"
#include "Diagnostics.h"
-#include "Flags.h"
#include "ResourceParser.h"
#include "ResourceTable.h"
+#include "cmd/Util.h"
#include "compile/IdAssigner.h"
#include "compile/InlineXmlFormatParser.h"
#include "compile/Png.h"
@@ -120,16 +121,6 @@ static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path,
extension.to_string(), config_str.to_string(), config};
}
-struct CompileOptions {
- std::string output_path;
- Maybe<std::string> res_dir;
- Maybe<std::string> generate_text_symbols_path;
- bool pseudolocalize = false;
- bool no_png_crunch = false;
- bool legacy_mode = false;
- bool verbose = false;
-};
-
static std::string BuildIntermediateContainerFilename(const ResourcePathData& data) {
std::stringstream name;
name << data.resource_dir;
@@ -226,6 +217,10 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options,
// If the filename includes donottranslate, then the default translatable is false.
parser_options.translatable = path_data.name.find("donottranslate") == std::string::npos;
+ // If visibility was forced, we need to use it when creating a new resource and also error if
+ // we try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags.
+ parser_options.visibility = options.visibility;
+
ResourceParser res_parser(context->GetDiagnostics(), &table, path_data.source, path_data.config,
parser_options);
if (!res_parser.Parse(&xml_parser)) {
@@ -319,8 +314,16 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options,
if (!entry->values.empty()) {
auto styleable = static_cast<const Styleable*>(entry->values.front()->value.get());
for (const auto& attr : styleable->entries) {
+ // The visibility of the children under the styleable does not matter as they are
+ // nested under their parent and use its visibility.
r_txt_printer.Print("default int styleable ");
r_txt_printer.Print(entry->name);
+ // If the package name is present, also include it in the mangled name (e.g.
+ // "android")
+ if (!attr.name.value().package.empty()) {
+ r_txt_printer.Print("_");
+ r_txt_printer.Print(MakePackageSafeName(attr.name.value().package));
+ }
r_txt_printer.Print("_");
r_txt_printer.Println(attr.name.value().entry);
}
@@ -694,56 +697,47 @@ class CompileContext : public IAaptContext {
bool verbose_ = false;
};
-// 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;
-
- bool verbose = false;
- Flags flags =
- Flags()
- .RequiredFlag("-o", "Output path", &options.output_path)
- .OptionalFlag("--dir", "Directory to scan for resources", &options.res_dir)
- .OptionalFlag("--output-text-symbols",
- "Generates a text file containing the resource symbols in the\n"
- "specified file",
- &options.generate_text_symbols_path)
- .OptionalSwitch("--pseudo-localize",
- "Generate resources for pseudo-locales "
- "(en-XA and ar-XB)",
- &options.pseudolocalize)
- .OptionalSwitch("--no-crunch", "Disables PNG processing", &options.no_png_crunch)
- .OptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
- &options.legacy_mode)
- .OptionalSwitch("-v", "Enables verbose logging", &verbose);
- if (!flags.Parse("aapt2 compile", args, &std::cerr)) {
- return 1;
+int CompileCommand::Action(const std::vector<std::string>& args) {
+ CompileContext context(diagnostic_);
+ context.SetVerbose(options_.verbose);
+
+ if (visibility_) {
+ if (visibility_.value() == "public") {
+ options_.visibility = Visibility::Level::kPublic;
+ } else if (visibility_.value() == "private") {
+ options_.visibility = Visibility::Level::kPrivate;
+ } else if (visibility_.value() == "default") {
+ options_.visibility = Visibility::Level::kUndefined;
+ } else {
+ context.GetDiagnostics()->Error(
+ DiagMessage() << "Unrecognized visibility level passes to --visibility: '"
+ << visibility_.value() << "'. Accepted levels: public, private, default");
+ return 1;
+ }
}
- context.SetVerbose(verbose);
-
std::unique_ptr<IArchiveWriter> archive_writer;
std::vector<ResourcePathData> input_data;
- if (options.res_dir) {
- if (!flags.GetArgs().empty()) {
+ if (options_.res_dir) {
+ if (!args.empty()) {
// Can't have both files and a resource directory.
context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified");
- flags.Usage("aapt2 compile", &std::cerr);
+ Usage(&std::cerr);
return 1;
}
- if (!LoadInputFilesFromDir(&context, options, &input_data)) {
+ if (!LoadInputFilesFromDir(&context, options_, &input_data)) {
return 1;
}
- archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options.output_path);
+ archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path);
} else {
- input_data.reserve(flags.GetArgs().size());
+ input_data.reserve(args.size());
// Collect data from the path for each input file.
- for (const std::string& arg : flags.GetArgs()) {
+ for (const std::string& arg : args) {
std::string error_str;
if (Maybe<ResourcePathData> path_data = ExtractResourcePathData(arg, &error_str)) {
input_data.push_back(std::move(path_data.value()));
@@ -753,7 +747,7 @@ int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
}
}
- archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options.output_path);
+ archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options_.output_path);
}
if (!archive_writer) {
@@ -762,7 +756,7 @@ int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
bool error = false;
for (ResourcePathData& path_data : input_data) {
- if (options.verbose) {
+ if (options_.verbose) {
context.GetDiagnostics()->Note(DiagMessage(path_data.source) << "processing");
}
@@ -783,21 +777,21 @@ int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
if (*type != ResourceType::kRaw) {
if (path_data.extension == "xml") {
compile_func = &CompileXml;
- } else if ((!options.no_png_crunch && path_data.extension == "png")
+ } 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 << "'");
+ << "invalid file path '" << path_data.source << "'");
error = true;
continue;
}
// Treat periods as a reserved character that should not be present in a file name
// Legacy support for AAPT which did not reserve periods
- if (compile_func != &CompileFile && !options.legacy_mode
+ if (compile_func != &CompileFile && !options_.legacy_mode
&& std::count(path_data.name.begin(), path_data.name.end(), '.') != 0) {
error = true;
context.GetDiagnostics()->Error(DiagMessage() << "resource file '" << path_data.source.path
@@ -808,7 +802,7 @@ int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
// Compile the file.
const std::string out_path = BuildIntermediateContainerFilename(path_data);
- error |= !compile_func(&context, options, path_data, archive_writer.get(), out_path);
+ error |= !compile_func(&context, options_, path_data, archive_writer.get(), out_path);
}
return error ? 1 : 0;
}
diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h
index d95cf1c22732..41519520fda1 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -1,13 +1,69 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
#ifndef AAPT2_COMPILE_H
#define AAPT2_COMPILE_H
#include "androidfw/StringPiece.h"
+#include "Command.h"
#include "Diagnostics.h"
+#include "ResourceTable.h"
namespace aapt {
- int Compile(const std::vector<android::StringPiece>& args, IDiagnostics* diagnostics);
+struct CompileOptions {
+ std::string output_path;
+ Maybe<std::string> res_dir;
+ Maybe<std::string> generate_text_symbols_path;
+ Maybe<Visibility::Level> visibility;
+ bool pseudolocalize = false;
+ bool no_png_crunch = false;
+ bool legacy_mode = false;
+ bool verbose = false;
+};
+
+class CompileCommand : public Command {
+ public:
+ explicit CompileCommand(IDiagnostics* diagnostic) : Command("compile", "c"),
+ diagnostic_(diagnostic) {
+ SetDescription("Compiles resources to be linked into an apk.");
+ AddRequiredFlag("-o", "Output path", &options_.output_path);
+ AddOptionalFlag("--dir", "Directory to scan for resources", &options_.res_dir);
+ AddOptionalFlag("--output-text-symbols",
+ "Generates a text file containing the resource symbols in the\n"
+ "specified file", &options_.generate_text_symbols_path);
+ AddOptionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales "
+ "(en-XA and ar-XB)", &options_.pseudolocalize);
+ AddOptionalSwitch("--no-crunch", "Disables PNG processing", &options_.no_png_crunch);
+ AddOptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
+ &options_.legacy_mode);
+ AddOptionalSwitch("-v", "Enables verbose logging", &options_.verbose);
+ AddOptionalFlag("--visibility",
+ "Sets the visibility of the compiled resources to the specified\n"
+ "level. Accepted levels: public, private, default", &visibility_);
+ }
+
+ int Action(const std::vector<std::string>& args) override;
+
+ private:
+ IDiagnostics* diagnostic_;
+ CompileOptions options_;
+ Maybe<std::string> visibility_;
+};
}// namespace aapt
diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp
index 212f2cf26e0d..d21addf4a081 100644
--- a/tools/aapt2/cmd/Compile_test.cpp
+++ b/tools/aapt2/cmd/Compile_test.cpp
@@ -23,7 +23,8 @@
namespace aapt {
-int TestCompile(std::string path, std::string outDir, bool legacy, StdErrDiagnostics& diag) {
+int TestCompile(const std::string& path, const std::string& outDir, bool legacy,
+ StdErrDiagnostics& diag) {
std::vector<android::StringPiece> args;
args.push_back(path);
args.push_back("-o");
@@ -32,7 +33,7 @@ int TestCompile(std::string path, std::string outDir, bool legacy, StdErrDiagnos
if (legacy) {
args.push_back("--legacy");
}
- return aapt::Compile(args, &diag);
+ return CompileCommand(&diag).Execute(args, &std::cerr);
}
TEST(CompilerTest, MultiplePeriods) {
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index eb307fb1ddce..d57eaa1ba145 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -14,12 +14,13 @@
* limitations under the License.
*/
+#include "Convert.h"
+
#include <vector>
#include "android-base/macros.h"
#include "androidfw/StringPiece.h"
-#include "Flags.h"
#include "LoadedApk.h"
#include "ValueVisitor.h"
#include "cmd/Util.h"
@@ -321,37 +322,18 @@ class Context : public IAaptContext {
StdErrDiagnostics diag_;
};
-int Convert(const vector<StringPiece>& args) {
-
- static const char* kOutputFormatProto = "proto";
- static const char* kOutputFormatBinary = "binary";
+const char* ConvertCommand::kOutputFormatProto = "proto";
+const char* ConvertCommand::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) {
+int ConvertCommand::Action(const std::vector<std::string>& args) {
+ if (args.size() != 1) {
std::cerr << "must supply a single proto APK\n";
- flags.Usage("aapt2 convert", &std::cerr);
+ Usage(&std::cerr);
return 1;
}
- const StringPiece& path = flags.GetArgs()[0];
+ Context context;
+ const StringPiece& path = args[0];
unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
if (apk == nullptr) {
context.GetDiagnostics()->Error(DiagMessage(path) << "failed to load APK");
@@ -367,24 +349,24 @@ int Convert(const vector<StringPiece>& args) {
context.package_ = app_info.value().package;
unique_ptr<IArchiveWriter> writer =
- CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path);
+ 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) {
+ if (!output_format_ || output_format_.value() == ConvertCommand::kOutputFormatBinary) {
+
+ serializer.reset(new BinaryApkSerializer(&context, apk->GetSource(), options_));
+ } else if (output_format_.value() == ConvertCommand::kOutputFormatProto) {
serializer.reset(new ProtoApkSerializer(&context, apk->GetSource()));
} else {
context.GetDiagnostics()->Error(DiagMessage(path)
- << "Invalid value for flag --output-format: "
- << output_format.value());
+ << "Invalid value for flag --output-format: "
+ << output_format_.value());
return 1;
}
-
return ConvertApk(&context, std::move(apk), serializer.get(), writer.get()) ? 0 : 1;
}
diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h
new file mode 100644
index 000000000000..fcec23d16f78
--- /dev/null
+++ b/tools/aapt2/cmd/Convert.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT2_CONVERT_H
+#define AAPT2_CONVERT_H
+
+#include "Command.h"
+#include "format/binary/TableFlattener.h"
+
+namespace aapt {
+
+class ConvertCommand : public Command {
+ public:
+ explicit ConvertCommand() : Command("convert") {
+ SetDescription("Converts an apk between binary and proto formats.");
+ AddRequiredFlag("-o", "Output path", &output_path_);
+ AddOptionalFlag("--output-format", android::base::StringPrintf("Format of the output. "
+ "Accepted values are '%s' and '%s'. When not set, defaults to '%s'.",
+ kOutputFormatProto, kOutputFormatBinary, kOutputFormatBinary), &output_format_);
+ AddOptionalSwitch("--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);
+ AddOptionalSwitch("-v", "Enables verbose logging", &verbose_);
+ }
+
+ int Action(const std::vector<std::string>& args) override;
+
+ private:
+ const static char* kOutputFormatProto;
+ const static char* kOutputFormatBinary;
+
+ TableFlattenerOptions options_;
+ std::string output_path_;
+ Maybe<std::string> output_format_;
+ bool verbose_ = false;
+};
+
+}// namespace aapt
+
+#endif //AAPT2_CONVERT_H
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index 12113ed8a48a..262f4fc4e394 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -14,9 +14,10 @@
* limitations under the License.
*/
+#include "Diff.h"
+
#include "android-base/macros.h"
-#include "Flags.h"
#include "LoadedApk.h"
#include "ValueVisitor.h"
#include "process/IResourceTableConsumer.h"
@@ -344,23 +345,18 @@ static void ZeroOutAppReferences(ResourceTable* table) {
VisitAllValuesInTable(table, &visitor);
}
-int Diff(const std::vector<StringPiece>& args) {
+int DiffCommand::Action(const std::vector<std::string>& args) {
DiffContext context;
- Flags flags;
- if (!flags.Parse("aapt2 diff", args, &std::cerr)) {
- return 1;
- }
-
- if (flags.GetArgs().size() != 2u) {
+ if (args.size() != 2u) {
std::cerr << "must have two apks as arguments.\n\n";
- flags.Usage("aapt2 diff", &std::cerr);
+ Usage(&std::cerr);
return 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);
+ std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(args[0], diag);
+ std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(args[1], diag);
if (!apk_a || !apk_b) {
return 1;
}
diff --git a/tools/aapt2/cmd/Diff.h b/tools/aapt2/cmd/Diff.h
new file mode 100644
index 000000000000..c38888863be9
--- /dev/null
+++ b/tools/aapt2/cmd/Diff.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT2_DIFF_H
+#define AAPT2_DIFF_H
+
+#include "Command.h"
+
+namespace aapt {
+
+class DiffCommand : public Command {
+ public:
+ explicit DiffCommand() : Command("diff") {
+ SetDescription("Prints the differences in resources of two apks.");
+ }
+
+ int Action(const std::vector<std::string>& args) override;
+};
+
+}// namespace aapt
+
+#endif //AAPT2_DIFF_H
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 8e7e5e59bc31..8b1f67204c22 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include "Dump.h"
+
#include <cinttypes>
#include <vector>
@@ -22,7 +24,6 @@
#include "Debug.h"
#include "Diagnostics.h"
-#include "Flags.h"
#include "format/Container.h"
#include "format/binary/BinaryResourceParser.h"
#include "format/proto/ProtoDeserialize.h"
@@ -38,13 +39,6 @@ using ::android::base::StringPrintf;
namespace aapt {
-struct DumpOptions {
- DebugPrintTableOptions print_options;
-
- // The path to a file within an APK to dump.
- Maybe<std::string> file_to_dump_path;
-};
-
static const char* ResourceFileTypeToString(const ResourceFile::Type& type) {
switch (type) {
case ResourceFile::Type::kPng:
@@ -305,29 +299,13 @@ class DumpContext : public IAaptContext {
} // namespace
-// Entry point for dump command.
-int Dump(const std::vector<StringPiece>& args) {
- bool verbose = false;
- bool no_values = false;
- DumpOptions options;
- Flags flags = Flags()
- .OptionalSwitch("--no-values",
- "Suppresses output of values when displaying resource tables.",
- &no_values)
- .OptionalFlag("--file", "Dumps the specified file from the APK passed as arg.",
- &options.file_to_dump_path)
- .OptionalSwitch("-v", "increase verbosity of output", &verbose);
- if (!flags.Parse("aapt2 dump", args, &std::cerr)) {
- return 1;
- }
-
+int DumpCommand::Action(const std::vector<std::string>& args) {
DumpContext context;
- context.SetVerbose(verbose);
-
- options.print_options.show_sources = true;
- options.print_options.show_values = !no_values;
- for (const std::string& arg : flags.GetArgs()) {
- if (!TryDumpFile(&context, arg, options)) {
+ context.SetVerbose(verbose_);
+ options_.print_options.show_sources = true;
+ options_.print_options.show_values = !no_values_;
+ for (const std::string& arg : args) {
+ if (!TryDumpFile(&context, arg, options_)) {
return 1;
}
}
diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h
new file mode 100644
index 000000000000..4893c8b76041
--- /dev/null
+++ b/tools/aapt2/cmd/Dump.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT2_DUMP_H
+#define AAPT2_DUMP_H
+
+#include "Command.h"
+#include "Debug.h"
+
+namespace aapt {
+
+struct DumpOptions {
+ DebugPrintTableOptions print_options;
+
+ // The path to a file within an APK to dump.
+ Maybe<std::string> file_to_dump_path;
+};
+
+class DumpCommand : public Command {
+ public:
+ DumpCommand() : Command("dump", "d") {
+ SetDescription("Prints resource and manifest information.");
+ AddOptionalSwitch("--no-values", "Suppresses output of values when displaying resource tables.",
+ &no_values_);
+ AddOptionalFlag("--file", "Dumps the specified file from the APK passed as arg.",
+ &options_.file_to_dump_path);
+ AddOptionalSwitch("-v", "increase verbosity of output", &verbose_);
+ }
+
+ int Action(const std::vector<std::string>& args) override;
+
+ private:
+ DumpOptions options_;
+
+ bool verbose_ = false;
+ bool no_values_ = false;
+};
+
+}// namespace aapt
+
+#endif //AAPT2_DUMP_H
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index db42e7cb3e02..c94b8473ea87 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -14,9 +14,12 @@
* limitations under the License.
*/
+#include "Link.h"
+
#include <sys/stat.h>
#include <cinttypes>
+#include <algorithm>
#include <queue>
#include <unordered_map>
#include <vector>
@@ -28,7 +31,6 @@
#include "AppInfo.h"
#include "Debug.h"
-#include "Flags.h"
#include "LoadedApk.h"
#include "Locale.h"
#include "NameMangler.h"
@@ -73,70 +75,6 @@ using ::android::base::StringPrintf;
namespace aapt {
-enum class OutputFormat {
- kApk,
- kProto,
-};
-
-struct LinkOptions {
- std::string output_path;
- std::string manifest_path;
- std::vector<std::string> include_paths;
- std::vector<std::string> overlay_files;
- 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;
- Maybe<std::string> custom_java_package;
- std::set<std::string> extra_java_packages;
- 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;
-
- // Optimizations/features.
- bool no_auto_version = false;
- bool no_version_vectors = false;
- bool no_version_transitions = false;
- bool no_resource_deduping = false;
- bool no_xml_namespaces = false;
- bool do_not_compress_anything = false;
- std::unordered_set<std::string> extensions_to_not_compress;
-
- // Static lib options.
- bool no_static_lib_packages = false;
-
- // AndroidManifest.xml massaging options.
- ManifestFixerOptions manifest_fixer_options;
-
- // Products to use/filter on.
- std::unordered_set<std::string> products;
-
- // Flattening options.
- TableFlattenerOptions table_flattener_options;
-
- // Split APK options.
- TableSplitterOptions table_splitter_options;
- std::vector<SplitConstraints> split_constraints;
- std::vector<std::string> split_paths;
-
- // Stable ID options.
- std::unordered_map<ResourceName, ResourceId> stable_id_map;
- Maybe<std::string> resource_id_map_path;
-
- // When 'true', allow reserved package IDs to be used for applications. Pre-O, the platform
- // treats negative resource IDs [those with a package ID of 0x80 or higher] as invalid.
- // In order to work around this limitation, we allow the use of traditionally reserved
- // resource IDs [those between 0x02 and 0x7E].
- bool allow_reserved_package_id = false;
-};
-
class LinkContext : public IAaptContext {
public:
LinkContext(IDiagnostics* diagnostics)
@@ -486,7 +424,7 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer
return {};
}
- if (options_.update_proguard_spec && !proguard::CollectProguardRules(doc, keep_set_)) {
+ if (options_.update_proguard_spec && !proguard::CollectProguardRules(context_, doc, keep_set_)) {
return {};
}
@@ -771,9 +709,9 @@ static int32_t FindFrameworkAssetManagerCookie(const android::AssetManager& asse
return table.getTableCookie(idx);
}
-class LinkCommand {
+class Linker {
public:
- LinkCommand(LinkContext* context, const LinkOptions& options)
+ Linker(LinkContext* context, const LinkOptions& options)
: options_(options),
context_(context),
final_table_(),
@@ -961,6 +899,18 @@ class LinkCommand {
app_info.version_code = maybe_code.value();
}
+ if (xml::Attribute* version_code_major_attr =
+ manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor")) {
+ Maybe<uint32_t> maybe_code = ResourceUtils::ParseInt(version_code_major_attr->value);
+ if (!maybe_code) {
+ diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+ << "invalid android:versionCodeMajor '"
+ << version_code_major_attr->value << "'");
+ return {};
+ }
+ app_info.version_code_major = maybe_code.value();
+ }
+
if (xml::Attribute* revision_code_attr =
manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) {
Maybe<uint32_t> maybe_code = ResourceUtils::ParseInt(revision_code_attr->value);
@@ -1702,6 +1652,7 @@ class LinkCommand {
TableMergerOptions table_merger_options;
table_merger_options.auto_add_overlay = options_.auto_add_overlay;
+ table_merger_options.strict_visibility = options_.strict_visibility;
table_merger_ = util::make_unique<TableMerger>(context_, &final_table_, table_merger_options);
if (context_->IsVerbose()) {
@@ -2025,188 +1976,12 @@ class LinkCommand {
Maybe<std::string> included_feature_base_;
};
-int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
- LinkContext context(diagnostics);
- LinkOptions options;
- std::vector<std::string> overlay_arg_list;
- std::vector<std::string> extra_java_packages;
- Maybe<std::string> package_id;
- std::vector<std::string> configs;
- Maybe<std::string> preferred_density;
- Maybe<std::string> product_list;
- bool legacy_x_flag = false;
- bool require_localization = false;
- 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 =
- Flags()
- .RequiredFlag("-o", "Output path.", &options.output_path)
- .RequiredFlag("--manifest", "Path to the Android manifest to build.",
- &options.manifest_path)
- .OptionalFlagList("-I", "Adds an Android APK to link against.", &options.include_paths)
- .OptionalFlagList("-A",
- "An assets directory to include in the APK. These are unprocessed.",
- &options.assets_dirs)
- .OptionalFlagList("-R",
- "Compilation unit to link, using `overlay` semantics.\n"
- "The last conflicting resource given takes precedence.",
- &overlay_arg_list)
- .OptionalFlag("--package-id",
- "Specify the package ID to use for this app. Must be greater or equal to\n"
- "0x7f and can't be used with --static-lib or --shared-lib.",
- &package_id)
- .OptionalFlag("--java", "Directory in which to generate R.java.",
- &options.generate_java_class_path)
- .OptionalFlag("--proguard", "Output file for generated Proguard rules.",
- &options.generate_proguard_rules_path)
- .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)
- .OptionalSwitch("--no-version-vectors",
- "Disables automatic versioning of vector drawables. Use this only\n"
- "when building with vector drawable support library.",
- &options.no_version_vectors)
- .OptionalSwitch("--no-version-transitions",
- "Disables automatic versioning of transition resources. Use this only\n"
- "when building with transition support library.",
- &options.no_version_transitions)
- .OptionalSwitch("--no-resource-deduping",
- "Disables automatic deduping of resources with\n"
- "identical values across compatible configurations.",
- &options.no_resource_deduping)
- .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("-x", "Legacy flag that specifies to use the package identifier 0x01.",
- &legacy_x_flag)
- .OptionalSwitch("-z", "Require localization of strings marked 'suggested'.",
- &require_localization)
- .OptionalFlagList("-c",
- "Comma separated list of configurations to include. The default\n"
- "is all configurations.",
- &configs)
- .OptionalFlag("--preferred-density",
- "Selects the closest matching density and strips out all others.",
- &preferred_density)
- .OptionalFlag("--product", "Comma separated list of product names to keep", &product_list)
- .OptionalSwitch("--output-to-dir",
- "Outputs the APK contents to a directory specified by -o.",
- &options.output_to_directory)
- .OptionalSwitch("--no-xml-namespaces",
- "Removes XML namespace prefix and URI information from\n"
- "AndroidManifest.xml and XML binaries in res/*.",
- &options.no_xml_namespaces)
- .OptionalFlag("--min-sdk-version",
- "Default minimum SDK version to use for AndroidManifest.xml.",
- &options.manifest_fixer_options.min_sdk_version_default)
- .OptionalFlag("--target-sdk-version",
- "Default target SDK version to use for AndroidManifest.xml.",
- &options.manifest_fixer_options.target_sdk_version_default)
- .OptionalFlag("--version-code",
- "Version code (integer) to inject into the AndroidManifest.xml if none is\n"
- "present.",
- &options.manifest_fixer_options.version_code_default)
- .OptionalFlag("--version-name",
- "Version name to inject into the AndroidManifest.xml if none is present.",
- &options.manifest_fixer_options.version_name_default)
- .OptionalSwitch("--replace-version",
- "If --version-code and/or --version-name are specified, these\n"
- "values will replace any value already in the manifest. By\n"
- "default, nothing is changed if the manifest already defines\n"
- "these attributes.",
- &options.manifest_fixer_options.replace_version)
- .OptionalFlag("--compile-sdk-version-code",
- "Version code (integer) to inject into the AndroidManifest.xml if none is\n"
- "present.",
- &options.manifest_fixer_options.compile_sdk_version)
- .OptionalFlag("--compile-sdk-version-name",
- "Version name to inject into the AndroidManifest.xml if none is present.",
- &options.manifest_fixer_options.compile_sdk_version_codename)
- .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)
- .OptionalSwitch("--non-final-ids",
- "Generates R.java without the final modifier. This is implied when\n"
- "--static-lib is specified.",
- &options.generate_non_final_ids)
- .OptionalFlag("--stable-ids", "File containing a list of name to ID mapping.",
- &stable_id_file_path)
- .OptionalFlag("--emit-ids",
- "Emit a file at the given path with a list of name to ID mappings,\n"
- "suitable for use with --stable-ids.",
- &options.resource_id_map_path)
- .OptionalFlag("--private-symbols",
- "Package name to use when generating R.java for private symbols.\n"
- "If not specified, public and private symbols will use the application's\n"
- "package name.",
- &options.private_symbols)
- .OptionalFlag("--custom-package", "Custom Java package under which to generate R.java.",
- &options.custom_java_package)
- .OptionalFlagList("--extra-packages",
- "Generate the same R.java but with different package names.",
- &extra_java_packages)
- .OptionalFlagList("--add-javadoc-annotation",
- "Adds a JavaDoc annotation to all generated Java classes.",
- &options.javadoc_annotations)
- .OptionalFlag("--output-text-symbols",
- "Generates a text file containing the resource symbols of the R class in\n"
- "the specified folder.",
- &options.generate_text_symbols_path)
- .OptionalSwitch("--allow-reserved-package-id",
- "Allows the use of a reserved package ID. This should on be used for\n"
- "packages with a pre-O min-sdk\n",
- &options.allow_reserved_package_id)
- .OptionalSwitch("--auto-add-overlay",
- "Allows the addition of new resources in overlays without\n"
- "<add-resource> tags.",
- &options.auto_add_overlay)
- .OptionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml.",
- &options.manifest_fixer_options.rename_manifest_package)
- .OptionalFlag("--rename-instrumentation-target-package",
- "Changes the name of the target package for instrumentation. Most useful\n"
- "when used in conjunction with --rename-manifest-package.",
- &options.manifest_fixer_options.rename_instrumentation_target_package)
- .OptionalFlagList("-0", "File extensions not to compress.",
- &options.extensions_to_not_compress)
- .OptionalSwitch("--warn-manifest-validation",
- "Treat manifest validation errors as warnings.",
- &options.manifest_fixer_options.warn_validation)
- .OptionalFlagList("--split",
- "Split resources matching a set of configs out to a Split APK.\n"
- "Syntax: path/to/output.apk:<config>[,<config>[...]].\n"
- "On Windows, use a semicolon ';' separator instead.",
- &split_args)
- .OptionalSwitch("-v", "Enables verbose logging.", &verbose)
- .OptionalSwitch("--debug-mode",
- "Inserts android:debuggable=\"true\" in to the application node of the\n"
- "manifest, making the application debuggable even on production devices.",
- &options.manifest_fixer_options.debug_mode);
-
- if (!flags.Parse("aapt2 link", args, &std::cerr)) {
- return 1;
- }
+int LinkCommand::Action(const std::vector<std::string>& args) {
+ LinkContext context(diag_);
// Expand all argument-files passed into the command line. These start with '@'.
std::vector<std::string> arg_list;
- for (const std::string& arg : flags.GetArgs()) {
+ for (const std::string& arg : args) {
if (util::StartsWith(arg, "@")) {
const std::string path = arg.substr(1, arg.size() - 1);
std::string error;
@@ -2220,27 +1995,27 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
}
// Expand all argument-files passed to -R.
- for (const std::string& arg : overlay_arg_list) {
+ for (const std::string& arg : overlay_arg_list_) {
if (util::StartsWith(arg, "@")) {
const std::string path = arg.substr(1, arg.size() - 1);
std::string error;
- if (!file::AppendArgsFromFile(path, &options.overlay_files, &error)) {
+ if (!file::AppendArgsFromFile(path, &options_.overlay_files, &error)) {
context.GetDiagnostics()->Error(DiagMessage(path) << error);
return 1;
}
} else {
- options.overlay_files.push_back(arg);
+ options_.overlay_files.push_back(arg);
}
}
- if (verbose) {
- context.SetVerbose(verbose);
+ if (verbose_) {
+ context.SetVerbose(verbose_);
}
- if (int{shared_lib} + int{static_lib} + int{proto_format} > 1) {
+ 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");
+ << "only one of --shared-lib, --static-lib, or --proto_format can be defined");
return 1;
}
@@ -2248,26 +2023,26 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
context.SetPackageType(PackageType::kApp);
context.SetPackageId(kAppPackageId);
- if (shared_lib) {
+ if (shared_lib_) {
context.SetPackageType(PackageType::kSharedLib);
context.SetPackageId(0x00);
- } else if (static_lib) {
+ } else if (static_lib_) {
context.SetPackageType(PackageType::kStaticLib);
- options.output_format = OutputFormat::kProto;
- } else if (proto_format) {
- options.output_format = OutputFormat::kProto;
+ options_.output_format = OutputFormat::kProto;
+ } else if (proto_format_) {
+ options_.output_format = OutputFormat::kProto;
}
- if (package_id) {
+ if (package_id_) {
if (context.GetPackageType() != PackageType::kApp) {
context.GetDiagnostics()->Error(
DiagMessage() << "can't specify --package-id when not building a regular app");
return 1;
}
- const Maybe<uint32_t> maybe_package_id_int = ResourceUtils::ParseInt(package_id.value());
+ const Maybe<uint32_t> maybe_package_id_int = ResourceUtils::ParseInt(package_id_.value());
if (!maybe_package_id_int) {
- context.GetDiagnostics()->Error(DiagMessage() << "package ID '" << package_id.value()
+ context.GetDiagnostics()->Error(DiagMessage() << "package ID '" << package_id_.value()
<< "' is not a valid integer");
return 1;
}
@@ -2275,7 +2050,7 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
const uint32_t package_id_int = maybe_package_id_int.value();
if (package_id_int > std::numeric_limits<uint8_t>::max()
|| package_id_int == kFrameworkPackageId
- || (!options.allow_reserved_package_id && package_id_int < kAppPackageId)) {
+ || (!options_.allow_reserved_package_id && package_id_int < kAppPackageId)) {
context.GetDiagnostics()->Error(
DiagMessage() << StringPrintf(
"invalid package ID 0x%02x. Must be in the range 0x7f-0xff.", package_id_int));
@@ -2285,71 +2060,71 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
}
// Populate the set of extra packages for which to generate R.java.
- for (std::string& extra_package : extra_java_packages) {
+ for (std::string& extra_package : extra_java_packages_) {
// A given package can actually be a colon separated list of packages.
for (StringPiece package : util::Split(extra_package, ':')) {
- options.extra_java_packages.insert(package.to_string());
+ options_.extra_java_packages.insert(package.to_string());
}
}
- if (product_list) {
- for (StringPiece product : util::Tokenize(product_list.value(), ',')) {
+ if (product_list_) {
+ for (StringPiece product : util::Tokenize(product_list_.value(), ',')) {
if (product != "" && product != "default") {
- options.products.insert(product.to_string());
+ options_.products.insert(product.to_string());
}
}
}
std::unique_ptr<IConfigFilter> filter;
- if (!configs.empty()) {
- filter = ParseConfigFilterParameters(configs, context.GetDiagnostics());
+ if (!configs_.empty()) {
+ filter = ParseConfigFilterParameters(configs_, context.GetDiagnostics());
if (filter == nullptr) {
return 1;
}
- options.table_splitter_options.config_filter = filter.get();
+ options_.table_splitter_options.config_filter = filter.get();
}
- if (preferred_density) {
+ if (preferred_density_) {
Maybe<uint16_t> density =
- ParseTargetDensityParameter(preferred_density.value(), context.GetDiagnostics());
+ ParseTargetDensityParameter(preferred_density_.value(), context.GetDiagnostics());
if (!density) {
return 1;
}
- options.table_splitter_options.preferred_densities.push_back(density.value());
+ options_.table_splitter_options.preferred_densities.push_back(density.value());
}
// 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_constraints.back())) {
+ 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_constraints.back())) {
return 1;
}
}
- if (context.GetPackageType() != PackageType::kStaticLib && stable_id_file_path) {
- if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path.value(),
- &options.stable_id_map)) {
+ if (context.GetPackageType() != PackageType::kStaticLib && stable_id_file_path_) {
+ if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path_.value(),
+ &options_.stable_id_map)) {
return 1;
}
}
// Populate some default no-compress extensions that are already compressed.
- options.extensions_to_not_compress.insert(
+ options_.extensions_to_not_compress.insert(
{".jpg", ".jpeg", ".png", ".gif", ".wav", ".mp2", ".mp3", ".ogg",
- ".aac", ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl",
- ".imy", ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2",
- ".3gpp2", ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"});
+ ".aac", ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl",
+ ".imy", ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2",
+ ".3gpp2", ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"});
// Turn off auto versioning for static-libs.
if (context.GetPackageType() == PackageType::kStaticLib) {
- options.no_auto_version = true;
- options.no_version_vectors = true;
- options.no_version_transitions = true;
+ options_.no_auto_version = true;
+ options_.no_version_vectors = true;
+ options_.no_version_transitions = true;
}
- LinkCommand cmd(&context, options);
+ Linker cmd(&context, options_);
return cmd.Run(arg_list);
}
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
new file mode 100644
index 000000000000..e58a93ec416c
--- /dev/null
+++ b/tools/aapt2/cmd/Link.h
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT2_LINK_H
+#define AAPT2_LINK_H
+
+#include "Command.h"
+#include "Diagnostics.h"
+#include "Resource.h"
+#include "split/TableSplitter.h"
+#include "format/binary/TableFlattener.h"
+#include "link/ManifestFixer.h"
+
+namespace aapt {
+
+enum class OutputFormat {
+ kApk,
+ kProto,
+};
+
+struct LinkOptions {
+ std::string output_path;
+ std::string manifest_path;
+ std::vector<std::string> include_paths;
+ std::vector<std::string> overlay_files;
+ 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;
+ Maybe<std::string> custom_java_package;
+ std::set<std::string> extra_java_packages;
+ 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;
+
+ // Optimizations/features.
+ bool no_auto_version = false;
+ bool no_version_vectors = false;
+ bool no_version_transitions = false;
+ bool no_resource_deduping = false;
+ bool no_xml_namespaces = false;
+ bool do_not_compress_anything = false;
+ std::unordered_set<std::string> extensions_to_not_compress;
+
+ // Static lib options.
+ bool no_static_lib_packages = false;
+
+ // AndroidManifest.xml massaging options.
+ ManifestFixerOptions manifest_fixer_options;
+
+ // Products to use/filter on.
+ std::unordered_set<std::string> products;
+
+ // Flattening options.
+ TableFlattenerOptions table_flattener_options;
+
+ // Split APK options.
+ TableSplitterOptions table_splitter_options;
+ std::vector<SplitConstraints> split_constraints;
+ std::vector<std::string> split_paths;
+
+ // Stable ID options.
+ std::unordered_map<ResourceName, ResourceId> stable_id_map;
+ Maybe<std::string> resource_id_map_path;
+
+ // When 'true', allow reserved package IDs to be used for applications. Pre-O, the platform
+ // treats negative resource IDs [those with a package ID of 0x80 or higher] as invalid.
+ // In order to work around this limitation, we allow the use of traditionally reserved
+ // resource IDs [those between 0x02 and 0x7E].
+ bool allow_reserved_package_id = false;
+
+ // Whether we should fail on definitions of a resource with conflicting visibility.
+ bool strict_visibility = false;
+};
+
+class LinkCommand : public Command {
+ public:
+ explicit LinkCommand(IDiagnostics* diag) : Command("link", "l"),
+ diag_(diag) {
+ SetDescription("Links resources into an apk.");
+ AddRequiredFlag("-o", "Output path.", &options_.output_path);
+ AddRequiredFlag("--manifest", "Path to the Android manifest to build.",
+ &options_.manifest_path);
+ AddOptionalFlagList("-I", "Adds an Android APK to link against.", &options_.include_paths);
+ AddOptionalFlagList("-A", "An assets directory to include in the APK. These are unprocessed.",
+ &options_.assets_dirs);
+ AddOptionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
+ "The last conflicting resource given takes precedence.", &overlay_arg_list_);
+ AddOptionalFlag("--package-id",
+ "Specify the package ID to use for this app. Must be greater or equal to\n"
+ "0x7f and can't be used with --static-lib or --shared-lib.", &package_id_);
+ AddOptionalFlag("--java", "Directory in which to generate R.java.",
+ &options_.generate_java_class_path);
+ AddOptionalFlag("--proguard", "Output file for generated Proguard rules.",
+ &options_.generate_proguard_rules_path);
+ AddOptionalFlag("--proguard-main-dex",
+ "Output file for generated Proguard rules for the main dex.",
+ &options_.generate_main_dex_proguard_rules_path);
+ AddOptionalSwitch("--proguard-conditional-keep-rules",
+ "Generate conditional Proguard keep rules.",
+ &options_.generate_conditional_proguard_rules);
+ AddOptionalSwitch("--no-auto-version", "Disables automatic style and layout SDK versioning.",
+ &options_.no_auto_version);
+ AddOptionalSwitch("--no-version-vectors",
+ "Disables automatic versioning of vector drawables. Use this only\n"
+ "when building with vector drawable support library.",
+ &options_.no_version_vectors);
+ AddOptionalSwitch("--no-version-transitions",
+ "Disables automatic versioning of transition resources. Use this only\n"
+ "when building with transition support library.",
+ &options_.no_version_transitions);
+ AddOptionalSwitch("--no-resource-deduping", "Disables automatic deduping of resources with\n"
+ "identical values across compatible configurations.",
+ &options_.no_resource_deduping);
+ AddOptionalSwitch("--enable-sparse-encoding",
+ "This decreases APK size at the cost of resource retrieval performance.",
+ &options_.table_flattener_options.use_sparse_entries);
+ AddOptionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01.",
+ &legacy_x_flag_);
+ AddOptionalSwitch("-z", "Require localization of strings marked 'suggested'.",
+ &require_localization_);
+ AddOptionalFlagList("-c",
+ "Comma separated list of configurations to include. The default\n"
+ "is all configurations.", &configs_);
+ AddOptionalFlag("--preferred-density",
+ "Selects the closest matching density and strips out all others.",
+ &preferred_density_);
+ AddOptionalFlag("--product", "Comma separated list of product names to keep", &product_list_);
+ AddOptionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified by -o.",
+ &options_.output_to_directory);
+ AddOptionalSwitch("--no-xml-namespaces", "Removes XML namespace prefix and URI information\n"
+ "from AndroidManifest.xml and XML binaries in res/*.",
+ &options_.no_xml_namespaces);
+ AddOptionalFlag("--min-sdk-version",
+ "Default minimum SDK version to use for AndroidManifest.xml.",
+ &options_.manifest_fixer_options.min_sdk_version_default);
+ AddOptionalFlag("--target-sdk-version",
+ "Default target SDK version to use for AndroidManifest.xml.",
+ &options_.manifest_fixer_options.target_sdk_version_default);
+ AddOptionalFlag("--version-code",
+ "Version code (integer) to inject into the AndroidManifest.xml if none is\n"
+ "present.", &options_.manifest_fixer_options.version_code_default);
+ AddOptionalFlag("--version-code-major",
+ "Version code major (integer) to inject into the AndroidManifest.xml if none is\n"
+ "present.", &options_.manifest_fixer_options.version_code_major_default);
+ AddOptionalFlag("--version-name",
+ "Version name to inject into the AndroidManifest.xml if none is present.",
+ &options_.manifest_fixer_options.version_name_default);
+ AddOptionalSwitch("--replace-version",
+ "If --version-code and/or --version-name are specified, these\n"
+ "values will replace any value already in the manifest. By\n"
+ "default, nothing is changed if the manifest already defines\n"
+ "these attributes.",
+ &options_.manifest_fixer_options.replace_version);
+ AddOptionalFlag("--compile-sdk-version-code",
+ "Version code (integer) to inject into the AndroidManifest.xml if none is\n"
+ "present.",
+ &options_.manifest_fixer_options.compile_sdk_version);
+ AddOptionalFlag("--compile-sdk-version-name",
+ "Version name to inject into the AndroidManifest.xml if none is present.",
+ &options_.manifest_fixer_options.compile_sdk_version_codename);
+ AddOptionalSwitch("--shared-lib", "Generates a shared Android runtime library.",
+ &shared_lib_);
+ AddOptionalSwitch("--static-lib", "Generate a static Android library.", &static_lib_);
+ AddOptionalSwitch("--proto-format",
+ "Generates compiled resources in Protobuf format.\n"
+ "Suitable as input to the bundle tool for generating an App Bundle.",
+ &proto_format_);
+ AddOptionalSwitch("--no-static-lib-packages",
+ "Merge all library resources under the app's package.",
+ &options_.no_static_lib_packages);
+ AddOptionalSwitch("--non-final-ids",
+ "Generates R.java without the final modifier. This is implied when\n"
+ "--static-lib is specified.",
+ &options_.generate_non_final_ids);
+ AddOptionalFlag("--stable-ids", "File containing a list of name to ID mapping.",
+ &stable_id_file_path_);
+ AddOptionalFlag("--emit-ids",
+ "Emit a file at the given path with a list of name to ID mappings,\n"
+ "suitable for use with --stable-ids.",
+ &options_.resource_id_map_path);
+ AddOptionalFlag("--private-symbols",
+ "Package name to use when generating R.java for private symbols.\n"
+ "If not specified, public and private symbols will use the application's\n"
+ "package name.",
+ &options_.private_symbols);
+ AddOptionalFlag("--custom-package", "Custom Java package under which to generate R.java.",
+ &options_.custom_java_package);
+ AddOptionalFlagList("--extra-packages",
+ "Generate the same R.java but with different package names.",
+ &extra_java_packages_);
+ AddOptionalFlagList("--add-javadoc-annotation",
+ "Adds a JavaDoc annotation to all generated Java classes.",
+ &options_.javadoc_annotations);
+ AddOptionalFlag("--output-text-symbols",
+ "Generates a text file containing the resource symbols of the R class in\n"
+ "the specified folder.",
+ &options_.generate_text_symbols_path);
+ AddOptionalSwitch("--allow-reserved-package-id",
+ "Allows the use of a reserved package ID. This should on be used for\n"
+ "packages with a pre-O min-sdk\n",
+ &options_.allow_reserved_package_id);
+ AddOptionalSwitch("--auto-add-overlay",
+ "Allows the addition of new resources in overlays without\n"
+ "<add-resource> tags.",
+ &options_.auto_add_overlay);
+ AddOptionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml.",
+ &options_.manifest_fixer_options.rename_manifest_package);
+ AddOptionalFlag("--rename-instrumentation-target-package",
+ "Changes the name of the target package for instrumentation. Most useful\n"
+ "when used in conjunction with --rename-manifest-package.",
+ &options_.manifest_fixer_options.rename_instrumentation_target_package);
+ AddOptionalFlagList("-0", "File extensions not to compress.",
+ &options_.extensions_to_not_compress);
+ AddOptionalSwitch("--no-compress", "Do not compress any resources.",
+ &options_.do_not_compress_anything);
+ AddOptionalSwitch("--warn-manifest-validation",
+ "Treat manifest validation errors as warnings.",
+ &options_.manifest_fixer_options.warn_validation);
+ AddOptionalFlagList("--split",
+ "Split resources matching a set of configs out to a Split APK.\n"
+ "Syntax: path/to/output.apk:<config>[,<config>[...]].\n"
+ "On Windows, use a semicolon ';' separator instead.",
+ &split_args_);
+ AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_);
+ AddOptionalSwitch("--debug-mode",
+ "Inserts android:debuggable=\"true\" in to the application node of the\n"
+ "manifest, making the application debuggable even on production devices.",
+ &options_.manifest_fixer_options.debug_mode);
+ AddOptionalSwitch("--strict-visibility",
+ "Do not allow overlays with different visibility levels.",
+ &options_.strict_visibility);
+ }
+
+ int Action(const std::vector<std::string>& args) override;
+
+ private:
+ IDiagnostics* diag_;
+ LinkOptions options_;
+
+ std::vector<std::string> overlay_arg_list_;
+ std::vector<std::string> extra_java_packages_;
+ Maybe<std::string> package_id_;
+ std::vector<std::string> configs_;
+ Maybe<std::string> preferred_density_;
+ Maybe<std::string> product_list_;
+ bool legacy_x_flag_ = false;
+ bool require_localization_ = false;
+ 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_;
+};
+
+}// namespace aapt
+
+#endif //AAPT2_LINK_H \ No newline at end of file
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 9c76119f9504..47288ec092de 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include "Optimize.h"
+
#include <memory>
#include <vector>
@@ -24,7 +26,6 @@
#include "androidfw/StringPiece.h"
#include "Diagnostics.h"
-#include "Flags.h"
#include "LoadedApk.h"
#include "ResourceUtils.h"
#include "SdkConstants.h"
@@ -38,6 +39,7 @@
#include "io/Util.h"
#include "optimize/MultiApkGenerator.h"
#include "optimize/ResourceDeduper.h"
+#include "optimize/ResourceFilter.h"
#include "optimize/VersionCollapser.h"
#include "split/TableSplitter.h"
#include "util/Files.h"
@@ -53,33 +55,6 @@ using ::android::base::StringPrintf;
namespace aapt {
-struct OptimizeOptions {
- // Path to the output APK.
- Maybe<std::string> output_path;
- // Path to the output APK directory for splits.
- Maybe<std::string> output_dir;
-
- // Details of the app extracted from the AndroidManifest.xml
- AppInfo app_info;
-
- // Split APK options.
- TableSplitterOptions table_splitter_options;
-
- // List of output split paths. These are in the same order as `split_constraints`.
- std::vector<std::string> split_paths;
-
- // List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`.
- std::vector<SplitConstraints> split_constraints;
-
- TableFlattenerOptions table_flattener_options;
-
- Maybe<std::vector<OutputArtifact>> apk_artifacts;
-
- // 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 {
public:
OptimizeContext() = default;
@@ -137,9 +112,9 @@ class OptimizeContext : public IAaptContext {
int sdk_version_ = 0;
};
-class OptimizeCommand {
+class Optimizer {
public:
- OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
+ Optimizer(OptimizeContext* context, const OptimizeOptions& options)
: options_(options), context_(context) {
}
@@ -147,6 +122,13 @@ class OptimizeCommand {
if (context_->IsVerbose()) {
context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
}
+ if (!options_.resources_blacklist.empty()) {
+ ResourceFilter filter(options_.resources_blacklist);
+ if (!filter.Consume(context_, apk->GetResourceTable())) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "failed filtering resources");
+ return 1;
+ }
+ }
VersionCollapser collapser;
if (!collapser.Consume(context_, apk->GetResourceTable())) {
@@ -284,16 +266,62 @@ class OptimizeCommand {
OptimizeContext* context_;
};
-bool ExtractWhitelistFromConfig(const std::string& path, OptimizeContext* context,
- OptimizeOptions* options) {
+bool ExtractObfuscationWhitelistFromConfig(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;
}
- for (const StringPiece& resource_name : util::Tokenize(contents, ',')) {
- options->table_flattener_options.whitelisted_resources.insert(resource_name.to_string());
+ for (StringPiece resource_name : util::Tokenize(contents, ',')) {
+ options->table_flattener_options.whitelisted_resources.insert(
+ resource_name.to_string());
+ }
+ return true;
+}
+
+bool ExtractConfig(const std::string& path, OptimizeContext* context,
+ OptimizeOptions* options) {
+ std::string content;
+ if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) {
+ context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading whitelist");
+ return false;
+ }
+
+ size_t line_no = 0;
+ for (StringPiece line : util::Tokenize(content, '\n')) {
+ line_no++;
+ line = util::TrimWhitespace(line);
+ if (line.empty()) {
+ continue;
+ }
+
+ auto split_line = util::Split(line, '#');
+ if (split_line.size() < 2) {
+ context->GetDiagnostics()->Error(DiagMessage(line) << "No # found in line");
+ return false;
+ }
+ StringPiece resource_string = split_line[0];
+ StringPiece directives = split_line[1];
+ ResourceNameRef resource_name;
+ if (!ResourceUtils::ParseResourceName(resource_string, &resource_name)) {
+ context->GetDiagnostics()->Error(DiagMessage(line) << "Malformed resource name");
+ return false;
+ }
+ if (!resource_name.package.empty()) {
+ context->GetDiagnostics()->Error(DiagMessage(line)
+ << "Package set for resource. Only use type/name");
+ return false;
+ }
+ for (StringPiece directive : util::Tokenize(directives, ',')) {
+ if (directive == "remove") {
+ options->resources_blacklist.insert(resource_name.ToResourceName());
+ } else if (directive == "no_obfuscate") {
+ options->table_flattener_options.whitelisted_resources.insert(
+ resource_name.entry.to_string());
+ }
+ }
}
return true;
}
@@ -317,76 +345,24 @@ bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
return true;
}
-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;
- 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)
- .OptionalFlagList("-c",
- "Comma separated list of configurations to include. The default\n"
- "is all configurations.",
- &configs)
- .OptionalFlagList("--split",
- "Split resources matching a set of configs out to a "
- "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)) {
- return 1;
- }
-
- if (flags.GetArgs().size() != 1u) {
+int OptimizeCommand::Action(const std::vector<std::string>& args) {
+ if (args.size() != 1u) {
std::cerr << "must have one APK as argument.\n\n";
- flags.Usage("aapt2 optimize", &std::cerr);
+ Usage(&std::cerr);
return 1;
}
- const std::string& apk_path = flags.GetArgs()[0];
-
- context.SetVerbose(verbose);
+ const std::string& apk_path = args[0];
+ OptimizeContext context;
+ context.SetVerbose(verbose_);
IDiagnostics* diag = context.GetDiagnostics();
- if (config_path) {
- std::string& path = config_path.value();
+ if (config_path_) {
+ std::string& path = config_path_.value();
Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
if (for_path) {
- options.apk_artifacts = for_path.value().WithDiagnostics(diag).Parse(apk_path);
- if (!options.apk_artifacts) {
+ options_.apk_artifacts = for_path.value().WithDiagnostics(diag).Parse(apk_path);
+ if (!options_.apk_artifacts) {
diag->Error(DiagMessage() << "Failed to parse the output artifact list");
return 1;
}
@@ -396,28 +372,28 @@ int Optimize(const std::vector<StringPiece>& args) {
return 1;
}
- if (print_only) {
- for (const OutputArtifact& artifact : options.apk_artifacts.value()) {
+ if (print_only_) {
+ for (const OutputArtifact& artifact : options_.apk_artifacts.value()) {
std::cout << artifact.name << std::endl;
}
return 0;
}
- if (!kept_artifacts.empty()) {
- for (const std::string& artifact_str : kept_artifacts) {
+ if (!kept_artifacts_.empty()) {
+ for (const std::string& artifact_str : kept_artifacts_) {
for (const StringPiece& artifact : util::Tokenize(artifact_str, ',')) {
- options.kept_artifacts.insert(artifact.to_string());
+ 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) {
+ if (!options_.output_dir) {
diag->Error(DiagMessage() << "Output directory is required when using a configuration file");
return 1;
}
- } else if (print_only) {
+ } else if (print_only_) {
diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations");
return 1;
}
@@ -427,50 +403,57 @@ int Optimize(const std::vector<StringPiece>& args) {
return 1;
}
- if (target_densities) {
+ if (target_densities_) {
// Parse the target screen densities.
- for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
+ for (const StringPiece& config_str : util::Tokenize(target_densities_.value(), ',')) {
Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
if (!target_density) {
return 1;
}
- options.table_splitter_options.preferred_densities.push_back(target_density.value());
+ options_.table_splitter_options.preferred_densities.push_back(target_density.value());
}
}
std::unique_ptr<IConfigFilter> filter;
- if (!configs.empty()) {
- filter = ParseConfigFilterParameters(configs, diag);
+ if (!configs_.empty()) {
+ filter = ParseConfigFilterParameters(configs_, diag);
if (filter == nullptr) {
return 1;
}
- options.table_splitter_options.config_filter = filter.get();
+ options_.table_splitter_options.config_filter = filter.get();
}
// Parse the split parameters.
- for (const std::string& split_arg : split_args) {
- options.split_paths.emplace_back();
- options.split_constraints.emplace_back();
- if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
- &options.split_constraints.back())) {
+ for (const std::string& split_arg : split_args_) {
+ 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 (options.table_flattener_options.collapse_key_stringpool) {
- if (whitelist_path) {
- std::string& path = whitelist_path.value();
- if (!ExtractWhitelistFromConfig(path, &context, &options)) {
+ if (options_.table_flattener_options.collapse_key_stringpool) {
+ if (whitelist_path_) {
+ std::string& path = whitelist_path_.value();
+ if (!ExtractObfuscationWhitelistFromConfig(path, &context, &options_)) {
return 1;
}
}
}
- if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
+ if (resources_config_path_) {
+ std::string& path = resources_config_path_.value();
+ if (!ExtractConfig(path, &context, &options_)) {
+ return 1;
+ }
+ }
+
+ if (!ExtractAppDataFromManifest(&context, apk.get(), &options_)) {
return 1;
}
- OptimizeCommand cmd(&context, options);
+ Optimizer cmd(&context, options_);
return cmd.Run(std::move(apk));
}
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
new file mode 100644
index 000000000000..43bc216382fa
--- /dev/null
+++ b/tools/aapt2/cmd/Optimize.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT2_OPTIMIZE_H
+#define AAPT2_OPTIMIZE_H
+
+#include "AppInfo.h"
+#include "Command.h"
+#include "configuration/ConfigurationParser.h"
+#include "format/binary/TableFlattener.h"
+#include "split/TableSplitter.h"
+
+namespace aapt {
+
+struct OptimizeOptions {
+ friend class OptimizeCommand;
+
+ // Path to the output APK.
+ Maybe<std::string> output_path;
+ // Path to the output APK directory for splits.
+ Maybe<std::string> output_dir;
+
+ // Details of the app extracted from the AndroidManifest.xml
+ AppInfo app_info;
+
+ // Blacklist of unused resources that should be removed from the apk.
+ std::unordered_set<ResourceName> resources_blacklist;
+
+ // Split APK options.
+ TableSplitterOptions table_splitter_options;
+
+ // List of output split paths. These are in the same order as `split_constraints`.
+ std::vector<std::string> split_paths;
+
+ // List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`.
+ std::vector<SplitConstraints> split_constraints;
+
+ TableFlattenerOptions table_flattener_options;
+
+ Maybe<std::vector<aapt::configuration::OutputArtifact>> apk_artifacts;
+
+ // 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 OptimizeCommand : public Command {
+ public:
+ explicit OptimizeCommand() : Command("optimize") {
+ SetDescription("Preforms resource optimizations on an apk.");
+ AddOptionalFlag("-o", "Path to the output APK.", &options_.output_path);
+ AddOptionalFlag("-d", "Path to the output directory (for splits).", &options_.output_dir);
+ AddOptionalFlag("-x", "Path to XML configuration file.", &config_path_);
+ AddOptionalSwitch("-p", "Print the multi APK artifacts and exit.", &print_only_);
+ AddOptionalFlag(
+ "--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_);
+ AddOptionalFlag("--whitelist-path",
+ "Path to the whitelist.cfg file containing whitelisted resources \n"
+ "whose names should not be altered in final resource tables.",
+ &whitelist_path_);
+ AddOptionalFlag("--resources-config-path",
+ "Path to the resources.cfg file containing the list of resources and \n"
+ "directives to each resource. \n"
+ "Format: type/resource_name#[directive][,directive]",
+ &resources_config_path_);
+ AddOptionalFlagList("-c",
+ "Comma separated list of configurations to include. The default\n"
+ "is all configurations.",
+ &configs_);
+ AddOptionalFlagList("--split",
+ "Split resources matching a set of configs out to a "
+ "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n"
+ "On Windows, use a semicolon ';' separator instead.",
+ &split_args_);
+ AddOptionalFlagList("--keep-artifacts",
+ "Comma separated list of artifacts to keep. If none are specified,\n"
+ "all artifacts will be kept.",
+ &kept_artifacts_);
+ AddOptionalSwitch("--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);
+ AddOptionalSwitch("--enable-resource-obfuscation",
+ "Enables obfuscation of key string pool to single value",
+ &options_.table_flattener_options.collapse_key_stringpool);
+ AddOptionalSwitch("-v", "Enables verbose logging", &verbose_);
+ }
+
+ int Action(const std::vector<std::string>& args) override;
+
+ private:
+ OptimizeOptions options_;
+
+ Maybe<std::string> config_path_;
+ Maybe<std::string> whitelist_path_;
+ Maybe<std::string> resources_config_path_;
+ Maybe<std::string> target_densities_;
+ std::vector<std::string> configs_;
+ std::vector<std::string> split_args_;
+ std::unordered_set<std::string> kept_artifacts_;
+ bool print_only_ = false;
+ bool verbose_ = false;
+};
+
+}// namespace aapt
+
+#endif //AAPT2_OPTIMIZE_H \ No newline at end of file
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 8b3a6701b409..c6c82b04ff2e 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -29,6 +29,7 @@
#include "util/Util.h"
using ::android::StringPiece;
+using ::android::base::StringPrintf;
namespace aapt {
@@ -145,7 +146,7 @@ static xml::NamespaceDecl CreateAndroidNamespaceDecl() {
//
// See frameworks/base/core/java/android/content/pm/PackageParser.java which
// checks this at runtime.
-static std::string MakePackageSafeName(const std::string &name) {
+std::string MakePackageSafeName(const std::string &name) {
std::string result(name);
bool first = true;
for (char &c : result) {
@@ -168,6 +169,7 @@ static std::string MakePackageSafeName(const std::string &name) {
std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
const SplitConstraints& constraints) {
const ResourceId kVersionCode(0x0101021b);
+ const ResourceId kVersionCodeMajor(0x01010576);
const ResourceId kRevisionCode(0x010104d5);
const ResourceId kHasCode(0x0101000c);
@@ -184,6 +186,14 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_DEC, version_code)});
}
+ if (app_info.version_code_major) {
+ const uint32_t version_code_major = app_info.version_code_major.value();
+ manifest_el->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid, "versionCodeMajor", std::to_string(version_code_major),
+ CreateAttributeWithId(kVersionCodeMajor),
+ util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_DEC, version_code_major)});
+ }
+
if (app_info.revision_code) {
const uint32_t revision_code = app_info.revision_code.value();
manifest_el->attributes.push_back(xml::Attribute{
@@ -355,6 +365,17 @@ Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res,
app_info.version_code = maybe_code.value();
}
+ if (const xml::Attribute* version_code_major_attr =
+ manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor")) {
+ Maybe<uint32_t> maybe_code = ExtractCompiledInt(*version_code_major_attr, &error_msg);
+ if (!maybe_code) {
+ diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
+ << "invalid android:versionCodeMajor: " << error_msg);
+ return {};
+ }
+ app_info.version_code_major = maybe_code.value();
+ }
+
if (const xml::Attribute* revision_code_attr =
manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) {
Maybe<uint32_t> maybe_code = ExtractCompiledInt(*revision_code_attr, &error_msg);
@@ -391,4 +412,21 @@ Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res,
return app_info;
}
+void SetLongVersionCode(xml::Element* manifest, uint64_t version) {
+ // Write the low bits of the version code to android:versionCode
+ auto version_code = manifest->FindOrCreateAttribute(xml::kSchemaAndroid, "versionCode");
+ version_code->value = StringPrintf("0x%08x", (uint32_t) (version & 0xffffffff));
+ version_code->compiled_value = ResourceUtils::TryParseInt(version_code->value);
+
+ auto version_high = (uint32_t) (version >> 32);
+ if (version_high != 0) {
+ // Write the high bits of the version code to android:versionCodeMajor
+ auto version_major = manifest->FindOrCreateAttribute(xml::kSchemaAndroid, "versionCodeMajor");
+ version_major->value = StringPrintf("0x%08x", version_high);
+ version_major->compiled_value = ResourceUtils::TryParseInt(version_major->value);
+ } else {
+ manifest->RemoveAttribute(xml::kSchemaAndroid, "versionCodeMajor");
+ }
+}
+
} // namespace aapt
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index 7611c1526104..cf1443e30e1f 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -60,6 +60,18 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res,
IDiagnostics* diag);
+// Returns a copy of 'name' which conforms to the regex '[a-zA-Z]+[a-zA-Z0-9_]*' by
+// replacing nonconforming characters with underscores.
+//
+// See frameworks/base/core/java/android/content/pm/PackageParser.java which
+// checks this at runtime.
+std::string MakePackageSafeName(const std::string &name);
+
+// Sets the versionCode and versionCodeMajor attributes to the version code. Attempts to encode the
+// version code using the versionCode attribute only, and encodes using both versionCode and
+// versionCodeMajor if the version code requires more than 32 bits.
+void SetLongVersionCode(xml::Element* manifest, uint64_t version_code);
+
} // namespace aapt
#endif /* AAPT_SPLIT_UTIL_H */
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
index 0c527f6a90dc..b9fb5b2868a7 100644
--- a/tools/aapt2/cmd/Util_test.cpp
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -18,6 +18,7 @@
#include "AppInfo.h"
#include "split/TableSplitter.h"
+#include "test/Builders.h"
#include "test/Test.h"
namespace aapt {
@@ -36,4 +37,51 @@ TEST(UtilTest, SplitNamesAreSanitized) {
EXPECT_EQ(root->FindAttribute("", "targetConfig")->value, "b+sr+Latn,en-rUS-land");
}
+TEST (UtilTest, LongVersionCodeDefined) {
+ auto doc = test::BuildXmlDom(R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.aapt.test" android:versionCode="0x1" android:versionCodeMajor="0x1">
+ </manifest>)");
+ SetLongVersionCode(doc->root.get(), 42);
+
+ auto version_code = doc->root->FindAttribute(xml::kSchemaAndroid, "versionCode");
+ ASSERT_NE(version_code, nullptr);
+ EXPECT_EQ(version_code->value, "0x0000002a");
+
+ ASSERT_NE(version_code->compiled_value, nullptr);
+ auto compiled_version_code = ValueCast<BinaryPrimitive>(version_code->compiled_value.get());
+ ASSERT_NE(compiled_version_code, nullptr);
+ EXPECT_EQ(compiled_version_code->value.data, 42U);
+
+ auto version_code_major = doc->root->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor");
+ EXPECT_EQ(version_code_major, nullptr);
+}
+
+TEST (UtilTest, LongVersionCodeUndefined) {
+ auto doc = test::BuildXmlDom(R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.aapt.test">
+ </manifest>)");
+ SetLongVersionCode(doc->root.get(), 420000000000);
+
+ auto version_code = doc->root->FindAttribute(xml::kSchemaAndroid, "versionCode");
+ ASSERT_NE(version_code, nullptr);
+ EXPECT_EQ(version_code->value, "0xc9f36800");
+
+ ASSERT_NE(version_code->compiled_value, nullptr);
+ auto compiled_version_code = ValueCast<BinaryPrimitive>(version_code->compiled_value.get());
+ ASSERT_NE(compiled_version_code, nullptr);
+ EXPECT_EQ(compiled_version_code->value.data, 0xc9f36800);
+
+ auto version_code_major = doc->root->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor");
+ ASSERT_NE(version_code_major, nullptr);
+ EXPECT_EQ(version_code_major->value, "0x00000061");
+
+ ASSERT_NE(version_code_major->compiled_value, nullptr);
+ auto compiled_version_code_major = ValueCast<BinaryPrimitive>(
+ version_code_major->compiled_value.get());
+ ASSERT_NE(compiled_version_code_major, nullptr);
+ EXPECT_EQ(compiled_version_code_major->value.data, 0x61);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 6b07b1e96261..d1a70a75a44e 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -256,9 +256,20 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res
styleable_attr.field_name =
TransformNestedAttr(attr.name.value(), array_field_name, package_name_to_generate);
+ Reference ref = attr;
+ if (attr.name.value().package.empty()) {
+
+ // If the resource does not have a package name, set the package to the unmangled package name
+ // of the styleable declaration because attributes without package names would have been
+ // declared in the same package as the styleable.
+ ref.name = ResourceName(package_name_to_generate, ref.name.value().type,
+ ref.name.value().entry);
+ }
+
// Look up the symbol so that we can write out in the comments what are possible legal values
// for this attribute.
- const SymbolTable::Symbol* symbol = context_->GetExternalSymbols()->FindByReference(attr);
+ const SymbolTable::Symbol* symbol = context_->GetExternalSymbols()->FindByReference(ref);
+
if (symbol && symbol->attribute) {
// Copy the symbol data structure because the returned instance can be destroyed.
styleable_attr.symbol = *symbol;
@@ -303,7 +314,7 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res
const ResourceName& attr_name = entry.attr_ref->name.value();
styleable_comment << "<tr><td><code>{@link #" << entry.field_name << " "
<< (!attr_name.package.empty() ? attr_name.package
- : context_->GetCompilationPackage())
+ : package_name_to_generate)
<< ":" << attr_name.entry << "}</code></td>";
// Only use the comment up until the first '.'. This is to stay compatible with
@@ -347,7 +358,9 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res
}
// Add the Styleable array to the Styleable class.
- out_class_def->AddMember(std::move(array_def));
+ if (out_class_def != nullptr) {
+ out_class_def->AddMember(std::move(array_def));
+ }
// Now we emit the indices into the array.
for (size_t i = 0; i < attr_count; i++) {
@@ -372,7 +385,7 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res
StringPiece package_name = attr_name.package;
if (package_name.empty()) {
- package_name = context_->GetCompilationPackage();
+ package_name = package_name_to_generate;
}
std::unique_ptr<IntMember> index_member =
@@ -578,7 +591,6 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
if (out_r_txt != nullptr) {
r_txt_printer = util::make_unique<Printer>(out_r_txt);
}
-
// Generate an onResourcesLoaded() callback if requested.
if (out != nullptr && options_.rewrite_callback_options) {
rewrite_method =
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index e449546f9399..fa208be120ed 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -107,6 +107,55 @@ TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) {
EXPECT_THAT(output, Not(HasSubstr("com_foo$two")));
}
+TEST(JavaClassGeneratorTest, StyleableAttributesWithDifferentPackageName) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .SetPackageId("app", 0x7f)
+ .AddValue("app:attr/foo", ResourceId(0x7f010000),
+ test::AttributeBuilder().Build())
+ .AddValue("app:attr/bar", ResourceId(0x7f010001),
+ test::AttributeBuilder().Build())
+ .AddValue("android:attr/baz", ResourceId(0x01010000),
+ test::AttributeBuilder().Build())
+ .AddValue("app:styleable/MyStyleable", ResourceId(0x7f030000),
+ test::StyleableBuilder()
+ .AddItem("app:attr/foo", ResourceId(0x7f010000))
+ .AddItem("attr/bar", ResourceId(0x7f010001))
+ .AddItem("android:attr/baz", ResourceId(0x01010000))
+ .Build())
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder()
+ .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+ .SetNameManglerPolicy(NameManglerPolicy{"custom"})
+ .SetCompilationPackage("custom")
+ .Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+
+ std::string output;
+ StringOutputStream out(&output);
+ EXPECT_TRUE(generator.Generate("app", &out));
+ out.Flush();
+
+ EXPECT_THAT(output, Not(HasSubstr("public static final int baz=0x01010000;")));
+ EXPECT_THAT(output, HasSubstr("public static final int foo=0x7f010000;"));
+ EXPECT_THAT(output, HasSubstr("public static final int bar=0x7f010001;"));
+
+ EXPECT_THAT(output, HasSubstr("public static final int MyStyleable_android_baz=0;"));
+ EXPECT_THAT(output, HasSubstr("public static final int MyStyleable_foo=1;"));
+ EXPECT_THAT(output, HasSubstr("public static final int MyStyleable_bar=2;"));
+
+ EXPECT_THAT(output, HasSubstr("@link #MyStyleable_android_baz android:baz"));
+ EXPECT_THAT(output, HasSubstr("@link #MyStyleable_foo app:foo"));
+ EXPECT_THAT(output, HasSubstr("@link #MyStyleable_bar app:bar"));
+
+ EXPECT_THAT(output, HasSubstr("@link android.R.attr#baz"));
+ EXPECT_THAT(output, HasSubstr("@link app.R.attr#foo"));
+ EXPECT_THAT(output, HasSubstr("@link app.R.attr#bar"));
+}
+
TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
@@ -438,4 +487,22 @@ TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary)
EXPECT_THAT(output, HasSubstr("com.boo.R.onResourcesLoaded"));
}
+TEST(JavaClassGeneratorTest, OnlyGenerateRText) {
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddValue("android:attr/foo", ResourceId(0x01010000), util::make_unique<Attribute>())
+ .AddValue("android:styleable/hey.dude", ResourceId(0x01020000),
+ test::StyleableBuilder()
+ .AddItem("android:attr/foo", ResourceId(0x01010000))
+ .Build())
+ .Build();
+
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetPackageId(0x01).SetCompilationPackage("android").Build();
+ JavaClassGenerator generator(context.get(), table.get(), {});
+
+ ASSERT_TRUE(generator.Generate("android", nullptr));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index ffcef8966654..d40795accf79 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -39,7 +39,11 @@ class BaseVisitor : public xml::Visitor {
public:
using xml::Visitor::Visit;
- BaseVisitor(const ResourceFile& file, KeepSet* keep_set) : file_(file), keep_set_(keep_set) {
+ BaseVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set, "...") {
+ }
+
+ BaseVisitor(const ResourceFile& file, KeepSet* keep_set, const std::string& ctor_signature)
+ : file_(file), keep_set_(keep_set), ctor_signature_(ctor_signature) {
}
void Visit(xml::Element* node) override {
@@ -50,11 +54,11 @@ class BaseVisitor : public xml::Visitor {
// This is a custom view, let's figure out the class name from this.
std::string package = maybe_package.value().package + "." + node->name;
if (util::IsJavaClassName(package)) {
- AddClass(node->line_number, package);
+ AddClass(node->line_number, package, ctor_signature_);
}
}
} else if (util::IsJavaClassName(node->name)) {
- AddClass(node->line_number, node->name);
+ AddClass(node->line_number, node->name, ctor_signature_);
}
for (const auto& child : node->children) {
@@ -74,13 +78,18 @@ class BaseVisitor : public xml::Visitor {
protected:
ResourceFile file_;
KeepSet* keep_set_;
+ std::string ctor_signature_;
- virtual void AddClass(size_t line_number, const std::string& class_name) {
- keep_set_->AddConditionalClass({file_.name, file_.source.WithLine(line_number)}, class_name);
+ virtual void AddClass(size_t line_number, const std::string& class_name,
+ const std::string& ctor_signature) {
+ keep_set_->AddConditionalClass({file_.name, file_.source.WithLine(line_number)},
+ {class_name, ctor_signature});
}
- void AddMethod(size_t line_number, const std::string& method_name) {
- keep_set_->AddMethod({file_.name, file_.source.WithLine(line_number)}, method_name);
+ void AddMethod(size_t line_number, const std::string& method_name,
+ const std::string& method_signature) {
+ keep_set_->AddMethod({file_.name, file_.source.WithLine(line_number)},
+ {method_name, method_signature});
}
void AddReference(size_t line_number, Reference* ref) {
@@ -100,32 +109,39 @@ class BaseVisitor : public xml::Visitor {
class LayoutVisitor : public BaseVisitor {
public:
- LayoutVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
+ LayoutVisitor(const ResourceFile& file, KeepSet* keep_set)
+ : BaseVisitor(file, keep_set, "android.content.Context, android.util.AttributeSet") {
}
void Visit(xml::Element* node) override {
- bool check_class = false;
- bool check_name = false;
+ bool is_view = false;
+ bool is_fragment = false;
if (node->namespace_uri.empty()) {
if (node->name == "view") {
- check_class = true;
+ is_view = true;
} else if (node->name == "fragment") {
- check_class = check_name = true;
+ is_fragment = true;
}
} else if (node->namespace_uri == xml::kSchemaAndroid) {
- check_name = node->name == "fragment";
+ is_fragment = node->name == "fragment";
}
for (const auto& attr : node->attributes) {
- if (check_class && attr.namespace_uri.empty() && attr.name == "class" &&
- util::IsJavaClassName(attr.value)) {
- AddClass(node->line_number, attr.value);
- } else if (check_name && attr.namespace_uri == xml::kSchemaAndroid &&
- attr.name == "name" && util::IsJavaClassName(attr.value)) {
- AddClass(node->line_number, attr.value);
- } else if (attr.namespace_uri == xml::kSchemaAndroid &&
- attr.name == "onClick") {
- AddMethod(node->line_number, attr.value);
+ if (attr.namespace_uri.empty() && attr.name == "class") {
+ if (util::IsJavaClassName(attr.value)) {
+ if (is_view) {
+ AddClass(node->line_number, attr.value,
+ "android.content.Context, android.util.AttributeSet");
+ } else if (is_fragment) {
+ AddClass(node->line_number, attr.value, "");
+ }
+ }
+ } else if (attr.namespace_uri == xml::kSchemaAndroid && attr.name == "name") {
+ if (is_fragment && util::IsJavaClassName(attr.value)) {
+ AddClass(node->line_number, attr.value, "");
+ }
+ } else if (attr.namespace_uri == xml::kSchemaAndroid && attr.name == "onClick") {
+ AddMethod(node->line_number, attr.value, "android.view.View");
}
}
@@ -147,9 +163,9 @@ class MenuVisitor : public BaseVisitor {
if (attr.namespace_uri == xml::kSchemaAndroid) {
if ((attr.name == "actionViewClass" || attr.name == "actionProviderClass") &&
util::IsJavaClassName(attr.value)) {
- AddClass(node->line_number, attr.value);
+ AddClass(node->line_number, attr.value, "android.content.Context");
} else if (attr.name == "onClick") {
- AddMethod(node->line_number, attr.value);
+ AddMethod(node->line_number, attr.value, "android.view.MenuItem");
}
}
}
@@ -178,7 +194,7 @@ class XmlResourceVisitor : public BaseVisitor {
xml::Attribute* attr =
node->FindAttribute(xml::kSchemaAndroid, "fragment");
if (attr && util::IsJavaClassName(attr->value)) {
- AddClass(node->line_number, attr->value);
+ AddClass(node->line_number, attr->value, "");
}
}
@@ -189,6 +205,29 @@ class XmlResourceVisitor : public BaseVisitor {
DISALLOW_COPY_AND_ASSIGN(XmlResourceVisitor);
};
+class NavigationVisitor : public BaseVisitor {
+ public:
+ NavigationVisitor(const ResourceFile& file, KeepSet* keep_set, const std::string& package)
+ : BaseVisitor(file, keep_set), package_(package) {
+ }
+
+ void Visit(xml::Element* node) override {
+ const auto& attr = node->FindAttribute(xml::kSchemaAndroid, "name");
+ if (attr != nullptr && !attr->value.empty()) {
+ std::string name = (attr->value[0] == '.') ? package_ + attr->value : attr->value;
+ if (util::IsJavaClassName(name)) {
+ AddClass(node->line_number, name, "...");
+ }
+ }
+
+ BaseVisitor::Visit(node);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NavigationVisitor);
+ const std::string package_;
+};
+
class TransitionVisitor : public BaseVisitor {
public:
TransitionVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
@@ -200,7 +239,8 @@ class TransitionVisitor : public BaseVisitor {
if (check_class) {
xml::Attribute* attr = node->FindAttribute({}, "class");
if (attr && util::IsJavaClassName(attr->value)) {
- AddClass(node->line_number, attr->value);
+ AddClass(node->line_number, attr->value,
+ "android.content.Context, android.util.AttributeSet");
}
}
@@ -231,7 +271,14 @@ class ManifestVisitor : public BaseVisitor {
if (attr) {
Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value);
if (result) {
- AddClass(node->line_number, result.value());
+ AddClass(node->line_number, result.value(), "");
+ }
+ }
+ attr = node->FindAttribute(xml::kSchemaAndroid, "appComponentFactory");
+ if (attr) {
+ Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value);
+ if (result) {
+ AddClass(node->line_number, result.value(), "");
}
}
if (main_dex_only_) {
@@ -262,7 +309,7 @@ class ManifestVisitor : public BaseVisitor {
if (get_name) {
Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value);
if (result) {
- AddClass(node->line_number, result.value());
+ AddClass(node->line_number, result.value(), "");
}
}
}
@@ -270,7 +317,8 @@ class ManifestVisitor : public BaseVisitor {
BaseVisitor::Visit(node);
}
- virtual void AddClass(size_t line_number, const std::string& class_name) override {
+ virtual void AddClass(size_t line_number, const std::string& class_name,
+ const std::string& ctor_signature) override {
keep_set_->AddManifestClass({file_.name, file_.source.WithLine(line_number)}, class_name);
}
@@ -291,7 +339,7 @@ bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set, b
return false;
}
-bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set) {
+bool CollectProguardRules(IAaptContext* context_, xml::XmlResource* res, KeepSet* keep_set) {
if (!res->root) {
return false;
}
@@ -309,6 +357,12 @@ bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set) {
break;
}
+ case ResourceType::kNavigation: {
+ NavigationVisitor visitor(res->file, keep_set, context_->GetCompilationPackage());
+ res->root->Accept(&visitor);
+ break;
+ }
+
case ResourceType::kTransition: {
TransitionVisitor visitor(res->file, keep_set);
res->root->Accept(&visitor);
@@ -336,7 +390,7 @@ void WriteKeepSet(const KeepSet& keep_set, OutputStream* out) {
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.Print("-keep class ").Print(entry.first).Println(" { <init>(); }");
}
for (const auto& entry : keep_set.conditional_class_set_) {
@@ -352,13 +406,15 @@ void WriteKeepSet(const KeepSet& keep_set, OutputStream* out) {
printer.Print("-if class **.R$layout { int ")
.Print(JavaClassGenerator::TransformToFieldName(location.name.entry))
.Println("; }");
- printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }");
+ printer.Print("-keep class ").Print(entry.first.name).Print(" { <init>(")
+ .Print(entry.first.signature).Println("); }");
}
} 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.Print("-keep class ").Print(entry.first.name).Print(" { <init>(")
+ .Print(entry.first.signature).Println("); }");
}
printer.Println();
}
@@ -367,7 +423,8 @@ void WriteKeepSet(const KeepSet& keep_set, OutputStream* out) {
for (const UsageLocation& location : entry.second) {
printer.Print("# Referenced at ").Println(location.source.to_string());
}
- printer.Print("-keepclassmembers class * { *** ").Print(entry.first).Println("(...); }");
+ printer.Print("-keepclassmembers class * { *** ").Print(entry.first.name)
+ .Print("(").Print(entry.first.signature).Println("); }");
printer.Println();
}
}
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index 46827ee7cf93..01dad0b08aea 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -40,6 +40,11 @@ struct UsageLocation {
Source source;
};
+struct NameAndSignature {
+ std::string name;
+ std::string signature;
+};
+
class KeepSet {
public:
KeepSet() = default;
@@ -51,12 +56,13 @@ class KeepSet {
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 AddConditionalClass(const UsageLocation& file,
+ const NameAndSignature& class_and_signature) {
+ conditional_class_set_[class_and_signature].insert(file);
}
- inline void AddMethod(const UsageLocation& file, const std::string& method_name) {
- method_set_[method_name].insert(file);
+ inline void AddMethod(const UsageLocation& file, const NameAndSignature& name_and_signature) {
+ method_set_[name_and_signature].insert(file);
}
inline void AddReference(const UsageLocation& file, const ResourceName& resource_name) {
@@ -71,15 +77,15 @@ class KeepSet {
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<NameAndSignature, std::set<UsageLocation>> method_set_;
+ std::map<NameAndSignature, std::set<UsageLocation>> conditional_class_set_;
std::map<ResourceName, std::set<UsageLocation>> reference_set_;
};
bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set,
bool main_dex_only = false);
-bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set);
+bool CollectProguardRules(IAaptContext* context, xml::XmlResource* res, KeepSet* keep_set);
bool CollectResourceReferences(IAaptContext* context, ResourceTable* table, KeepSet* keep_set);
@@ -100,6 +106,20 @@ inline int operator<(const UsageLocation& lhs, const UsageLocation& rhs) {
return lhs.name.compare(rhs.name);
}
+//
+// NameAndSignature implementation.
+//
+
+inline bool operator<(const NameAndSignature& lhs, const NameAndSignature& rhs) {
+ if (lhs.name < rhs.name) {
+ return true;
+ }
+ if (lhs.name == rhs.name) {
+ return lhs.signature < rhs.signature;
+ }
+ return false;
+}
+
} // namespace proguard
} // namespace aapt
diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp
index 37d1a5fbaeb8..83c72d89bb62 100644
--- a/tools/aapt2/java/ProguardRules_test.cpp
+++ b/tools/aapt2/java/ProguardRules_test.cpp
@@ -34,6 +34,37 @@ std::string GetKeepSetString(const proguard::KeepSet& set) {
return out;
}
+TEST(ProguardRulesTest, ManifestRuleDefaultConstructorOnly) {
+ std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <application
+ android:appComponentFactory="com.foo.BarAppComponentFactory"
+ android:backupAgent="com.foo.BarBackupAgent"
+ android:name="com.foo.BarApplication"
+ >
+ <activity android:name="com.foo.BarActivity"/>
+ <service android:name="com.foo.BarService"/>
+ <receiver android:name="com.foo.BarReceiver"/>
+ <provider android:name="com.foo.BarProvider"/>
+ </application>
+ <instrumentation android:name="com.foo.BarInstrumentation"/>
+ </manifest>)");
+
+ proguard::KeepSet set;
+ ASSERT_TRUE(proguard::CollectProguardRulesForManifest(manifest.get(), &set, false));
+
+ std::string actual = GetKeepSetString(set);
+
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarAppComponentFactory { <init>(); }"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarBackupAgent { <init>(); }"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarApplication { <init>(); }"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarActivity { <init>(); }"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarService { <init>(); }"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarReceiver { <init>(); }"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarProvider { <init>(); }"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarInstrumentation { <init>(); }"));
+}
+
TEST(ProguardRulesTest, FragmentNameRuleIsEmitted) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
@@ -42,11 +73,11 @@ TEST(ProguardRulesTest, FragmentNameRuleIsEmitted) {
layout->file.name = test::ParseNameOrDie("layout/foo");
proguard::KeepSet set;
- ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
+ ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
std::string actual = GetKeepSetString(set);
- EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(); }"));
}
TEST(ProguardRulesTest, FragmentClassRuleIsEmitted) {
@@ -56,11 +87,11 @@ TEST(ProguardRulesTest, FragmentClassRuleIsEmitted) {
layout->file.name = test::ParseNameOrDie("layout/foo");
proguard::KeepSet set;
- ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
+ ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
std::string actual = GetKeepSetString(set);
- EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(); }"));
}
TEST(ProguardRulesTest, FragmentNameAndClassRulesAreEmitted) {
@@ -72,12 +103,40 @@ TEST(ProguardRulesTest, FragmentNameAndClassRulesAreEmitted) {
layout->file.name = test::ParseNameOrDie("layout/foo");
proguard::KeepSet set;
- ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
+ ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
std::string actual = GetKeepSetString(set);
- EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
- EXPECT_THAT(actual, HasSubstr("com.foo.Baz"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(); }"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz { <init>(); }"));
+}
+
+TEST(ProguardRulesTest, NavigationFragmentNameAndClassRulesAreEmitted) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .SetCompilationPackage("com.base").Build();
+ std::unique_ptr<xml::XmlResource> navigation = test::BuildXmlDom(R"(
+ <navigation
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <custom android:id="@id/foo"
+ android:name="com.package.Foo"/>
+ <fragment android:id="@id/bar"
+ android:name="com.package.Bar">
+ <nested android:id="@id/nested"
+ android:name=".Nested"/>
+ </fragment>
+ </navigation>
+ )");
+
+ navigation->file.name = test::ParseNameOrDie("navigation/graph.xml");
+
+ proguard::KeepSet set;
+ ASSERT_TRUE(proguard::CollectProguardRules(context.get(), navigation.get(), &set));
+
+ std::string actual = GetKeepSetString(set);
+ EXPECT_THAT(actual, HasSubstr("-keep class com.package.Foo { <init>(...); }"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.package.Bar { <init>(...); }"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.base.Nested { <init>(...); }"));
}
TEST(ProguardRulesTest, CustomViewRulesAreEmitted) {
@@ -89,11 +148,12 @@ TEST(ProguardRulesTest, CustomViewRulesAreEmitted) {
layout->file.name = test::ParseNameOrDie("layout/foo");
proguard::KeepSet set;
- ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
+ ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
std::string actual = GetKeepSetString(set);
- EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
+ EXPECT_THAT(actual, HasSubstr(
+ "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
}
TEST(ProguardRulesTest, IncludedLayoutRulesAreConditional) {
@@ -125,16 +185,16 @@ TEST(ProguardRulesTest, IncludedLayoutRulesAreConditional) {
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));
+ ASSERT_TRUE(proguard::CollectProguardRules(context.get(), bar_layout.get(), &set));
+ ASSERT_TRUE(proguard::CollectProguardRules(context.get(), 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(
+ "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
EXPECT_THAT(actual, HasSubstr("int foo"));
EXPECT_THAT(actual, HasSubstr("int bar"));
- EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
}
TEST(ProguardRulesTest, AliasedLayoutRulesAreConditional) {
@@ -147,15 +207,15 @@ TEST(ProguardRulesTest, AliasedLayoutRulesAreConditional) {
proguard::KeepSet set = proguard::KeepSet(true);
set.AddReference({test::ParseNameOrDie("layout/bar"), {}}, layout->file.name);
- ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
+ ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
std::string actual = GetKeepSetString(set);
- EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
+ EXPECT_THAT(actual, HasSubstr(
+ "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
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) {
@@ -168,12 +228,13 @@ TEST(ProguardRulesTest, NonLayoutReferencesAreUnconditional) {
proguard::KeepSet set = proguard::KeepSet(true);
set.AddReference({test::ParseNameOrDie("style/MyStyle"), {}}, layout->file.name);
- ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
+ ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
std::string actual = GetKeepSetString(set);
EXPECT_THAT(actual, Not(HasSubstr("-if")));
- EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
+ EXPECT_THAT(actual, HasSubstr(
+ "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
}
TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) {
@@ -184,11 +245,12 @@ TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) {
layout->file.name = test::ParseNameOrDie("layout/foo");
proguard::KeepSet set;
- ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
+ ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
std::string actual = GetKeepSetString(set);
- EXPECT_THAT(actual, HasSubstr("bar_method"));
+ EXPECT_THAT(actual, HasSubstr(
+ "-keepclassmembers class * { *** bar_method(android.view.View); }"));
}
TEST(ProguardRulesTest, MenuRulesAreEmitted) {
@@ -203,14 +265,49 @@ TEST(ProguardRulesTest, MenuRulesAreEmitted) {
menu->file.name = test::ParseNameOrDie("menu/foo");
proguard::KeepSet set;
- ASSERT_TRUE(proguard::CollectProguardRules(menu.get(), &set));
+ ASSERT_TRUE(proguard::CollectProguardRules(context.get(), menu.get(), &set));
std::string actual = GetKeepSetString(set);
- EXPECT_THAT(actual, HasSubstr("on_click"));
- EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
- EXPECT_THAT(actual, HasSubstr("com.foo.Baz"));
+ EXPECT_THAT(actual, HasSubstr(
+ "-keepclassmembers class * { *** on_click(android.view.MenuItem); }"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(android.content.Context); }"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz { <init>(android.content.Context); }"));
EXPECT_THAT(actual, Not(HasSubstr("com.foo.Bat")));
}
+TEST(ProguardRulesTest, TransitionPathMotionRulesAreEmitted) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> transition = test::BuildXmlDom(R"(
+ <changeBounds>
+ <pathMotion class="com.foo.Bar"/>
+ </changeBounds>)");
+ transition->file.name = test::ParseNameOrDie("transition/foo");
+
+ proguard::KeepSet set;
+ ASSERT_TRUE(proguard::CollectProguardRules(context.get(), transition.get(), &set));
+
+ std::string actual = GetKeepSetString(set);
+
+ EXPECT_THAT(actual, HasSubstr(
+ "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
+}
+
+TEST(ProguardRulesTest, TransitionRulesAreEmitted) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> transitionSet = test::BuildXmlDom(R"(
+ <transitionSet>
+ <transition class="com.foo.Bar"/>
+ </transitionSet>)");
+ transitionSet->file.name = test::ParseNameOrDie("transition/foo");
+
+ proguard::KeepSet set;
+ ASSERT_TRUE(proguard::CollectProguardRules(context.get(), transitionSet.get(), &set));
+
+ std::string actual = GetKeepSetString(set);
+
+ EXPECT_THAT(actual, HasSubstr(
+ "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/jni/aapt2_jni.cpp b/tools/aapt2/jni/aapt2_jni.cpp
index ad5ad4c336e5..ba9646f9aeb4 100644
--- a/tools/aapt2/jni/aapt2_jni.cpp
+++ b/tools/aapt2/jni/aapt2_jni.cpp
@@ -25,15 +25,12 @@
#include "ScopedUtfChars.h"
#include "Diagnostics.h"
+#include "cmd/Compile.h"
+#include "cmd/Link.h"
#include "util/Util.h"
using android::StringPiece;
-namespace aapt {
-extern int Compile(const std::vector<StringPiece>& args, IDiagnostics* iDiagnostics);
-extern int Link(const std::vector<StringPiece>& args, IDiagnostics* iDiagnostics);
-}
-
/*
* Converts a java List<String> into C++ vector<ScopedUtfChars>.
*/
@@ -126,7 +123,7 @@ JNIEXPORT jint JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeCompile(
list_to_utfchars(env, arguments_obj);
std::vector<StringPiece> compile_args = extract_pieces(compile_args_jni);
JniDiagnostics diagnostics(env, diagnostics_obj);
- return aapt::Compile(compile_args, &diagnostics);
+ return aapt::CompileCommand(&diagnostics).Execute(compile_args, &std::cerr);
}
JNIEXPORT jint JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeLink(JNIEnv* env,
@@ -137,7 +134,7 @@ JNIEXPORT jint JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeLink(JNIEnv*
list_to_utfchars(env, arguments_obj);
std::vector<StringPiece> link_args = extract_pieces(link_args_jni);
JniDiagnostics diagnostics(env, diagnostics_obj);
- return aapt::Link(link_args, &diagnostics);
+ return aapt::LinkCommand(&diagnostics).Execute(link_args, &std::cerr);
}
JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_ping(
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index ee4e70288994..55a32c8b512c 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -282,6 +282,17 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
}
}
+ if (options_.version_code_major_default) {
+ if (options_.replace_version) {
+ el->RemoveAttribute(xml::kSchemaAndroid, "versionCodeMajor");
+ }
+ if (el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor") == nullptr) {
+ el->attributes.push_back(
+ xml::Attribute{xml::kSchemaAndroid, "versionCodeMajor",
+ options_.version_code_major_default.value()});
+ }
+ }
+
if (el->FindAttribute("", "platformBuildVersionCode") == nullptr) {
auto versionCode = el->FindAttribute(xml::kSchemaAndroid, "versionCode");
if (versionCode != nullptr) {
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index 98d06fd776d7..3ef57d0d0e42 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -52,6 +52,10 @@ struct ManifestFixerOptions {
// replace_version is set.
Maybe<std::string> version_code_default;
+ // The version code to set if 'android:versionCodeMajor' is not defined in <manifest> or if
+ // replace_version is set.
+ Maybe<std::string> version_code_major_default;
+
// The version of the framework being compiled against to set for 'android:compileSdkVersion' in
// the <manifest> tag.
Maybe<std::string> compile_sdk_version;
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 5bc004d62ff2..adea6273bc8b 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -329,6 +329,7 @@ TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) {
ManifestFixerOptions options;
options.version_name_default = std::string("Beta");
options.version_code_default = std::string("0x10000000");
+ options.version_code_major_default = std::string("0x20000000");
std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
@@ -347,136 +348,199 @@ TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) {
attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
ASSERT_THAT(attr, NotNull());
EXPECT_THAT(attr->value, StrEq("0x10000000"));
+
+ attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x20000000"));
}
TEST_F(ManifestFixerTest, DontUseDefaultVersionNameAndCode) {
-ManifestFixerOptions options;
-options.version_name_default = std::string("Beta");
-options.version_code_default = std::string("0x10000000");
+ ManifestFixerOptions options;
+ options.version_name_default = std::string("Beta");
+ options.version_code_default = std::string("0x10000000");
+ options.version_code_major_default = std::string("0x20000000");
+
+ std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android"
+ android:versionCode="0x00000001"
+ android:versionCodeMajor="0x00000002"
+ android:versionName="Alpha" />)EOF",
+ options);
+ ASSERT_THAT(doc, NotNull());
+
+ xml::Element* manifest_el = doc->root.get();
+ ASSERT_THAT(manifest_el, NotNull());
+
+ xml::Attribute* attr =
+ manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("Alpha"));
+
+ attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x00000001"));
+
+ attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x00000002"));
+}
-std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+TEST_F(ManifestFixerTest, ReplaceVersionNameAndCode) {
+ ManifestFixerOptions options;
+ options.replace_version = true;
+ options.version_name_default = std::string("Beta");
+ options.version_code_default = std::string("0x10000000");
+ options.version_code_major_default = std::string("0x20000000");
+
+ std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android"
- android:versionCode="0x20000000"
+ android:versionCode="0x00000001"
+ android:versionCodeMajor="0x00000002"
android:versionName="Alpha" />)EOF",
- options);
-ASSERT_THAT(doc, NotNull());
+ options);
+ ASSERT_THAT(doc, NotNull());
-xml::Element* manifest_el = doc->root.get();
-ASSERT_THAT(manifest_el, NotNull());
+ xml::Element* manifest_el = doc->root.get();
+ ASSERT_THAT(manifest_el, NotNull());
-xml::Attribute* attr =
- manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
-ASSERT_THAT(attr, NotNull());
-EXPECT_THAT(attr->value, StrEq("Alpha"));
+ xml::Attribute* attr =
+ manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("Beta"));
-attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
-ASSERT_THAT(attr, NotNull());
-EXPECT_THAT(attr->value, StrEq("0x20000000"));
+ attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x10000000"));
+
+ attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x20000000"));
}
-TEST_F(ManifestFixerTest, ReplaceVersionNameAndCode) {
-ManifestFixerOptions options;
-options.replace_version = true;
-options.version_name_default = std::string("Beta");
-options.version_code_default = std::string("0x10000000");
+TEST_F(ManifestFixerTest, ReplaceVersionName) {
+ ManifestFixerOptions options;
+ options.replace_version = true;
+ options.version_name_default = std::string("Beta");
+
-std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android"
- android:versionCode="0x20000000"
+ android:versionCode="0x00000001"
+ android:versionCodeMajor="0x00000002"
android:versionName="Alpha" />)EOF",
- options);
-ASSERT_THAT(doc, NotNull());
+ options);
+ ASSERT_THAT(doc, NotNull());
-xml::Element* manifest_el = doc->root.get();
-ASSERT_THAT(manifest_el, NotNull());
+ xml::Element* manifest_el = doc->root.get();
+ ASSERT_THAT(manifest_el, NotNull());
-xml::Attribute* attr =
- manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
-ASSERT_THAT(attr, NotNull());
-EXPECT_THAT(attr->value, StrEq("Beta"));
+ xml::Attribute* attr =
+ manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("Beta"));
-attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
-ASSERT_THAT(attr, NotNull());
-EXPECT_THAT(attr->value, StrEq("0x10000000"));
+ attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x00000001"));
+
+ attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x00000002"));
}
-TEST_F(ManifestFixerTest, ReplaceVersionName) {
-ManifestFixerOptions options;
-options.replace_version = true;
-options.version_name_default = std::string("Beta");
+TEST_F(ManifestFixerTest, ReplaceVersionCode) {
+ ManifestFixerOptions options;
+ options.replace_version = true;
+ options.version_code_default = std::string("0x10000000");
-std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android"
- android:versionCode="0x20000000"
- android:versionName="Alpha" />)EOF",
- options);
-ASSERT_THAT(doc, NotNull());
+ std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android"
+ android:versionCode="0x00000001"
+ android:versionCodeMajor="0x00000002"
+ android:versionName="Alpha" />)EOF",
+ options);
+ ASSERT_THAT(doc, NotNull());
+
+ xml::Element* manifest_el = doc->root.get();
+ ASSERT_THAT(manifest_el, NotNull());
-xml::Element* manifest_el = doc->root.get();
-ASSERT_THAT(manifest_el, NotNull());
+ xml::Attribute* attr =
+ manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("Alpha"));
-xml::Attribute* attr =
- manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
-ASSERT_THAT(attr, NotNull());
-EXPECT_THAT(attr->value, StrEq("Beta"));
+ attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x10000000"));
-attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
-ASSERT_THAT(attr, NotNull());
-EXPECT_THAT(attr->value, StrEq("0x20000000"));
+ attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x00000002"));
}
-TEST_F(ManifestFixerTest, ReplaceVersionCode) {
-ManifestFixerOptions options;
-options.replace_version = true;
-options.version_code_default = std::string("0x10000000");
+TEST_F(ManifestFixerTest, ReplaceVersionCodeMajor) {
+ ManifestFixerOptions options;
+ options.replace_version = true;
+ options.version_code_major_default = std::string("0x20000000");
-std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android"
- android:versionCode="0x20000000"
- android:versionName="Alpha" />)EOF",
- options);
-ASSERT_THAT(doc, NotNull());
+ package="android"
+ android:versionCode="0x00000001"
+ android:versionCodeMajor="0x00000002"
+ android:versionName="Alpha" />)EOF",
+ options);
+ ASSERT_THAT(doc, NotNull());
-xml::Element* manifest_el = doc->root.get();
-ASSERT_THAT(manifest_el, NotNull());
+ xml::Element* manifest_el = doc->root.get();
+ ASSERT_THAT(manifest_el, NotNull());
-xml::Attribute* attr =
- manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
-ASSERT_THAT(attr, NotNull());
-EXPECT_THAT(attr->value, StrEq("Alpha"));
+ xml::Attribute* attr =
+ manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("Alpha"));
-attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
-ASSERT_THAT(attr, NotNull());
-EXPECT_THAT(attr->value, StrEq("0x10000000"));
+ attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x00000001"));
+
+ attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x20000000"));
}
TEST_F(ManifestFixerTest, DontReplaceVersionNameOrCode) {
-ManifestFixerOptions options;
-options.replace_version = true;
+ ManifestFixerOptions options;
+ options.replace_version = true;
-std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android"
- android:versionCode="0x20000000"
- android:versionName="Alpha" />)EOF",
- options);
-ASSERT_THAT(doc, NotNull());
+ std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android"
+ android:versionCode="0x00000001"
+ android:versionCodeMajor="0x00000002"
+ android:versionName="Alpha" />)EOF",
+ options);
+ ASSERT_THAT(doc, NotNull());
+
+ xml::Element* manifest_el = doc->root.get();
+ ASSERT_THAT(manifest_el, NotNull());
-xml::Element* manifest_el = doc->root.get();
-ASSERT_THAT(manifest_el, NotNull());
+ xml::Attribute* attr =
+ manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("Alpha"));
-xml::Attribute* attr =
- manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
-ASSERT_THAT(attr, NotNull());
-EXPECT_THAT(attr->value, StrEq("Alpha"));
+ attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x00000001"));
-attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
-ASSERT_THAT(attr, NotNull());
-EXPECT_THAT(attr->value, StrEq("0x20000000"));
+ attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor");
+ ASSERT_THAT(attr, NotNull());
+ EXPECT_THAT(attr->value, StrEq("0x00000002"));
}
TEST_F(ManifestFixerTest, EnsureManifestAttributesAreTyped) {
diff --git a/tools/aapt2/link/NoDefaultResourceRemover.cpp b/tools/aapt2/link/NoDefaultResourceRemover.cpp
index cfb4b26aba02..5173b8541943 100644
--- a/tools/aapt2/link/NoDefaultResourceRemover.cpp
+++ b/tools/aapt2/link/NoDefaultResourceRemover.cpp
@@ -22,17 +22,7 @@
namespace aapt {
-static bool IsDefaultConfigRequired(const ConfigDescription& config) {
- // We don't want to be overzealous with resource removal, so have strict requirements.
- // If a resource defines a value for a locale-only configuration, the default configuration is
- // required.
- if (ConfigDescription::DefaultConfig().diff(config) == ConfigDescription::CONFIG_LOCALE) {
- return true;
- }
- return false;
-}
-
-static bool KeepResource(const std::unique_ptr<ResourceEntry>& entry) {
+static bool KeepResource(const std::unique_ptr<ResourceEntry>& entry, int minSdk) {
if (entry->visibility.level == Visibility::Level::kPublic) {
// Removing a public API without the developer knowing is bad, so just leave this here for now.
return true;
@@ -44,22 +34,44 @@ static bool KeepResource(const std::unique_ptr<ResourceEntry>& entry) {
}
// There is no default value defined, check if removal is required.
+ bool defaultRequired = false;
for (const auto& config_value : entry->values) {
- if (IsDefaultConfigRequired(config_value->config)) {
- return false;
+ const int config = ConfigDescription::DefaultConfig().diff(config_value->config);
+ // If a resource defines a value for a locale-only configuration, the default configuration is
+ // required.
+ if (config == ConfigDescription::CONFIG_LOCALE) {
+ defaultRequired = true;
+ }
+ // If a resource defines a version-only config, the config value can be used as a default if
+ // the version is at most the minimum sdk version
+ else if (config == ConfigDescription::CONFIG_VERSION
+ && config_value->config.sdkVersion <= minSdk) {
+ return true;
+ }
+ // If a resource defines a value for a density only configuration, then that value could be used
+ // as a default and the entry should not be removed
+ else if (config == ConfigDescription::CONFIG_DENSITY
+ || (config == (ConfigDescription::CONFIG_DENSITY | ConfigDescription::CONFIG_VERSION)
+ && config_value->config.sdkVersion <= minSdk)) {
+ return true;
}
}
- return true;
+
+ return !defaultRequired;
}
bool NoDefaultResourceRemover::Consume(IAaptContext* context, ResourceTable* table) {
- const ConfigDescription default_config = ConfigDescription::DefaultConfig();
for (auto& pkg : table->packages) {
for (auto& type : pkg->types) {
+ // Gather the entries without defaults that must be removed
+ const int minSdk = context->GetMinSdkVersion();
const auto end_iter = type->entries.end();
- const auto new_end_iter =
- std::stable_partition(type->entries.begin(), end_iter, KeepResource);
- for (auto iter = new_end_iter; iter != end_iter; ++iter) {
+ const auto remove_iter = std::stable_partition(type->entries.begin(), end_iter,
+ [&minSdk](const std::unique_ptr<ResourceEntry>& entry) -> bool {
+ return KeepResource(entry, minSdk);
+ });
+
+ for (auto iter = remove_iter; iter != end_iter; ++iter) {
const ResourceName name(pkg->name, type->type, (*iter)->name);
IDiagnostics* diag = context->GetDiagnostics();
diag->Warn(DiagMessage() << "removing resource " << name
@@ -74,7 +86,7 @@ bool NoDefaultResourceRemover::Consume(IAaptContext* context, ResourceTable* tab
}
}
- type->entries.erase(new_end_iter, type->entries.end());
+ type->entries.erase(remove_iter, end_iter);
}
}
return true;
diff --git a/tools/aapt2/link/NoDefaultResourceRemover_test.cpp b/tools/aapt2/link/NoDefaultResourceRemover_test.cpp
index 943709a2af12..d129c9ac8db7 100644
--- a/tools/aapt2/link/NoDefaultResourceRemover_test.cpp
+++ b/tools/aapt2/link/NoDefaultResourceRemover_test.cpp
@@ -46,4 +46,64 @@ TEST(NoDefaultResourceRemoverTest, RemoveEntryWithNoDefaultAndOnlyLocales) {
EXPECT_TRUE(table->FindResource(test::ParseNameOrDie("android:string/baz")));
}
+TEST(NoDefaultResourceRemoverTest, KeepEntryWithLocalesAndDensities) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().SetMinSdkVersion(26).Build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddSimple("android:drawable/keep1", test::ParseConfigOrDie("mdpi")) // v4
+ .AddSimple("android:drawable/keep1", test::ParseConfigOrDie("en-rGB"))
+ .AddSimple("android:drawable/keep1", test::ParseConfigOrDie("fr-rFR"))
+ .AddSimple("android:drawable/keep2", test::ParseConfigOrDie("fr-rFR"))
+ .AddSimple("android:drawable/keep2", test::ParseConfigOrDie("en-rGB"))
+ .AddSimple("android:drawable/keep2", test::ParseConfigOrDie("xxxhdpi")) // v4
+ .AddSimple("android:drawable/remove1", test::ParseConfigOrDie("fr-rFR"))
+ .AddSimple("android:drawable/remove1", test::ParseConfigOrDie("en-rGB"))
+ .AddSimple("android:drawable/remove1", test::ParseConfigOrDie("w600dp-xhdpi")) // v13
+ .Build();
+
+ NoDefaultResourceRemover remover;
+ ASSERT_TRUE(remover.Consume(context.get(), table.get()));
+
+ EXPECT_TRUE(table->FindResource(test::ParseNameOrDie("android:drawable/keep1")));
+ EXPECT_TRUE(table->FindResource(test::ParseNameOrDie("android:drawable/keep2")));
+ EXPECT_FALSE(table->FindResource(test::ParseNameOrDie("android:drawable/remove1")));
+}
+
+TEST(NoDefaultResourceRemoverTest, RemoveEntryWithLocalesAndDensitiesLowVersion) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().SetMinSdkVersion(3).Build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddSimple("android:drawable/remove1", test::ParseConfigOrDie("mdpi")) // v4
+ .AddSimple("android:drawable/remove1", test::ParseConfigOrDie("en-rGB"))
+ .AddSimple("android:drawable/remove1", test::ParseConfigOrDie("fr-rFR"))
+ .Build();
+
+ NoDefaultResourceRemover remover;
+ ASSERT_TRUE(remover.Consume(context.get(), table.get()));
+
+ EXPECT_FALSE(table->FindResource(test::ParseNameOrDie("android:drawable/remove1")));
+}
+
+TEST(NoDefaultResourceRemoverTest, KeepEntryWithVersion) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().SetMinSdkVersion(8).Build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("android", 0x01)
+ .AddSimple("android:drawable/keep1", test::ParseConfigOrDie("v8"))
+ .AddSimple("android:drawable/keep1", test::ParseConfigOrDie("en-rGB"))
+ .AddSimple("android:drawable/keep1", test::ParseConfigOrDie("fr-rFR"))
+ .AddSimple("android:drawable/remove1", test::ParseConfigOrDie("v9"))
+ .AddSimple("android:drawable/remove1", test::ParseConfigOrDie("en-rGB"))
+ .AddSimple("android:drawable/remove1", test::ParseConfigOrDie("fr-rFR"))
+ .Build();
+
+ NoDefaultResourceRemover remover;
+ ASSERT_TRUE(remover.Consume(context.get(), table.get()));
+
+ EXPECT_TRUE(table->FindResource(test::ParseNameOrDie("android:drawable/keep1")));
+ EXPECT_FALSE(table->FindResource(test::ParseNameOrDie("android:drawable/remove1")));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index e819f51a5634..afb8ae097449 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -102,7 +102,17 @@ static bool MergeType(IAaptContext* context, const Source& src, ResourceTableTyp
}
static bool MergeEntry(IAaptContext* context, const Source& src, bool overlay,
- ResourceEntry* dst_entry, ResourceEntry* src_entry) {
+ ResourceEntry* dst_entry, ResourceEntry* src_entry,
+ bool strict_visibility) {
+ if (strict_visibility
+ && dst_entry->visibility.level != Visibility::Level::kUndefined
+ && src_entry->visibility.level != dst_entry->visibility.level) {
+ context->GetDiagnostics()->Error(
+ DiagMessage(src) << "cannot merge resource '" << dst_entry->name << "' with conflicting visibilities: "
+ << "public and private");
+ return false;
+ }
+
// Copy over the strongest visibility.
if (src_entry->visibility.level > dst_entry->visibility.level) {
// Only copy the ID if the source is public, or else the ID is meaningless.
@@ -234,7 +244,7 @@ bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table,
continue;
}
- if (!MergeEntry(context_, src, overlay, dst_entry, src_entry.get())) {
+ if (!MergeEntry(context_, src, overlay, dst_entry, src_entry.get(), options_.strict_visibility)) {
error = true;
continue;
}
@@ -271,8 +281,17 @@ bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table,
dst_config_value->value = std::move(new_file_ref);
} else {
+ Maybe<std::string> original_comment = (dst_config_value->value)
+ ? dst_config_value->value->GetComment() : Maybe<std::string>();
+
dst_config_value->value = std::unique_ptr<Value>(
src_config_value->value->Clone(&master_table_->string_pool));
+
+ // Keep the comment from the original resource and ignore all comments from overlaying
+ // resources
+ if (overlay && original_comment) {
+ dst_config_value->value->SetComment(original_comment.value());
+ }
}
}
}
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 47e23dded26f..24c5e1329244 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -35,6 +35,8 @@ namespace aapt {
struct TableMergerOptions {
// If true, resources in overlays can be added without previously having existed.
bool auto_add_overlay = false;
+ // If true, resource overlays with conflicting visibility are not allowed.
+ bool strict_visibility = false;
};
// TableMerger takes resource tables and merges all packages within the tables that have the same
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 34461c6b467d..79a734bffabd 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -178,6 +178,49 @@ TEST_F(TableMergerTest, OverrideResourceWithOverlay) {
Pointee(Field(&BinaryPrimitive::value, Field(&android::Res_value::data, Eq(0u)))));
}
+TEST_F(TableMergerTest, DoNotOverrideResourceComment) {
+ std::unique_ptr<Value> foo_original = ResourceUtils::TryParseBool("true");
+ foo_original->SetComment(android::StringPiece("Original foo comment"));
+ std::unique_ptr<Value> bar_original = ResourceUtils::TryParseBool("true");
+
+ std::unique_ptr<Value> foo_overlay = ResourceUtils::TryParseBool("false");
+ foo_overlay->SetComment(android::StringPiece("Overlay foo comment"));
+ std::unique_ptr<Value> bar_overlay = ResourceUtils::TryParseBool("false");
+ bar_overlay->SetComment(android::StringPiece("Overlay bar comment"));
+ std::unique_ptr<Value> baz_overlay = ResourceUtils::TryParseBool("false");
+ baz_overlay->SetComment(android::StringPiece("Overlay baz comment"));
+
+ std::unique_ptr<ResourceTable> base =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x00)
+ .AddValue("bool/foo", std::move(foo_original))
+ .AddValue("bool/bar", std::move(bar_original))
+ .Build();
+
+ std::unique_ptr<ResourceTable> overlay =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x00)
+ .AddValue("bool/foo", std::move(foo_overlay))
+ .AddValue("bool/bar", std::move(bar_overlay))
+ .AddValue("bool/baz", std::move(baz_overlay))
+ .Build();
+
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = true;
+ TableMerger merger(context_.get(), &final_table, options);
+
+ 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");
+ EXPECT_THAT(foo, Pointee(Property(&BinaryPrimitive::GetComment, StrEq("Original foo comment"))));
+ BinaryPrimitive* bar = test::GetValue<BinaryPrimitive>(&final_table, "com.app.a:bool/bar");
+ EXPECT_THAT(bar, Pointee(Property(&BinaryPrimitive::GetComment, StrEq(""))));
+ BinaryPrimitive* baz = test::GetValue<BinaryPrimitive>(&final_table, "com.app.a:bool/baz");
+ EXPECT_THAT(baz, Pointee(Property(&BinaryPrimitive::GetComment, StrEq("Overlay baz comment"))));
+}
+
TEST_F(TableMergerTest, OverrideSameResourceIdsWithOverlay) {
std::unique_ptr<ResourceTable> base =
test::ResourceTableBuilder()
@@ -241,6 +284,37 @@ TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) {
ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/));
}
+TEST_F(TableMergerTest, FailConflictingVisibility) {
+ std::unique_ptr<ResourceTable> base =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
+ .Build();
+ std::unique_ptr<ResourceTable> overlay =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPrivate)
+ .Build();
+
+ // It should fail if the "--strict-visibility" flag is set.
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ options.strict_visibility = true;
+ TableMerger merger(context_.get(), &final_table, options);
+
+ ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+ ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/));
+
+ // But it should still pass if the flag is not set.
+ ResourceTable final_table2;
+ options.strict_visibility = false;
+ TableMerger merger2(context_.get(), &final_table2, options);
+
+ ASSERT_TRUE(merger2.Merge({}, base.get(), false /*overlay*/));
+ ASSERT_TRUE(merger2.Merge({}, overlay.get(), true /*overlay*/));
+}
+
TEST_F(TableMergerTest, MergeAddResourceFromOverlay) {
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder().SetPackageId("", 0x7f).Build();
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 588b3316e6fa..e92c121272eb 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -26,6 +26,7 @@
#include "ResourceUtils.h"
#include "ValueVisitor.h"
#include "configuration/ConfigurationParser.h"
+#include "cmd/Util.h"
#include "filter/AbiFilter.h"
#include "filter/Filter.h"
#include "format/Archive.h"
@@ -265,7 +266,7 @@ bool MultiApkGenerator::UpdateManifest(const OutputArtifact& artifact,
// Make sure the first element is <manifest> with package attribute.
xml::Element* manifest_el = manifest->root.get();
- if (manifest_el == nullptr) {
+ if (!manifest_el) {
return false;
}
@@ -274,21 +275,35 @@ bool MultiApkGenerator::UpdateManifest(const OutputArtifact& artifact,
return false;
}
- // Update the versionCode attribute.
- xml::Attribute* versionCode = manifest_el->FindAttribute(kSchemaAndroid, "versionCode");
- if (versionCode == nullptr) {
+ // Retrieve the versionCode attribute.
+ auto version_code = manifest_el->FindAttribute(kSchemaAndroid, "versionCode");
+ if (!version_code) {
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) {
+ auto version_code_value = ValueCast<BinaryPrimitive>(version_code->compiled_value.get());
+ if (!version_code_value) {
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));
+ // Retrieve the versionCodeMajor attribute.
+ auto version_code_major = manifest_el->FindAttribute(kSchemaAndroid, "versionCodeMajor");
+ BinaryPrimitive* version_code_major_value = nullptr;
+ if (version_code_major) {
+ version_code_major_value = ValueCast<BinaryPrimitive>(version_code_major->compiled_value.get());
+ if (!version_code_major_value) {
+ diag->Error(DiagMessage(manifest->file.source) << "versionCodeMajor is invalid");
+ return false;
+ }
+ }
+
+ // Calculate and set the updated version code
+ uint64_t major = (version_code_major_value)
+ ? ((uint64_t) version_code_major_value->value.data) << 32 : 0;
+ uint64_t new_version = (major | version_code_value->value.data) + artifact.version;
+ SetLongVersionCode(manifest_el, new_version);
// Check to see if the minSdkVersion needs to be updated.
if (artifact.android_sdk) {
diff --git a/tools/aapt2/optimize/ResourceFilter.cpp b/tools/aapt2/optimize/ResourceFilter.cpp
new file mode 100644
index 000000000000..250b65197a7d
--- /dev/null
+++ b/tools/aapt2/optimize/ResourceFilter.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "optimize/ResourceFilter.h"
+
+#include "ResourceTable.h"
+
+namespace aapt {
+
+ResourceFilter::ResourceFilter(const std::unordered_set<ResourceName>& blacklist)
+ : blacklist_(blacklist) {
+}
+
+bool ResourceFilter::Consume(IAaptContext* context, ResourceTable* table) {
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto it = type->entries.begin(); it != type->entries.end(); ) {
+ ResourceName resource = ResourceName({}, type->type, (*it)->name);
+ if (blacklist_.find(resource) != blacklist_.end()) {
+ it = type->entries.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/optimize/ResourceFilter.h b/tools/aapt2/optimize/ResourceFilter.h
new file mode 100644
index 000000000000..d4baf654b0ff
--- /dev/null
+++ b/tools/aapt2/optimize/ResourceFilter.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_OPTIMIZE_RESOURCEFILTER_H
+#define AAPT_OPTIMIZE_RESOURCEFILTER_H
+
+#include "android-base/macros.h"
+
+#include "process/IResourceTableConsumer.h"
+
+#include <unordered_set>
+
+namespace aapt {
+
+// Removes non-whitelisted entries from resource table.
+class ResourceFilter : public IResourceTableConsumer {
+ public:
+ explicit ResourceFilter(const std::unordered_set<ResourceName>& blacklist);
+
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceFilter);
+ std::unordered_set<ResourceName> blacklist_;
+};
+
+} // namespace aapt
+
+#endif // AAPT_OPTIMIZE_RESOURCEFILTER_H
diff --git a/tools/aapt2/optimize/ResourceFilter_test.cpp b/tools/aapt2/optimize/ResourceFilter_test.cpp
new file mode 100644
index 000000000000..800b2bfd0403
--- /dev/null
+++ b/tools/aapt2/optimize/ResourceFilter_test.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "optimize/ResourceFilter.h"
+
+#include "ResourceTable.h"
+#include "test/Test.h"
+
+using ::aapt::test::HasValue;
+using ::testing::Not;
+
+namespace aapt {
+
+TEST(ResourceFilterTest, SomeValuesAreFilteredOut) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ const ConfigDescription default_config = {};
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/notblacklisted", ResourceId{}, default_config, "value")
+ .AddString("android:string/blacklisted", ResourceId{}, default_config, "value")
+ .AddString("android:string/notblacklisted2", ResourceId{}, default_config, "value")
+ .AddString("android:string/blacklisted2", ResourceId{}, default_config, "value")
+ .Build();
+
+ std::unordered_set<ResourceName> blacklist = {
+ ResourceName({}, ResourceType::kString, "blacklisted"),
+ ResourceName({}, ResourceType::kString, "blacklisted2"),
+ };
+
+ ASSERT_TRUE(ResourceFilter(blacklist).Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/notblacklisted", default_config));
+ EXPECT_THAT(table, HasValue("android:string/notblacklisted2", default_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/blacklisted", default_config)));
+ EXPECT_THAT(table, Not(HasValue("android:string/blacklisted2", default_config)));
+}
+
+TEST(ResourceFilterTest, TypeIsCheckedBeforeFiltering) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ const ConfigDescription default_config = {};
+
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddString("android:string/notblacklisted", ResourceId{}, default_config, "value")
+ .AddString("android:string/blacklisted", ResourceId{}, default_config, "value")
+ .AddString("android:drawable/notblacklisted", ResourceId{}, default_config, "value")
+ .AddString("android:drawable/blacklisted", ResourceId{}, default_config, "value")
+ .Build();
+
+ std::unordered_set<ResourceName> blacklist = {
+ ResourceName({}, ResourceType::kString, "blacklisted"),
+ };
+
+ ASSERT_TRUE(ResourceFilter(blacklist).Consume(context.get(), table.get()));
+ EXPECT_THAT(table, HasValue("android:string/notblacklisted", default_config));
+ EXPECT_THAT(table, HasValue("android:drawable/blacklisted", default_config));
+ EXPECT_THAT(table, HasValue("android:drawable/notblacklisted", default_config));
+ EXPECT_THAT(table, Not(HasValue("android:string/blacklisted", default_config)));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index d1c9ca1644d9..9bef54e590c9 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -297,6 +297,53 @@ bool VerifyJavaStringFormat(const StringPiece& str) {
return true;
}
+std::string Utf8ToModifiedUtf8(const std::string& utf8) {
+ // Java uses Modified UTF-8 which only supports the 1, 2, and 3 byte formats of UTF-8. To encode
+ // 4 byte UTF-8 codepoints, Modified UTF-8 allows the use of surrogate pairs in the same format
+ // of CESU-8 surrogate pairs. Calculate the size of the utf8 string with all 4 byte UTF-8
+ // codepoints replaced with 2 3 byte surrogate pairs
+ size_t modified_size = 0;
+ const size_t size = utf8.size();
+ for (size_t i = 0; i < size; i++) {
+ if (((uint8_t) utf8[i] >> 4) == 0xF) {
+ modified_size += 6;
+ i += 3;
+ } else {
+ modified_size++;
+ }
+ }
+
+ // Early out if no 4 byte codepoints are found
+ if (size == modified_size) {
+ return utf8;
+ }
+
+ std::string output;
+ output.reserve(modified_size);
+ for (size_t i = 0; i < size; i++) {
+ if (((uint8_t) utf8[i] >> 4) == 0xF) {
+ auto codepoint = (char32_t) utf32_from_utf8_at(utf8.data(), size, i, nullptr);
+
+ // Calculate the high and low surrogates as UTF-16 would
+ char32_t high = ((codepoint - 0x10000) / 0x400) + 0xD800;
+ char32_t low = ((codepoint - 0x10000) % 0x400) + 0xDC00;
+
+ // Encode each surrogate in UTF-8
+ output.push_back((char) (0xE4 | ((high >> 12) & 0xF)));
+ output.push_back((char) (0x80 | ((high >> 6) & 0x3F)));
+ output.push_back((char) (0x80 | (high & 0x3F)));
+ output.push_back((char) (0xE4 | ((low >> 12) & 0xF)));
+ output.push_back((char) (0x80 | ((low >> 6) & 0x3F)));
+ output.push_back((char) (0x80 | (low & 0x3F)));
+ i += 3;
+ } else {
+ output.push_back(utf8[i]);
+ }
+ }
+
+ return output;
+}
+
std::u16string Utf8ToUtf16(const StringPiece& utf8) {
ssize_t utf16_length = utf8_to_utf16_length(
reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 0eb35d18c06e..36b733376e6f 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -197,6 +197,9 @@ inline StringBuilder::operator bool() const {
return error_.empty();
}
+// Converts a UTF8 string into Modified UTF8
+std::string Utf8ToModifiedUtf8(const std::string& utf8);
+
// Converts a UTF8 string to a UTF16 string.
std::u16string Utf8ToUtf16(const android::StringPiece& utf8);
std::string Utf16ToUtf8(const android::StringPiece16& utf16);
diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
index 402e5a459f4e..a023494ad8f7 100644
--- a/tools/aapt2/xml/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -38,6 +38,7 @@ XmlPullParser::XmlPullParser(InputStream* in) : in_(in), empty_(), depth_(0) {
EndNamespaceHandler);
XML_SetCharacterDataHandler(parser_, CharacterDataHandler);
XML_SetCommentHandler(parser_, CommentDataHandler);
+ XML_SetCdataSectionHandler(parser_, StartCdataSectionHandler, EndCdataSectionHandler);
event_queue_.push(EventData{Event::kStartDocument, 0, depth_++});
}
@@ -287,6 +288,22 @@ void XMLCALL XmlPullParser::CommentDataHandler(void* user_data,
parser->depth_, comment});
}
+void XMLCALL XmlPullParser::StartCdataSectionHandler(void* user_data) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data);
+
+ parser->event_queue_.push(EventData{Event::kCdataStart,
+ XML_GetCurrentLineNumber(parser->parser_),
+ parser->depth_ });
+}
+
+void XMLCALL XmlPullParser::EndCdataSectionHandler(void* user_data) {
+ XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(user_data);
+
+ parser->event_queue_.push(EventData{Event::kCdataEnd,
+ XML_GetCurrentLineNumber(parser->parser_),
+ parser->depth_ });
+}
+
Maybe<StringPiece> FindAttribute(const XmlPullParser* parser,
const StringPiece& name) {
auto iter = parser->FindAttribute("", name);
diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index 63db66f0b2b7..6ebaa285745b 100644
--- a/tools/aapt2/xml/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -52,6 +52,8 @@ class XmlPullParser : public IPackageDeclStack {
kEndElement,
kText,
kComment,
+ kCdataStart,
+ kCdataEnd,
};
/**
@@ -159,6 +161,8 @@ class XmlPullParser : public IPackageDeclStack {
static void XMLCALL EndElementHandler(void* user_data, const char* name);
static void XMLCALL EndNamespaceHandler(void* user_data, const char* prefix);
static void XMLCALL CommentDataHandler(void* user_data, const char* comment);
+ static void XMLCALL StartCdataSectionHandler(void* user_data);
+ static void XMLCALL EndCdataSectionHandler(void* user_data);
struct EventData {
Event event;
@@ -223,6 +227,10 @@ inline ::std::ostream& operator<<(::std::ostream& out,
return out << "Text";
case XmlPullParser::Event::kComment:
return out << "Comment";
+ case XmlPullParser::Event::kCdataStart:
+ return out << "CdataStart";
+ case XmlPullParser::Event::kCdataEnd:
+ return out << "CdataEnd";
}
return out;
}
@@ -240,6 +248,8 @@ inline bool XmlPullParser::NextChildNode(XmlPullParser* parser, size_t start_dep
case Event::kText:
case Event::kComment:
case Event::kStartElement:
+ case Event::kCdataStart:
+ case Event::kCdataEnd:
return true;
default:
break;
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 70a47cf42755..018e9c9d5f57 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -69,12 +69,19 @@ class Field():
self.raw = raw.strip(" {;")
self.blame = blame
+ # drop generics for now; may need multiple passes
+ raw = re.sub("<[^<]+?>", "", raw)
+ raw = re.sub("<[^<]+?>", "", raw)
+
raw = raw.split()
self.split = list(raw)
for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
while r in raw: raw.remove(r)
+ # ignore annotations for now
+ raw = [ r for r in raw if not r.startswith("@") ]
+
self.typ = raw[0]
self.name = raw[1].strip(";")
if len(raw) >= 4 and raw[2] == "=":
@@ -97,25 +104,39 @@ class Method():
self.raw = raw.strip(" {;")
self.blame = blame
- # drop generics for now
- raw = re.sub("<.+?>", "", raw)
+ # drop generics for now; may need multiple passes
+ raw = re.sub("<[^<]+?>", "", raw)
+ raw = re.sub("<[^<]+?>", "", raw)
+
+ # handle each clause differently
+ raw_prefix, raw_args, _, raw_throws = re.match(r"(.*?)\((.*?)\)( throws )?(.*?);$", raw).groups()
- raw = re.split("[\s(),;]+", raw)
+ # parse prefixes
+ raw = re.split("[\s]+", raw_prefix)
for r in ["", ";"]:
while r in raw: raw.remove(r)
self.split = list(raw)
- for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default"]:
+ for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default", "operator"]:
while r in raw: raw.remove(r)
self.typ = raw[0]
self.name = raw[1]
+
+ # parse args
self.args = []
+ for arg in re.split(",\s*", raw_args):
+ arg = re.split("\s", arg)
+ # ignore annotations for now
+ arg = [ a for a in arg if not a.startswith("@") ]
+ if len(arg[0]) > 0:
+ self.args.append(arg[0])
+
+ # parse throws
self.throws = []
- target = self.args
- for r in raw[2:]:
- if r == "throws": target = self.throws
- else: target.append(r)
+ for throw in re.split(",\s*", raw_throws):
+ self.throws.append(throw)
+
self.ident = ident(self.raw)
def __hash__(self):
@@ -135,12 +156,18 @@ class Class():
self.fields = []
self.methods = []
+ # drop generics for now; may need multiple passes
+ raw = re.sub("<[^<]+?>", "", raw)
+ raw = re.sub("<[^<]+?>", "", raw)
+
raw = raw.split()
self.split = list(raw)
if "class" in raw:
self.fullname = raw[raw.index("class")+1]
elif "interface" in raw:
self.fullname = raw[raw.index("interface")+1]
+ elif "@interface" in raw:
+ self.fullname = raw[raw.index("@interface")+1]
else:
raise ValueError("Funky class type %s" % (self.raw))
@@ -1334,18 +1361,25 @@ def verify_clone(clazz):
error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
+def is_interesting(clazz):
+ """Test if given class is interesting from an Android PoV."""
+
+ if clazz.pkg.name.startswith("java"): return False
+ if clazz.pkg.name.startswith("junit"): return False
+ if clazz.pkg.name.startswith("org.apache"): return False
+ if clazz.pkg.name.startswith("org.xml"): return False
+ if clazz.pkg.name.startswith("org.json"): return False
+ if clazz.pkg.name.startswith("org.w3c"): return False
+ if clazz.pkg.name.startswith("android.icu."): return False
+ return True
+
+
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
- if clazz.pkg.name.startswith("org.xml"): return
- if clazz.pkg.name.startswith("org.json"): return
- if clazz.pkg.name.startswith("org.w3c"): return
- if clazz.pkg.name.startswith("android.icu."): return
+ if not is_interesting(clazz): return
verify_constants(clazz)
verify_enums(clazz)
@@ -1479,6 +1513,7 @@ def show_deprecations_at_birth(cur, prev):
# Remove all existing things so we're left with new
for prev_clazz in prev.values():
cur_clazz = cur[prev_clazz.fullname]
+ if not is_interesting(cur_clazz): continue
sigs = { i.ident: i for i in prev_clazz.ctors }
cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
@@ -1506,6 +1541,38 @@ def show_deprecations_at_birth(cur, prev):
print
+def show_stats(cur, prev):
+ """Show API stats."""
+
+ stats = collections.defaultdict(int)
+ for cur_clazz in cur.values():
+ if not is_interesting(cur_clazz): continue
+
+ if cur_clazz.fullname not in prev:
+ stats['new_classes'] += 1
+ stats['new_ctors'] += len(cur_clazz.ctors)
+ stats['new_methods'] += len(cur_clazz.methods)
+ stats['new_fields'] += len(cur_clazz.fields)
+ else:
+ prev_clazz = prev[cur_clazz.fullname]
+
+ sigs = { i.ident: i for i in prev_clazz.ctors }
+ ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
+ sigs = { i.ident: i for i in prev_clazz.methods }
+ methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
+ sigs = { i.ident: i for i in prev_clazz.fields }
+ fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
+
+ if ctors + methods + fields > 0:
+ stats['extend_classes'] += 1
+ stats['extend_ctors'] += ctors
+ stats['extend_methods'] += methods
+ stats['extend_fields'] += fields
+
+ print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
+ print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
+
+
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Enforces common Android public API design \
patterns. It ignores lint messages from a previous API level, if provided.")
@@ -1520,6 +1587,8 @@ if __name__ == "__main__":
help="Show API changes noticed")
parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
help="Show API deprecations at birth")
+ parser.add_argument("--show-stats", action='store_const', const=True,
+ help="Show API stats")
args = vars(parser.parse_args())
if args['no_color']:
@@ -1539,6 +1608,14 @@ if __name__ == "__main__":
show_deprecations_at_birth(cur, prev)
sys.exit()
+ if args['show_stats']:
+ with current_file as f:
+ cur = _parse_stream(f)
+ with previous_file as f:
+ prev = _parse_stream(f)
+ show_stats(cur, prev)
+ sys.exit()
+
with current_file as f:
cur_fail, cur_noticed = examine_stream(f)
if not previous_file is None:
diff --git a/tools/apilint/apilint_stats.sh b/tools/apilint/apilint_stats.sh
new file mode 100755
index 000000000000..052d9a5265fe
--- /dev/null
+++ b/tools/apilint/apilint_stats.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+API=28
+while [ $API -gt 14 ]; do
+ echo "# Changes in API $((API))"
+ python tools/apilint/apilint.py --show-stats ../../prebuilts/sdk/$((API))/public/api/android.txt ../../prebuilts/sdk/$((API-1))/public/api/android.txt
+ let API=API-1
+done
diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp
index 3689a8f2981e..639f98062b71 100644
--- a/tools/incident_section_gen/main.cpp
+++ b/tools/incident_section_gen/main.cpp
@@ -110,9 +110,7 @@ static bool generateIncidentSectionsCpp(Descriptor const* descriptor)
N = descriptor->field_count();
for (int i=0; i<N; i++) {
const FieldDescriptor* field = descriptor->field(i);
- if (field->type() == FieldDescriptor::TYPE_MESSAGE) {
- sections[field->name()] = field;
- }
+ sections[field->name()] = field;
}
printf("IncidentSection const INCIDENT_SECTIONS[] = {\n");
@@ -404,7 +402,7 @@ static bool generateSectionListCpp(Descriptor const* descriptor) {
for (int i=0; i<descriptor->field_count(); i++) {
const FieldDescriptor* field = descriptor->field(i);
- if (field->type() != FieldDescriptor::TYPE_MESSAGE) {
+ if (field->type() != FieldDescriptor::TYPE_MESSAGE && field->type() != FieldDescriptor::TYPE_STRING) {
continue;
}
const SectionFlags s = getSectionFlags(field);
@@ -412,8 +410,7 @@ static bool generateSectionListCpp(Descriptor const* descriptor) {
case SECTION_NONE:
continue;
case SECTION_FILE:
- printf(" new FileSection(%d, \"%s\", %s),\n", field->number(), s.args().c_str(),
- s.device_specific() ? "true" : "false");
+ printf(" new FileSection(%d, \"%s\"),\n", field->number(), s.args().c_str());
break;
case SECTION_COMMAND:
printf(" new CommandSection(%d,", field->number());
@@ -457,13 +454,13 @@ static bool generateSectionListCpp(Descriptor const* descriptor) {
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) {
+ printPrivacy(fieldName, field, "NULL", fieldDest, "NULL");
continue;
}
+
+ skip[i] = true;
+ const string fieldMessageName = getMessageName(field->message_type(), fieldDest);
// generate privacy flags for each section.
if (generatePrivacyFlags(field->message_type(), fieldDest, variableNames, &parents)) {
printPrivacy(fieldName, field, fieldMessageName, fieldDest, "NULL");
diff --git a/tools/stringslint/stringslint.py b/tools/stringslint/stringslint.py
index d637ff346c82..03c0b9af66a0 100644
--- a/tools/stringslint/stringslint.py
+++ b/tools/stringslint/stringslint.py
@@ -20,11 +20,22 @@ a previous strings file, if provided.
Usage: stringslint.py strings.xml
Usage: stringslint.py strings.xml old_strings.xml
+
+In general:
+* Errors signal issues that must be fixed before submitting, and are only
+ used when there are no false-positives.
+* Warnings signal issues that might need to be fixed, but need manual
+ inspection due to risk of false-positives.
+* Info signal issues that should be fixed to match best-practices, such
+ as providing comments to aid translation.
"""
-import re, sys
+import re, sys, codecs
import lxml.etree as ET
+reload(sys)
+sys.setdefaultencoding('utf8')
+
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False):
@@ -43,10 +54,10 @@ def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False):
warnings = None
-def warn(tag, msg, actual, expected):
+def warn(tag, msg, actual, expected, color=YELLOW):
global warnings
key = "%s:%d" % (tag.attrib["name"], hash(msg))
- value = "%sLine %d: '%s':%s %s" % (format(fg=YELLOW, bold=True),
+ value = "%sLine %d: '%s':%s %s" % (format(fg=color, bold=True),
tag.sourceline,
tag.attrib["name"],
format(reset=True),
@@ -59,6 +70,46 @@ def warn(tag, msg, actual, expected):
format(reset=True))
warnings[key] = value
+
+def error(tag, msg, actual, expected):
+ warn(tag, msg, actual, expected, RED)
+
+def info(tag, msg, actual, expected):
+ warn(tag, msg, actual, expected, CYAN)
+
+# Escaping logic borrowed from https://stackoverflow.com/a/24519338
+ESCAPE_SEQUENCE_RE = re.compile(r'''
+ ( \\U........ # 8-digit hex escapes
+ | \\u.... # 4-digit hex escapes
+ | \\x.. # 2-digit hex escapes
+ | \\[0-7]{1,3} # Octal escapes
+ | \\N\{[^}]+\} # Unicode characters by name
+ | \\[\\'"abfnrtv] # Single-character escapes
+ )''', re.UNICODE | re.VERBOSE)
+
+def decode_escapes(s):
+ def decode_match(match):
+ return codecs.decode(match.group(0), 'unicode-escape')
+
+ s = re.sub(r"\n\s*", " ", s)
+ s = ESCAPE_SEQUENCE_RE.sub(decode_match, s)
+ s = re.sub(r"%(\d+\$)?[a-z]", "____", s)
+ s = re.sub(r"\^\d+", "____", s)
+ s = re.sub(r"<br/?>", "\n", s)
+ s = re.sub(r"</?[a-z]+>", "", s)
+ return s
+
+def sample_iter(tag):
+ if not isinstance(tag, ET._Comment) and re.match("{.*xliff.*}g", tag.tag) and "example" in tag.attrib:
+ yield tag.attrib["example"]
+ elif tag.text:
+ yield decode_escapes(tag.text)
+ for e in tag:
+ for v in sample_iter(e):
+ yield v
+ if e.tail:
+ yield decode_escapes(e.tail)
+
def lint(path):
global warnings
warnings = {}
@@ -80,35 +131,45 @@ def lint(path):
comment = last_comment
last_comment = None
+ # Prepare string for analysis
+ text = "".join(child.itertext())
+ sample = "".join(sample_iter(child)).strip().strip("'\"")
+
# Validate comment
if comment is None:
- warn(child, "Missing string comment to aid translation",
+ info(child, "Missing string comment to aid translation",
None, None)
continue
if "do not translate" in comment.text.lower():
continue
if "translatable" in child.attrib and child.attrib["translatable"].lower() == "false":
continue
- if re.search("CHAR[ _-]LIMIT=(\d+|NONE|none)", comment.text) is None:
- warn(child, "Missing CHAR LIMIT to aid translation",
+
+ limit = re.search("CHAR[ _-]LIMIT=(\d+|NONE|none)", comment.text)
+ if limit is None:
+ info(child, "Missing CHAR LIMIT to aid translation",
repr(comment), "<!-- Description of string [CHAR LIMIT=32] -->")
+ elif re.match("\d+", limit.group(1)):
+ limit = int(limit.group(1))
+ if len(sample) > limit:
+ warn(child, "Expanded string length is larger than CHAR LIMIT",
+ sample, None)
# Look for common mistakes/substitutions
- text = "".join(child.itertext()).strip()
if "'" in text:
- warn(child, "Turned quotation mark glyphs are more polished",
+ error(child, "Turned quotation mark glyphs are more polished",
text, "This doesn\u2019t need to \u2018happen\u2019 today")
if '"' in text and not text.startswith('"') and text.endswith('"'):
- warn(child, "Turned quotation mark glyphs are more polished",
+ error(child, "Turned quotation mark glyphs are more polished",
text, "This needs to \u201chappen\u201d today")
if "..." in text:
- warn(child, "Ellipsis glyph is more polished",
+ error(child, "Ellipsis glyph is more polished",
text, "Loading\u2026")
if "wi-fi" in text.lower():
- warn(child, "Non-breaking glyph is more polished",
+ error(child, "Non-breaking glyph is more polished",
text, "Wi\u2011Fi")
if "wifi" in text.lower():
- warn(child, "Using non-standard spelling",
+ error(child, "Using non-standard spelling",
text, "Wi\u2011Fi")
if re.search("\d-\d", text):
warn(child, "Ranges should use en dash glyph",
@@ -119,11 +180,17 @@ def lint(path):
if ". " in text:
warn(child, "Only use single space between sentences",
text, "First idea. Second idea.")
+ if re.match(r"^[A-Z\s]{5,}$", text):
+ warn(child, "Actions should use android:textAllCaps in layout; ignore if acronym",
+ text, "Refresh data")
+ if " phone " in text and "product" not in child.attrib:
+ warn(child, "Strings mentioning phones should have variants for tablets",
+ text, None)
# When more than one substitution, require indexes
if len(re.findall("%[^%]", text)) > 1:
if len(re.findall("%[^\d]", text)) > 0:
- warn(child, "Substitutions must be indexed",
+ error(child, "Substitutions must be indexed",
text, "Add %1$s to %2$s")
# Require xliff substitutions
@@ -132,15 +199,15 @@ def lint(path):
if gc.tail and re.search("%[^%]", gc.tail): badsub = True
if re.match("{.*xliff.*}g", gc.tag):
if "id" not in gc.attrib:
- warn(child, "Substitutions must define id attribute",
+ error(child, "Substitutions must define id attribute",
None, "<xliff:g id=\"domain\" example=\"example.com\">%1$s</xliff:g>")
if "example" not in gc.attrib:
- warn(child, "Substitutions must define example attribute",
+ error(child, "Substitutions must define example attribute",
None, "<xliff:g id=\"domain\" example=\"example.com\">%1$s</xliff:g>")
else:
if gc.text and re.search("%[^%]", gc.text): badsub = True
if badsub:
- warn(child, "Substitutions must be inside xliff tags",
+ error(child, "Substitutions must be inside xliff tags",
text, "<xliff:g id=\"domain\" example=\"example.com\">%1$s</xliff:g>")
return warnings