AAPT2: Set the minSdkVersion when generating multiple APKs.
When generating multiple APKs from a configuration file, check to see if
we have filtered resource by minSdkVersion and update the manifest to
reflect this. We only want to inflate and modify the manifest file if
there is an update to be applied.
Bug: 37944703
Bug: 67005138
Test: Ran unit tests
Test: Manually split an APK and verified the manifest by dumping with
AAPT (both xmltree and badging).
Change-Id: I64a0e4889d7d9e57373369b044a091287b06cc35
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index b80780e..6f2b865 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -20,11 +20,15 @@
#include "ValueVisitor.h"
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
+#include "flatten/XmlFlattener.h"
#include "io/BigBufferInputStream.h"
#include "io/Util.h"
+#include "xml/XmlDom.h"
namespace aapt {
+using xml::XmlResource;
+
std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context,
const android::StringPiece& path) {
Source source(path);
@@ -52,6 +56,7 @@
if (!parser.Parse()) {
return {};
}
+
return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
}
@@ -63,7 +68,7 @@
bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table,
const TableFlattenerOptions& options, FilterChain* filters,
- IArchiveWriter* writer) {
+ IArchiveWriter* writer, XmlResource* manifest) {
std::set<std::string> referenced_resources;
// List the files being referenced in the resource table.
for (auto& pkg : split_table->packages) {
@@ -119,6 +124,20 @@
return false;
}
+ } else if (manifest != nullptr && path == "AndroidManifest.xml") {
+ BigBuffer buffer(8192);
+ XmlFlattener xml_flattener(&buffer, {});
+ if (!xml_flattener.Consume(context, manifest)) {
+ context->GetDiagnostics()->Error(DiagMessage(path) << "flattening failed");
+ return false;
+ }
+
+ uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
+ io::BigBufferInputStream manifest_buffer_in(&buffer);
+ if (!io::CopyInputStreamToArchive(context, &manifest_buffer_in, path, compression_flags,
+ writer)) {
+ return false;
+ }
} else {
uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
if (!io::CopyFileToArchive(context, file, path, compression_flags, writer)) {
@@ -129,4 +148,26 @@
return true;
}
+std::unique_ptr<xml::XmlResource> LoadedApk::InflateManifest(IAaptContext* context) {
+ IDiagnostics* diag = context->GetDiagnostics();
+
+ io::IFile* manifest_file = GetFileCollection()->FindFile("AndroidManifest.xml");
+ if (manifest_file == nullptr) {
+ diag->Error(DiagMessage(source_) << "no AndroidManifest.xml found");
+ return {};
+ }
+
+ std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
+ if (manifest_data == nullptr) {
+ diag->Error(DiagMessage(manifest_file->GetSource()) << "could not open AndroidManifest.xml");
+ return {};
+ }
+
+ std::unique_ptr<xml::XmlResource> manifest =
+ xml::Inflate(manifest_data->data(), manifest_data->size(), diag, manifest_file->GetSource());
+ if (manifest == nullptr) {
+ diag->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
+ }
+ return manifest;
+}
} // namespace aapt
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index dacd0c2..d779b7e 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -25,17 +25,17 @@
#include "flatten/TableFlattener.h"
#include "io/ZipArchive.h"
#include "unflatten/BinaryResourceParser.h"
+#include "xml/XmlDom.h"
namespace aapt {
/** Info about an APK loaded in memory. */
class LoadedApk {
public:
- LoadedApk(
- const Source& source,
- std::unique_ptr<io::IFileCollection> apk,
- std::unique_ptr<ResourceTable> table)
- : source_(source), apk_(std::move(apk)), table_(std::move(table)) {}
+ LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
+ std::unique_ptr<ResourceTable> table)
+ : source_(source), apk_(std::move(apk)), table_(std::move(table)) {
+ }
io::IFileCollection* GetFileCollection() { return apk_.get(); }
@@ -51,13 +51,20 @@
IArchiveWriter* writer);
/**
- * Writes the APK on disk at the given path, while also removing the resource
- * files that are not referenced in the resource table. The provided filter
- * chain is applied to each entry in the APK file.
+ * Writes the APK on disk at the given path, while also removing the resource files that are not
+ * referenced in the resource table. The provided filter chain is applied to each entry in the APK
+ * file.
+ *
+ * If the manifest is also provided, it will be written to the new APK file, otherwise the
+ * original manifest will be written. The manifest is only required if the contents of the new APK
+ * have been modified in a way that require the AndroidManifest.xml to also be modified.
*/
virtual bool WriteToArchive(IAaptContext* context, ResourceTable* split_table,
const TableFlattenerOptions& options, FilterChain* filters,
- IArchiveWriter* writer);
+ IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
+
+ /** Inflates the AndroidManifest.xml file from the APK. */
+ std::unique_ptr<xml::XmlResource> InflateManifest(IAaptContext* context);
static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context,
const android::StringPiece& path);
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 704faee..56b61d0 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -283,24 +283,8 @@
bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
OptimizeOptions* out_options) {
- io::IFile* manifest_file = apk->GetFileCollection()->FindFile("AndroidManifest.xml");
- if (manifest_file == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "missing AndroidManifest.xml");
- return false;
- }
-
- std::unique_ptr<io::IData> data = manifest_file->OpenAsData();
- if (data == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage(manifest_file->GetSource())
- << "failed to open file");
- return false;
- }
-
- std::unique_ptr<xml::XmlResource> manifest = xml::Inflate(
- data->data(), data->size(), context->GetDiagnostics(), manifest_file->GetSource());
+ std::unique_ptr<xml::XmlResource> manifest = apk->InflateManifest(context);
if (manifest == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
return false;
}
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index 9d6d328..d2b4d72 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -27,6 +27,7 @@
#include "ConfigDescription.h"
#include "Diagnostics.h"
+#include "ResourceUtils.h"
#include "io/File.h"
#include "io/FileSystem.h"
#include "io/StringInputStream.h"
@@ -499,11 +500,11 @@
AndroidSdk entry;
for (const auto& attr : child->attributes) {
if (attr.name == "minSdkVersion") {
- entry.min_sdk_version = {attr.value};
+ entry.min_sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
} else if (attr.name == "targetSdkVersion") {
- entry.target_sdk_version = {attr.value};
+ entry.target_sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
} else if (attr.name == "maxSdkVersion") {
- entry.max_sdk_version = {attr.value};
+ entry.max_sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
} else {
diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
}
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index 9bc9081..ebf1c98 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -103,14 +103,14 @@
};
struct AndroidSdk {
- Maybe<std::string> min_sdk_version;
- Maybe<std::string> target_sdk_version;
- Maybe<std::string> max_sdk_version;
+ Maybe<int> min_sdk_version;
+ Maybe<int> target_sdk_version;
+ Maybe<int> max_sdk_version;
Maybe<AndroidManifest> manifest;
- static AndroidSdk ForMinSdk(std::string min_sdk) {
+ static AndroidSdk ForMinSdk(int min_sdk) {
AndroidSdk sdk;
- sdk.min_sdk_version = {std::move(min_sdk)};
+ sdk.min_sdk_version = min_sdk;
return sdk;
}
diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp
index 7ffb3d5..6bb168f 100644
--- a/tools/aapt2/configuration/ConfigurationParser_test.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp
@@ -24,6 +24,15 @@
#include "xml/XmlDom.h"
namespace aapt {
+
+namespace configuration {
+void PrintTo(const AndroidSdk& sdk, std::ostream* os) {
+ *os << "SDK: min=" << sdk.min_sdk_version.value_or_default(-1)
+ << ", target=" << sdk.target_sdk_version.value_or_default(-1)
+ << ", max=" << sdk.max_sdk_version.value_or_default(-1);
+}
+} // namespace configuration
+
namespace {
using ::android::ResTable_config;
@@ -76,9 +85,9 @@
</locale-group>
<android-sdk-group label="v19">
<android-sdk
- minSdkVersion="v19"
- targetSdkVersion="v24"
- maxSdkVersion="v25">
+ minSdkVersion="19"
+ targetSdkVersion="24"
+ maxSdkVersion="25">
<manifest>
<!--- manifest additions here XSLT? TODO -->
</manifest>
@@ -156,7 +165,7 @@
EXPECT_EQ(1ul, value.android_sdk_groups.size());
EXPECT_TRUE(value.android_sdk_groups["v19"].min_sdk_version);
- EXPECT_EQ("v19", value.android_sdk_groups["v19"].min_sdk_version.value());
+ EXPECT_EQ(19, value.android_sdk_groups["v19"].min_sdk_version.value());
EXPECT_EQ(1ul, value.gl_texture_groups.size());
EXPECT_EQ(1ul, value.gl_texture_groups["dxt1"].size());
@@ -321,9 +330,9 @@
static constexpr const char* xml = R"xml(
<android-sdk-group label="v19">
<android-sdk
- minSdkVersion="v19"
- targetSdkVersion="v24"
- maxSdkVersion="v25">
+ minSdkVersion="19"
+ targetSdkVersion="24"
+ maxSdkVersion="25">
<manifest>
<!--- manifest additions here XSLT? TODO -->
</manifest>
@@ -342,14 +351,43 @@
auto& out = config.android_sdk_groups["v19"];
AndroidSdk sdk;
- sdk.min_sdk_version = std::string("v19");
- sdk.target_sdk_version = std::string("v24");
- sdk.max_sdk_version = std::string("v25");
+ sdk.min_sdk_version = 19;
+ sdk.target_sdk_version = 24;
+ sdk.max_sdk_version = 25;
sdk.manifest = AndroidManifest();
ASSERT_EQ(sdk, out);
}
+TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_NonNumeric) {
+ static constexpr const char* xml = R"xml(
+ <android-sdk-group label="O">
+ <android-sdk
+ minSdkVersion="M"
+ targetSdkVersion="O"
+ maxSdkVersion="O">
+ </android-sdk>
+ </android-sdk-group>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ PostProcessingConfiguration config;
+ bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ ASSERT_EQ(1ul, config.android_sdk_groups.size());
+ ASSERT_EQ(1u, config.android_sdk_groups.count("O"));
+
+ auto& out = config.android_sdk_groups["O"];
+
+ AndroidSdk sdk;
+ sdk.min_sdk_version = {}; // Only the latest development version is supported.
+ sdk.target_sdk_version = 26;
+ sdk.max_sdk_version = 26;
+
+ ASSERT_EQ(sdk, out);
+}
+
TEST_F(ConfigurationParserTest, GlTextureGroupAction) {
static constexpr const char* xml = R"xml(
<gl-texture-group label="dxt1">
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 5ff8908..563c46d 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -22,22 +22,47 @@
#include "androidfw/StringPiece.h"
#include "LoadedApk.h"
+#include "ResourceUtils.h"
#include "configuration/ConfigurationParser.h"
#include "filter/AbiFilter.h"
#include "filter/Filter.h"
#include "flatten/Archive.h"
+#include "flatten/XmlFlattener.h"
#include "optimize/VersionCollapser.h"
#include "process/IResourceTableConsumer.h"
#include "split/TableSplitter.h"
#include "util/Files.h"
+#include "xml/XmlDom.h"
namespace aapt {
using ::aapt::configuration::AndroidSdk;
using ::aapt::configuration::Artifact;
using ::aapt::configuration::PostProcessingConfiguration;
+using ::aapt::xml::XmlResource;
using ::android::StringPiece;
+namespace {
+
+Maybe<AndroidSdk> GetAndroidSdk(const Artifact& artifact, const PostProcessingConfiguration& config,
+ IDiagnostics* diag) {
+ if (!artifact.android_sdk_group) {
+ return {};
+ }
+
+ const std::string& group_name = artifact.android_sdk_group.value();
+ auto group = config.android_sdk_groups.find(group_name);
+ // TODO: Remove validation when configuration parser ensures referential integrity.
+ if (group == config.android_sdk_groups.end()) {
+ diag->Error(DiagMessage() << "could not find referenced group '" << group_name << "'");
+ return {};
+ }
+
+ return group->second;
+}
+
+} // namespace
+
/**
* Context wrapper that allows the min Android SDK value to be overridden.
*/
@@ -127,6 +152,43 @@
return false;
}
+ std::unique_ptr<XmlResource> manifest;
+
+ Maybe<AndroidSdk> maybe_sdk = GetAndroidSdk(artifact, config, diag);
+ if (maybe_sdk) {
+ // TODO(safarmer): Handle the rest of the Android SDK.
+ const AndroidSdk& android_sdk = maybe_sdk.value();
+
+ manifest = apk_->InflateManifest(context_);
+ if (!manifest) {
+ return false;
+ }
+
+ // Make sure the first element is <manifest> with package attribute.
+ xml::Element* manifest_el = manifest->root.get();
+ if (manifest_el == nullptr) {
+ return {};
+ }
+
+ if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
+ diag->Error(DiagMessage(manifest->file.source) << "root tag must be <manifest>");
+ return {};
+ }
+
+ if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
+ if (xml::Attribute* min_sdk_attr =
+ uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
+ if (min_sdk_attr == nullptr) {
+ diag->Error(DiagMessage(manifest->file.source.WithLine(uses_sdk_el->line_number))
+ << "missing android:minSdkVersion");
+ return {};
+ }
+ const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version.value());
+ min_sdk_attr->compiled_value = ResourceUtils::TryParseInt(min_sdk_str);
+ }
+ }
+ }
+
std::string out = options.out_dir;
if (!file::mkdirs(out)) {
context_->GetDiagnostics()->Warn(DiagMessage() << "could not create out dir: " << out);
@@ -145,7 +207,7 @@
}
if (!apk_->WriteToArchive(context_, table.get(), options.table_flattener_options, &filters,
- writer.get())) {
+ writer.get(), manifest.get())) {
return false;
}
}
@@ -208,37 +270,15 @@
splits.config_filter = &axis_filter;
}
- if (artifact.android_sdk_group) {
- const std::string& group_name = artifact.android_sdk_group.value();
- auto group = config.android_sdk_groups.find(group_name);
- // TODO: Remove validation when configuration parser ensures referential integrity.
- if (group == config.android_sdk_groups.end()) {
- context_->GetDiagnostics()->Error(DiagMessage() << "could not find referenced group '"
- << group_name << "'");
- return {};
- }
-
- const AndroidSdk& sdk = group->second;
- if (!sdk.min_sdk_version) {
- context_->GetDiagnostics()->Error(DiagMessage()
- << "skipping SDK version. No min SDK: " << group_name);
- return {};
- }
-
- ConfigDescription c;
- const std::string& version = sdk.min_sdk_version.value();
- if (!ConfigDescription::Parse(version, &c)) {
- context_->GetDiagnostics()->Error(DiagMessage() << "could not parse min SDK: " << version);
- return {};
- }
-
- wrappedContext.SetMinSdkVersion(c.sdkVersion);
+ Maybe<AndroidSdk> sdk = GetAndroidSdk(artifact, config, context_->GetDiagnostics());
+ if (sdk && sdk.value().min_sdk_version) {
+ wrappedContext.SetMinSdkVersion(sdk.value().min_sdk_version.value());
}
std::unique_ptr<ResourceTable> table = old_table.Clone();
VersionCollapser collapser;
- if (!collapser.Consume(context_, table.get())) {
+ if (!collapser.Consume(&wrappedContext, table.get())) {
context_->GetDiagnostics()->Error(DiagMessage() << "Failed to strip versioned resources");
return {};
}
diff --git a/tools/aapt2/optimize/MultiApkGenerator_test.cpp b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
index 23f573c..e8e6adc 100644
--- a/tools/aapt2/optimize/MultiApkGenerator_test.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
@@ -50,14 +50,6 @@
using ::testing::Test;
using ::testing::_;
-/** Subclass the LoadedApk class so that we can mock the WriteToArchive method. */
-class MockApk : public LoadedApk {
- public:
- MockApk(std::unique_ptr<ResourceTable> table) : LoadedApk({"test.apk"}, {}, std::move(table)){};
- MOCK_METHOD5(WriteToArchive, bool(IAaptContext*, ResourceTable*, const TableFlattenerOptions&,
- FilterChain*, IArchiveWriter*));
-};
-
/**
* Subclass the MultiApkGenerator class so that we can access the protected FilterTable method to
* directly test table filter.
@@ -111,54 +103,10 @@
ConfigDescription v21_ = ParseConfigOrDie("v21");
};
-TEST_F(MultiApkGeneratorTest, FromBaseApk) {
- std::unique_ptr<ResourceTable> table = BuildTable();
-
- MockApk apk{std::move(table)};
-
- EXPECT_CALL(apk, WriteToArchive(_, _, _, _, _)).Times(0);
-
- test::Context ctx;
- PostProcessingConfiguration empty_config;
- TableFlattenerOptions table_flattener_options;
-
- MultiApkGenerator generator{&apk, &ctx};
- EXPECT_TRUE(generator.FromBaseApk({"out", empty_config, table_flattener_options}));
-
- Artifact x64 = test::ArtifactBuilder()
- .SetName("${basename}.x64.apk")
- .SetAbiGroup("x64")
- .SetLocaleGroup("en")
- .SetDensityGroup("xhdpi")
- .Build();
-
- Artifact intel = test::ArtifactBuilder()
- .SetName("${basename}.intel.apk")
- .SetAbiGroup("intel")
- .SetLocaleGroup("europe")
- .SetDensityGroup("large")
- .Build();
-
- auto config = test::PostProcessingConfigurationBuilder()
- .SetLocaleGroup("en", {"en"})
- .SetLocaleGroup("europe", {"en", "fr", "de", "es"})
- .SetAbiGroup("x64", {Abi::kX86_64})
- .SetAbiGroup("intel", {Abi::kX86_64, Abi::kX86})
- .SetDensityGroup("xhdpi", {"xhdpi"})
- .SetDensityGroup("large", {"xhdpi", "xxhdpi", "xxxhdpi"})
- .AddArtifact(x64)
- .AddArtifact(intel)
- .Build();
-
- // Called once for each artifact.
- EXPECT_CALL(apk, WriteToArchive(Eq(&ctx), _, _, _, _)).Times(2).WillRepeatedly(Return(true));
- EXPECT_TRUE(generator.FromBaseApk({"out", config, table_flattener_options}));
-}
-
TEST_F(MultiApkGeneratorTest, VersionFilterNewerVersion) {
std::unique_ptr<ResourceTable> table = BuildTable();
- MockApk apk{std::move(table)};
+ LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(19).Build();
PostProcessingConfiguration empty_config;
TableFlattenerOptions table_flattener_options;
@@ -174,7 +122,7 @@
.SetLocaleGroup("en", {"en"})
.SetAbiGroup("x64", {Abi::kX86_64})
.SetDensityGroup("xhdpi", {"xhdpi"})
- .SetAndroidSdk("v23", AndroidSdk::ForMinSdk("v23"))
+ .SetAndroidSdk("v23", AndroidSdk::ForMinSdk(23))
.AddArtifact(x64)
.Build();
@@ -199,7 +147,7 @@
TEST_F(MultiApkGeneratorTest, VersionFilterOlderVersion) {
std::unique_ptr<ResourceTable> table = BuildTable();
- MockApk apk{std::move(table)};
+ LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
PostProcessingConfiguration empty_config;
TableFlattenerOptions table_flattener_options;
@@ -215,7 +163,7 @@
.SetLocaleGroup("en", {"en"})
.SetAbiGroup("x64", {Abi::kX86_64})
.SetDensityGroup("xhdpi", {"xhdpi"})
- .SetAndroidSdk("v4", AndroidSdk::ForMinSdk("v4"))
+ .SetAndroidSdk("v4", AndroidSdk::ForMinSdk(4))
.AddArtifact(x64)
.Build();
@@ -238,7 +186,7 @@
TEST_F(MultiApkGeneratorTest, VersionFilterNoVersion) {
std::unique_ptr<ResourceTable> table = BuildTable();
- MockApk apk{std::move(table)};
+ LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
PostProcessingConfiguration empty_config;
TableFlattenerOptions table_flattener_options;