AAPT2: Finish support for feature splits
- Prefix the config split name generated from a feature split with the name of the feature split. - Add the 'configForSplit' attribute to the <manifest> tag of a config split and give it the same name as the feature split it was generated from. - Look for the featureSplit attribute in <manifest> and automatically convert it to 'split' and inject 'android:isFeatureSplit="true"'. Feature splits should be written like so: <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.foo.example" featureSplit="feature_b"> <uses-split android:name="feature_a" /> ... </manifest> Bug: 34703094 Test: manual Change-Id: I01b5c4a9aa03a2d25ef1e87bc7874b57c9deede9
This commit is contained in:
@@ -23,30 +23,22 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* Holds basic information about the app being built. Most of this information
|
||||
* will come from the app's AndroidManifest.
|
||||
*/
|
||||
// Information relevant to building an app, parsed from the app's AndroidManifest.xml.
|
||||
struct AppInfo {
|
||||
/**
|
||||
* App's package name.
|
||||
*/
|
||||
// The app's package name.
|
||||
std::string package;
|
||||
|
||||
/**
|
||||
* The App's minimum SDK version.
|
||||
*/
|
||||
// The app's minimum SDK version, if it is defined.
|
||||
Maybe<std::string> min_sdk_version;
|
||||
|
||||
/**
|
||||
* The Version code of the app.
|
||||
*/
|
||||
// The app's version code, if it is defined.
|
||||
Maybe<uint32_t> version_code;
|
||||
|
||||
/**
|
||||
* The revision code of the app.
|
||||
*/
|
||||
// The app's revision code, if it is defined.
|
||||
Maybe<uint32_t> revision_code;
|
||||
|
||||
// The app's split name, if it is a split.
|
||||
Maybe<std::string> split_name;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -751,70 +751,67 @@ class LinkCommand {
|
||||
return true;
|
||||
}
|
||||
|
||||
Maybe<AppInfo> ExtractAppInfoFromManifest(xml::XmlResource* xml_res,
|
||||
IDiagnostics* diag) {
|
||||
Maybe<AppInfo> ExtractAppInfoFromManifest(xml::XmlResource* xml_res, IDiagnostics* diag) {
|
||||
// Make sure the first element is <manifest> with package attribute.
|
||||
if (xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get())) {
|
||||
AppInfo app_info;
|
||||
|
||||
if (!manifest_el->namespace_uri.empty() ||
|
||||
manifest_el->name != "manifest") {
|
||||
diag->Error(DiagMessage(xml_res->file.source)
|
||||
<< "root tag must be <manifest>");
|
||||
return {};
|
||||
}
|
||||
|
||||
xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
|
||||
if (!package_attr) {
|
||||
diag->Error(DiagMessage(xml_res->file.source)
|
||||
<< "<manifest> must have a 'package' attribute");
|
||||
return {};
|
||||
}
|
||||
|
||||
app_info.package = package_attr->value;
|
||||
|
||||
if (xml::Attribute* version_code_attr =
|
||||
manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) {
|
||||
Maybe<uint32_t> maybe_code =
|
||||
ResourceUtils::ParseInt(version_code_attr->value);
|
||||
if (!maybe_code) {
|
||||
diag->Error(DiagMessage(xml_res->file.source.WithLine(
|
||||
manifest_el->line_number))
|
||||
<< "invalid android:versionCode '"
|
||||
<< version_code_attr->value << "'");
|
||||
return {};
|
||||
}
|
||||
app_info.version_code = 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);
|
||||
if (!maybe_code) {
|
||||
diag->Error(DiagMessage(xml_res->file.source.WithLine(
|
||||
manifest_el->line_number))
|
||||
<< "invalid android:revisionCode '"
|
||||
<< revision_code_attr->value << "'");
|
||||
return {};
|
||||
}
|
||||
app_info.revision_code = maybe_code.value();
|
||||
}
|
||||
|
||||
if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
|
||||
if (xml::Attribute* min_sdk = uses_sdk_el->FindAttribute(
|
||||
xml::kSchemaAndroid, "minSdkVersion")) {
|
||||
app_info.min_sdk_version = min_sdk->value;
|
||||
}
|
||||
}
|
||||
return app_info;
|
||||
xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get());
|
||||
if (manifest_el == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
|
||||
AppInfo app_info;
|
||||
|
||||
if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
|
||||
diag->Error(DiagMessage(xml_res->file.source) << "root tag must be <manifest>");
|
||||
return {};
|
||||
}
|
||||
|
||||
xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
|
||||
if (!package_attr) {
|
||||
diag->Error(DiagMessage(xml_res->file.source)
|
||||
<< "<manifest> must have a 'package' attribute");
|
||||
return {};
|
||||
}
|
||||
app_info.package = package_attr->value;
|
||||
|
||||
if (xml::Attribute* version_code_attr =
|
||||
manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) {
|
||||
Maybe<uint32_t> maybe_code = ResourceUtils::ParseInt(version_code_attr->value);
|
||||
if (!maybe_code) {
|
||||
diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
|
||||
<< "invalid android:versionCode '" << version_code_attr->value << "'");
|
||||
return {};
|
||||
}
|
||||
app_info.version_code = 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);
|
||||
if (!maybe_code) {
|
||||
diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
|
||||
<< "invalid android:revisionCode '" << revision_code_attr->value << "'");
|
||||
return {};
|
||||
}
|
||||
app_info.revision_code = maybe_code.value();
|
||||
}
|
||||
|
||||
if (xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) {
|
||||
if (!split_name_attr->value.empty()) {
|
||||
app_info.split_name = split_name_attr->value;
|
||||
}
|
||||
}
|
||||
|
||||
if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
|
||||
if (xml::Attribute* min_sdk =
|
||||
uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
|
||||
app_info.min_sdk_version = min_sdk->value;
|
||||
}
|
||||
}
|
||||
return app_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it
|
||||
* linked.
|
||||
* Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
|
||||
* Postcondition: ResourceTable has only one package left. All others are
|
||||
* stripped, or there is an error and false is returned.
|
||||
*/
|
||||
@@ -1367,45 +1364,44 @@ class LinkCommand {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<xml::XmlResource> GenerateSplitManifest(
|
||||
const AppInfo& app_info, const SplitConstraints& constraints) {
|
||||
std::unique_ptr<xml::XmlResource> doc =
|
||||
util::make_unique<xml::XmlResource>();
|
||||
std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
|
||||
const SplitConstraints& constraints) {
|
||||
std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>();
|
||||
|
||||
std::unique_ptr<xml::Namespace> namespace_android =
|
||||
util::make_unique<xml::Namespace>();
|
||||
std::unique_ptr<xml::Namespace> namespace_android = util::make_unique<xml::Namespace>();
|
||||
namespace_android->namespace_uri = xml::kSchemaAndroid;
|
||||
namespace_android->namespace_prefix = "android";
|
||||
|
||||
std::unique_ptr<xml::Element> manifest_el =
|
||||
util::make_unique<xml::Element>();
|
||||
std::unique_ptr<xml::Element> manifest_el = util::make_unique<xml::Element>();
|
||||
manifest_el->name = "manifest";
|
||||
manifest_el->attributes.push_back(
|
||||
xml::Attribute{"", "package", app_info.package});
|
||||
manifest_el->attributes.push_back(xml::Attribute{"", "package", app_info.package});
|
||||
|
||||
if (app_info.version_code) {
|
||||
manifest_el->attributes.push_back(
|
||||
xml::Attribute{xml::kSchemaAndroid, "versionCode",
|
||||
std::to_string(app_info.version_code.value())});
|
||||
manifest_el->attributes.push_back(xml::Attribute{
|
||||
xml::kSchemaAndroid, "versionCode", std::to_string(app_info.version_code.value())});
|
||||
}
|
||||
|
||||
if (app_info.revision_code) {
|
||||
manifest_el->attributes.push_back(
|
||||
xml::Attribute{xml::kSchemaAndroid, "revisionCode",
|
||||
std::to_string(app_info.revision_code.value())});
|
||||
manifest_el->attributes.push_back(xml::Attribute{
|
||||
xml::kSchemaAndroid, "revisionCode", std::to_string(app_info.revision_code.value())});
|
||||
}
|
||||
|
||||
std::stringstream split_name;
|
||||
if (app_info.split_name) {
|
||||
split_name << app_info.split_name.value() << ".";
|
||||
}
|
||||
split_name << "config." << util::Joiner(constraints.configs, "_");
|
||||
|
||||
manifest_el->attributes.push_back(
|
||||
xml::Attribute{"", "split", split_name.str()});
|
||||
manifest_el->attributes.push_back(xml::Attribute{"", "split", split_name.str()});
|
||||
|
||||
std::unique_ptr<xml::Element> application_el =
|
||||
util::make_unique<xml::Element>();
|
||||
if (app_info.split_name) {
|
||||
manifest_el->attributes.push_back(
|
||||
xml::Attribute{"", "configForSplit", app_info.split_name.value()});
|
||||
}
|
||||
|
||||
std::unique_ptr<xml::Element> application_el = util::make_unique<xml::Element>();
|
||||
application_el->name = "application";
|
||||
application_el->attributes.push_back(
|
||||
xml::Attribute{xml::kSchemaAndroid, "hasCode", "false"});
|
||||
application_el->attributes.push_back(xml::Attribute{xml::kSchemaAndroid, "hasCode", "false"});
|
||||
|
||||
manifest_el->AppendChild(std::move(application_el));
|
||||
namespace_android->AppendChild(std::move(manifest_el));
|
||||
|
||||
@@ -29,10 +29,7 @@ using android::StringPiece;
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* This is how PackageManager builds class names from AndroidManifest.xml
|
||||
* entries.
|
||||
*/
|
||||
// This is how PackageManager builds class names from AndroidManifest.xml entries.
|
||||
static bool NameIsJavaClassName(xml::Element* el, xml::Attribute* attr,
|
||||
SourcePathDiagnostics* diag) {
|
||||
// We allow unqualified class names (ie: .HelloActivity)
|
||||
@@ -90,6 +87,36 @@ static xml::XmlNodeAction::ActionFuncWithDiag RequiredAndroidAttribute(const std
|
||||
};
|
||||
}
|
||||
|
||||
static bool AutoGenerateIsFeatureSplit(xml::Element* el, SourcePathDiagnostics* diag) {
|
||||
constexpr const char* kFeatureSplit = "featureSplit";
|
||||
constexpr const char* kIsFeatureSplit = "isFeatureSplit";
|
||||
|
||||
xml::Attribute* attr = el->FindAttribute({}, kFeatureSplit);
|
||||
if (attr != nullptr) {
|
||||
// Rewrite the featureSplit attribute to be "split". This is what the
|
||||
// platform recognizes.
|
||||
attr->name = "split";
|
||||
|
||||
// Now inject the android:isFeatureSplit="true" attribute.
|
||||
xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, kIsFeatureSplit);
|
||||
if (attr != nullptr) {
|
||||
if (!ResourceUtils::ParseBool(attr->value).value_or_default(false)) {
|
||||
// The isFeatureSplit attribute is false, which conflicts with the use
|
||||
// of "featureSplit".
|
||||
diag->Error(DiagMessage(el->line_number)
|
||||
<< "attribute 'featureSplit' used in <manifest> but 'android:isFeatureSplit' "
|
||||
"is not 'true'");
|
||||
return false;
|
||||
}
|
||||
|
||||
// The attribute is already there and set to true, nothing to do.
|
||||
} else {
|
||||
el->attributes.push_back(xml::Attribute{xml::kSchemaAndroid, kIsFeatureSplit, "true"});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) {
|
||||
xml::Attribute* attr = el->FindAttribute({}, "package");
|
||||
if (!attr) {
|
||||
@@ -97,31 +124,34 @@ static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) {
|
||||
<< "<manifest> tag is missing 'package' attribute");
|
||||
return false;
|
||||
} else if (ResourceUtils::IsReference(attr->value)) {
|
||||
diag->Error(
|
||||
DiagMessage(el->line_number)
|
||||
<< "attribute 'package' in <manifest> tag must not be a reference");
|
||||
diag->Error(DiagMessage(el->line_number)
|
||||
<< "attribute 'package' in <manifest> tag must not be a reference");
|
||||
return false;
|
||||
} else if (!util::IsJavaPackageName(attr->value)) {
|
||||
diag->Error(DiagMessage(el->line_number)
|
||||
<< "attribute 'package' in <manifest> tag is not a valid Java "
|
||||
"package name: '"
|
||||
<< "attribute 'package' in <manifest> tag is not a valid Java package name: '"
|
||||
<< attr->value << "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
attr = el->FindAttribute({}, "split");
|
||||
if (attr) {
|
||||
if (!util::IsJavaPackageName(attr->value)) {
|
||||
diag->Error(DiagMessage(el->line_number) << "attribute 'split' in <manifest> tag is not a "
|
||||
"valid split name");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The coreApp attribute in <manifest> is not a regular AAPT attribute, so type
|
||||
* checking on it is manual.
|
||||
*/
|
||||
// The coreApp attribute in <manifest> is not a regular AAPT attribute, so type
|
||||
// checking on it is manual.
|
||||
static bool FixCoreAppAttribute(xml::Element* el, SourcePathDiagnostics* diag) {
|
||||
if (xml::Attribute* attr = el->FindAttribute("", "coreApp")) {
|
||||
std::unique_ptr<BinaryPrimitive> result =
|
||||
ResourceUtils::TryParseBool(attr->value);
|
||||
std::unique_ptr<BinaryPrimitive> result = ResourceUtils::TryParseBool(attr->value);
|
||||
if (!result) {
|
||||
diag->Error(DiagMessage(el->line_number)
|
||||
<< "attribute coreApp must be a boolean");
|
||||
diag->Error(DiagMessage(el->line_number) << "attribute coreApp must be a boolean");
|
||||
return false;
|
||||
}
|
||||
attr->compiled_value = std::move(result);
|
||||
@@ -172,8 +202,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
|
||||
}
|
||||
|
||||
if (options_.rename_instrumentation_target_package) {
|
||||
if (!util::IsJavaPackageName(
|
||||
options_.rename_instrumentation_target_package.value())) {
|
||||
if (!util::IsJavaPackageName(options_.rename_instrumentation_target_package.value())) {
|
||||
diag->Error(DiagMessage()
|
||||
<< "invalid instrumentation target package override '"
|
||||
<< options_.rename_instrumentation_target_package.value()
|
||||
@@ -203,6 +232,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
|
||||
|
||||
// Manifest actions.
|
||||
xml::XmlNodeAction& manifest_action = (*executor)["manifest"];
|
||||
manifest_action.Action(AutoGenerateIsFeatureSplit);
|
||||
manifest_action.Action(VerifyManifest);
|
||||
manifest_action.Action(FixCoreAppAttribute);
|
||||
manifest_action.Action([&](xml::Element* el) -> bool {
|
||||
@@ -276,6 +306,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
|
||||
manifest_action["compatible-screens"]["screen"];
|
||||
manifest_action["supports-gl-texture"];
|
||||
manifest_action["meta-data"] = meta_data_action;
|
||||
manifest_action["uses-split"].Action(RequiredNameIsJavaPackage);
|
||||
|
||||
// Application actions.
|
||||
xml::XmlNodeAction& application_action = manifest_action["application"];
|
||||
@@ -311,15 +342,13 @@ class FullyQualifiedClassNameVisitor : public xml::Visitor {
|
||||
public:
|
||||
using xml::Visitor::Visit;
|
||||
|
||||
explicit FullyQualifiedClassNameVisitor(const StringPiece& package)
|
||||
: package_(package) {}
|
||||
explicit FullyQualifiedClassNameVisitor(const StringPiece& package) : package_(package) {}
|
||||
|
||||
void Visit(xml::Element* el) override {
|
||||
for (xml::Attribute& attr : el->attributes) {
|
||||
if (attr.namespace_uri == xml::kSchemaAndroid &&
|
||||
class_attributes_.find(attr.name) != class_attributes_.end()) {
|
||||
if (Maybe<std::string> new_value =
|
||||
util::GetFullyQualifiedClassName(package_, attr.value)) {
|
||||
if (Maybe<std::string> new_value = util::GetFullyQualifiedClassName(package_, attr.value)) {
|
||||
attr.value = std::move(new_value.value());
|
||||
}
|
||||
}
|
||||
@@ -334,8 +363,7 @@ class FullyQualifiedClassNameVisitor : public xml::Visitor {
|
||||
std::unordered_set<StringPiece> class_attributes_ = {"name"};
|
||||
};
|
||||
|
||||
static bool RenameManifestPackage(const StringPiece& package_override,
|
||||
xml::Element* manifest_el) {
|
||||
static bool RenameManifestPackage(const StringPiece& package_override, xml::Element* manifest_el) {
|
||||
xml::Attribute* attr = manifest_el->FindAttribute({}, "package");
|
||||
|
||||
// We've already verified that the manifest element is present, with a package
|
||||
@@ -358,8 +386,7 @@ bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((options_.min_sdk_version_default ||
|
||||
options_.target_sdk_version_default) &&
|
||||
if ((options_.min_sdk_version_default || options_.target_sdk_version_default) &&
|
||||
root->FindChild({}, "uses-sdk") == nullptr) {
|
||||
// Auto insert a <uses-sdk> element. This must be inserted before the
|
||||
// <application> tag. The device runtime PackageParser will make SDK version
|
||||
@@ -374,8 +401,7 @@ bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!executor.Execute(xml::XmlActionExecutorPolicy::kWhitelist,
|
||||
context->GetDiagnostics(), doc)) {
|
||||
if (!executor.Execute(xml::XmlActionExecutorPolicy::kWhitelist, context->GetDiagnostics(), doc)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -383,8 +409,7 @@ bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) {
|
||||
// Rename manifest package outside of the XmlActionExecutor.
|
||||
// We need to extract the old package name and FullyQualify all class
|
||||
// names.
|
||||
if (!RenameManifestPackage(options_.rename_manifest_package.value(),
|
||||
root)) {
|
||||
if (!RenameManifestPackage(options_.rename_manifest_package.value(), root)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user