When parsing is complete, we now have a list of output artifacts that have their referential integrity validated. This means that once the configuration file is parsed, the only errors that can occur are related to APK processing, and not the configuration itself. This reduces the number of errors that could cause a partial output of APK artifacts. It simplifies the public API and reduces the complexity of the code to generate multiple APKs. Test: Ran unit tests Test: manually ran the optimize command to ensure it still works Change-Id: I3f2d885b207a84c958f5348a4baa6718598184a4
346 lines
11 KiB
C++
346 lines
11 KiB
C++
/*
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "MultiApkGenerator.h"
|
|
|
|
#include <algorithm>
|
|
#include <regex>
|
|
#include <string>
|
|
|
|
#include "androidfw/StringPiece.h"
|
|
|
|
#include "LoadedApk.h"
|
|
#include "ResourceUtils.h"
|
|
#include "ValueVisitor.h"
|
|
#include "configuration/ConfigurationParser.h"
|
|
#include "filter/AbiFilter.h"
|
|
#include "filter/Filter.h"
|
|
#include "format/Archive.h"
|
|
#include "format/binary/XmlFlattener.h"
|
|
#include "optimize/VersionCollapser.h"
|
|
#include "process/IResourceTableConsumer.h"
|
|
#include "split/TableSplitter.h"
|
|
#include "util/Files.h"
|
|
#include "xml/XmlDom.h"
|
|
#include "xml/XmlUtil.h"
|
|
|
|
namespace aapt {
|
|
|
|
using ::aapt::configuration::AndroidSdk;
|
|
using ::aapt::configuration::OutputArtifact;
|
|
using ::aapt::xml::kSchemaAndroid;
|
|
using ::aapt::xml::XmlResource;
|
|
using ::android::StringPiece;
|
|
|
|
/**
|
|
* Context wrapper that allows the min Android SDK value to be overridden.
|
|
*/
|
|
class ContextWrapper : public IAaptContext {
|
|
public:
|
|
explicit ContextWrapper(IAaptContext* context)
|
|
: context_(context), min_sdk_(context_->GetMinSdkVersion()) {
|
|
}
|
|
|
|
PackageType GetPackageType() override {
|
|
return context_->GetPackageType();
|
|
}
|
|
|
|
SymbolTable* GetExternalSymbols() override {
|
|
return context_->GetExternalSymbols();
|
|
}
|
|
|
|
IDiagnostics* GetDiagnostics() override {
|
|
if (source_diag_) {
|
|
return source_diag_.get();
|
|
}
|
|
return context_->GetDiagnostics();
|
|
}
|
|
|
|
const std::string& GetCompilationPackage() override {
|
|
return context_->GetCompilationPackage();
|
|
}
|
|
|
|
uint8_t GetPackageId() override {
|
|
return context_->GetPackageId();
|
|
}
|
|
|
|
NameMangler* GetNameMangler() override {
|
|
return context_->GetNameMangler();
|
|
}
|
|
|
|
bool IsVerbose() override {
|
|
return context_->IsVerbose();
|
|
}
|
|
|
|
int GetMinSdkVersion() override {
|
|
return min_sdk_;
|
|
}
|
|
|
|
void SetMinSdkVersion(int min_sdk) {
|
|
min_sdk_ = min_sdk;
|
|
}
|
|
|
|
void SetSource(const std::string& source) {
|
|
source_diag_ =
|
|
util::make_unique<SourcePathDiagnostics>(Source{source}, context_->GetDiagnostics());
|
|
}
|
|
|
|
private:
|
|
IAaptContext* context_;
|
|
std::unique_ptr<SourcePathDiagnostics> source_diag_;
|
|
|
|
int min_sdk_ = -1;
|
|
};
|
|
|
|
class SignatureFilter : public IPathFilter {
|
|
bool Keep(const std::string& path) override {
|
|
static std::regex signature_regex(R"regex(^META-INF/.*\.(RSA|DSA|EC|SF)$)regex");
|
|
if (std::regex_search(path, signature_regex)) {
|
|
return false;
|
|
}
|
|
return !(path == "META-INF/MANIFEST.MF");
|
|
}
|
|
};
|
|
|
|
MultiApkGenerator::MultiApkGenerator(LoadedApk* apk, IAaptContext* context)
|
|
: apk_(apk), context_(context) {
|
|
}
|
|
|
|
bool MultiApkGenerator::FromBaseApk(const MultiApkGeneratorOptions& options) {
|
|
// TODO(safarmer): Handle APK version codes for the generated APKs.
|
|
|
|
std::unordered_set<std::string> artifacts_to_keep = options.kept_artifacts;
|
|
std::unordered_set<std::string> filtered_artifacts;
|
|
std::unordered_set<std::string> kept_artifacts;
|
|
|
|
// For now, just write out the stripped APK since ABI splitting doesn't modify anything else.
|
|
for (const OutputArtifact& artifact : options.apk_artifacts) {
|
|
FilterChain filters;
|
|
|
|
ContextWrapper wrapped_context{context_};
|
|
wrapped_context.SetSource(artifact.name);
|
|
|
|
if (!options.kept_artifacts.empty()) {
|
|
const auto& it = artifacts_to_keep.find(artifact.name);
|
|
if (it == artifacts_to_keep.end()) {
|
|
filtered_artifacts.insert(artifact.name);
|
|
if (context_->IsVerbose()) {
|
|
context_->GetDiagnostics()->Note(DiagMessage(artifact.name) << "skipping artifact");
|
|
}
|
|
continue;
|
|
} else {
|
|
artifacts_to_keep.erase(it);
|
|
kept_artifacts.insert(artifact.name);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<ResourceTable> table =
|
|
FilterTable(context_, artifact, *apk_->GetResourceTable(), &filters);
|
|
if (!table) {
|
|
return false;
|
|
}
|
|
|
|
IDiagnostics* diag = wrapped_context.GetDiagnostics();
|
|
|
|
std::unique_ptr<XmlResource> manifest;
|
|
if (!UpdateManifest(artifact, &manifest, diag)) {
|
|
diag->Error(DiagMessage() << "could not update AndroidManifest.xml for output artifact");
|
|
return false;
|
|
}
|
|
|
|
std::string out = options.out_dir;
|
|
if (!file::mkdirs(out)) {
|
|
diag->Warn(DiagMessage() << "could not create out dir: " << out);
|
|
}
|
|
file::AppendPath(&out, artifact.name);
|
|
|
|
if (context_->IsVerbose()) {
|
|
diag->Note(DiagMessage() << "Generating split: " << out);
|
|
}
|
|
|
|
std::unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(diag, out);
|
|
|
|
if (context_->IsVerbose()) {
|
|
diag->Note(DiagMessage() << "Writing output: " << out);
|
|
}
|
|
|
|
filters.AddFilter(util::make_unique<SignatureFilter>());
|
|
if (!apk_->WriteToArchive(&wrapped_context, table.get(), options.table_flattener_options,
|
|
&filters, writer.get(), manifest.get())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Make sure all of the requested artifacts were valid. If there are any kept artifacts left,
|
|
// either the config or the command line was wrong.
|
|
if (!artifacts_to_keep.empty()) {
|
|
context_->GetDiagnostics()->Error(
|
|
DiagMessage() << "The configuration and command line to filter artifacts do not match");
|
|
|
|
context_->GetDiagnostics()->Error(DiagMessage() << kept_artifacts.size() << " kept:");
|
|
for (const auto& artifact : kept_artifacts) {
|
|
context_->GetDiagnostics()->Error(DiagMessage() << " " << artifact);
|
|
}
|
|
|
|
context_->GetDiagnostics()->Error(DiagMessage() << filtered_artifacts.size() << " filtered:");
|
|
for (const auto& artifact : filtered_artifacts) {
|
|
context_->GetDiagnostics()->Error(DiagMessage() << " " << artifact);
|
|
}
|
|
|
|
context_->GetDiagnostics()->Error(DiagMessage() << artifacts_to_keep.size() << " missing:");
|
|
for (const auto& artifact : artifacts_to_keep) {
|
|
context_->GetDiagnostics()->Error(DiagMessage() << " " << artifact);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr<ResourceTable> MultiApkGenerator::FilterTable(IAaptContext* context,
|
|
const OutputArtifact& artifact,
|
|
const ResourceTable& old_table,
|
|
FilterChain* filters) {
|
|
TableSplitterOptions splits;
|
|
AxisConfigFilter axis_filter;
|
|
ContextWrapper wrapped_context{context};
|
|
wrapped_context.SetSource(artifact.name);
|
|
|
|
if (!artifact.abis.empty()) {
|
|
filters->AddFilter(AbiFilter::FromAbiList(artifact.abis));
|
|
}
|
|
|
|
if (!artifact.screen_densities.empty()) {
|
|
for (const auto& density_config : artifact.screen_densities) {
|
|
splits.preferred_densities.push_back(density_config.density);
|
|
}
|
|
}
|
|
|
|
if (!artifact.locales.empty()) {
|
|
for (const auto& locale : artifact.locales) {
|
|
axis_filter.AddConfig(locale);
|
|
}
|
|
splits.config_filter = &axis_filter;
|
|
}
|
|
|
|
if (artifact.android_sdk && artifact.android_sdk.value().min_sdk_version) {
|
|
wrapped_context.SetMinSdkVersion(artifact.android_sdk.value().min_sdk_version.value());
|
|
}
|
|
|
|
std::unique_ptr<ResourceTable> table = old_table.Clone();
|
|
|
|
VersionCollapser collapser;
|
|
if (!collapser.Consume(&wrapped_context, table.get())) {
|
|
context->GetDiagnostics()->Error(DiagMessage() << "Failed to strip versioned resources");
|
|
return {};
|
|
}
|
|
|
|
TableSplitter splitter{{}, splits};
|
|
splitter.SplitTable(table.get());
|
|
return table;
|
|
}
|
|
|
|
bool MultiApkGenerator::UpdateManifest(const OutputArtifact& artifact,
|
|
std::unique_ptr<XmlResource>* updated_manifest,
|
|
IDiagnostics* diag) {
|
|
const xml::XmlResource* apk_manifest = apk_->GetManifest();
|
|
if (apk_manifest == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
*updated_manifest = apk_manifest->Clone();
|
|
XmlResource* manifest = updated_manifest->get();
|
|
|
|
// Make sure the first element is <manifest> with package attribute.
|
|
xml::Element* manifest_el = manifest->root.get();
|
|
if (manifest_el == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
|
|
diag->Error(DiagMessage(manifest->file.source) << "root tag must be <manifest>");
|
|
return false;
|
|
}
|
|
|
|
// Update the versionCode attribute.
|
|
xml::Attribute* versionCode = manifest_el->FindAttribute(kSchemaAndroid, "versionCode");
|
|
if (versionCode == nullptr) {
|
|
diag->Error(DiagMessage(manifest->file.source) << "manifest must have a versionCode attribute");
|
|
return false;
|
|
}
|
|
|
|
auto* compiled_version = ValueCast<BinaryPrimitive>(versionCode->compiled_value.get());
|
|
if (compiled_version == nullptr) {
|
|
diag->Error(DiagMessage(manifest->file.source) << "versionCode is invalid");
|
|
return false;
|
|
}
|
|
|
|
int new_version = compiled_version->value.data + artifact.version;
|
|
versionCode->compiled_value = ResourceUtils::TryParseInt(std::to_string(new_version));
|
|
|
|
// Check to see if the minSdkVersion needs to be updated.
|
|
if (artifact.android_sdk) {
|
|
// TODO(safarmer): Handle the rest of the Android SDK.
|
|
const AndroidSdk& android_sdk = artifact.android_sdk.value();
|
|
|
|
if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
|
|
if (xml::Attribute* min_sdk_attr =
|
|
uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
|
|
// Populate with a pre-compiles attribute to we don't need to relink etc.
|
|
const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version.value());
|
|
min_sdk_attr->compiled_value = ResourceUtils::TryParseInt(min_sdk_str);
|
|
} else {
|
|
// There was no minSdkVersion. This is strange since at this point we should have been
|
|
// through the manifest fixer which sets the default minSdkVersion.
|
|
diag->Error(DiagMessage(manifest->file.source) << "missing minSdkVersion from <uses-sdk>");
|
|
return false;
|
|
}
|
|
} else {
|
|
// No uses-sdk present. This is strange since at this point we should have been
|
|
// through the manifest fixer which should have added it.
|
|
diag->Error(DiagMessage(manifest->file.source) << "missing <uses-sdk> from <manifest>");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!artifact.screen_densities.empty()) {
|
|
xml::Element* screens_el = manifest_el->FindChild({}, "compatible-screens");
|
|
if (!screens_el) {
|
|
// create a new element.
|
|
std::unique_ptr<xml::Element> new_screens_el = util::make_unique<xml::Element>();
|
|
new_screens_el->name = "compatible-screens";
|
|
screens_el = new_screens_el.get();
|
|
manifest_el->InsertChild(0, std::move(new_screens_el));
|
|
} else {
|
|
// clear out the old element.
|
|
screens_el->GetChildElements().clear();
|
|
}
|
|
|
|
for (const auto& density : artifact.screen_densities) {
|
|
std::unique_ptr<xml::Element> screen_el = util::make_unique<xml::Element>();
|
|
screen_el->name = "screen";
|
|
const char* density_str = density.toString().string();
|
|
screen_el->attributes.push_back(xml::Attribute{kSchemaAndroid, "screenDensity", density_str});
|
|
screens_el->AppendChild(std::move(screen_el));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace aapt
|