Verify the locale format within a localeConfig file
Leverage the existing mechanism for validating the locale format to verify whether the locale format of the compiled localeConfig file follows the BCP47 reqular expressions
Bug: 208943132
Test: 1. atest aapt2_tests
2. Build the app bundle with the local aapt2 in the Android Studio
a. Make sure the app can be built pass with AGP 7.3.0-alpha06.
b. Make sure the app can be built pass by using the local aapt2 with gradle: set the android.aapt2FromMavenOverride=<path> variable in gradle.properties.
c. Revove the property above in gradle.properties then apply AGP 7.3.0-alpha07. Make sure the appt2 shows error(AAPT: error: failed to load XML file: No such file or directory) when building the app.
d. Add the locale aapt2 with gradle: set the android.aapt2FromMavenOverride=<path> variable in gradle.properties. Make sure the app can be built pass.
Change-Id: Ifa4b9123d36f9c2d450fdf16e1f52963962d7d81
This commit is contained in:
@@ -1066,6 +1066,112 @@ class Linker {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VerifyLocaleFormat(xml::XmlResource* manifest, android::IDiagnostics* diag) {
|
||||
// Skip it if the Manifest doesn't declare the localeConfig attribute within the <application>
|
||||
// element.
|
||||
const xml::Element* application = manifest->root->FindChild("", "application");
|
||||
if (!application) {
|
||||
return true;
|
||||
}
|
||||
const xml::Attribute* localeConfig =
|
||||
application->FindAttribute(xml::kSchemaAndroid, "localeConfig");
|
||||
if (!localeConfig) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Deserialize XML from the compiled file
|
||||
if (localeConfig->compiled_value) {
|
||||
const auto localeconfig_reference = ValueCast<Reference>(localeConfig->compiled_value.get());
|
||||
const auto localeconfig_entry =
|
||||
ResolveTableEntry(context_, &final_table_, localeconfig_reference);
|
||||
if (!localeconfig_entry) {
|
||||
context_->GetDiagnostics()->Error(
|
||||
android::DiagMessage(localeConfig->compiled_value->GetSource())
|
||||
<< "no localeConfig entry");
|
||||
return false;
|
||||
}
|
||||
for (const auto& value : localeconfig_entry->values) {
|
||||
const FileReference* file_ref = ValueCast<FileReference>(value->value.get());
|
||||
if (!file_ref) {
|
||||
context_->GetDiagnostics()->Error(
|
||||
android::DiagMessage(localeConfig->compiled_value->GetSource())
|
||||
<< "no file reference");
|
||||
return false;
|
||||
}
|
||||
io::IFile* file = file_ref->file;
|
||||
if (!file) {
|
||||
context_->GetDiagnostics()->Error(android::DiagMessage(file_ref->GetSource())
|
||||
<< "file not found");
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<io::IData> data = file->OpenAsData();
|
||||
if (!data) {
|
||||
context_->GetDiagnostics()->Error(android::DiagMessage(file->GetSource())
|
||||
<< "failed to open file");
|
||||
return false;
|
||||
}
|
||||
pb::XmlNode pb_xml_node;
|
||||
if (!pb_xml_node.ParseFromArray(data->data(), static_cast<int>(data->size()))) {
|
||||
context_->GetDiagnostics()->Error(android::DiagMessage(file->GetSource())
|
||||
<< "failed to parse proto XML");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string error;
|
||||
std::unique_ptr<xml::XmlResource> localeConfig_xml =
|
||||
DeserializeXmlResourceFromPb(pb_xml_node, &error);
|
||||
if (!localeConfig_xml) {
|
||||
context_->GetDiagnostics()->Error(android::DiagMessage(file->GetSource())
|
||||
<< "failed to deserialize proto XML: " << error);
|
||||
return false;
|
||||
}
|
||||
xml::Element* localeConfig_el = xml::FindRootElement(localeConfig_xml->root.get());
|
||||
if (!localeConfig_el) {
|
||||
diag->Error(android::DiagMessage(file->GetSource()) << "no root tag defined");
|
||||
return false;
|
||||
}
|
||||
if (localeConfig_el->name != "locale-config") {
|
||||
diag->Error(android::DiagMessage(file->GetSource())
|
||||
<< "invalid element name: " << localeConfig_el->name
|
||||
<< ", expected: locale-config");
|
||||
return false;
|
||||
}
|
||||
for (const xml::Element* child_el : localeConfig_el->GetChildElements()) {
|
||||
if (child_el->name == "locale") {
|
||||
if (const xml::Attribute* locale_name_attr =
|
||||
child_el->FindAttribute(xml::kSchemaAndroid, "name")) {
|
||||
const std::string& locale_name = locale_name_attr->value;
|
||||
const std::string valid_name = ConvertToBCP47Tag(locale_name);
|
||||
// Start to verify the locale format
|
||||
ConfigDescription config;
|
||||
if (!ConfigDescription::Parse(valid_name, &config)) {
|
||||
diag->Error(android::DiagMessage(file->GetSource())
|
||||
<< "invalid configuration: " << locale_name);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
diag->Error(android::DiagMessage(file->GetSource())
|
||||
<< "the attribute android:name is not found");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
diag->Error(android::DiagMessage(file->GetSource())
|
||||
<< "invalid element name: " << child_el->name << ", expected: locale");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string ConvertToBCP47Tag(const std::string& locale) {
|
||||
std::string bcp47tag = "b+";
|
||||
bcp47tag += locale;
|
||||
std::replace(bcp47tag.begin(), bcp47tag.end(), '-', '+');
|
||||
return bcp47tag;
|
||||
}
|
||||
|
||||
std::unique_ptr<IArchiveWriter> MakeArchiveWriter(const StringPiece& out) {
|
||||
if (options_.output_to_directory) {
|
||||
return CreateDirectoryArchiveWriter(context_->GetDiagnostics(), out);
|
||||
@@ -2195,6 +2301,10 @@ class Linker {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!VerifyLocaleFormat(manifest_xml.get(), context_->GetDiagnostics())) {
|
||||
return 1;
|
||||
};
|
||||
|
||||
if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), &final_table_)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -785,4 +785,175 @@ TEST_F(LinkTest, MacroSubstitution) {
|
||||
EXPECT_THAT(xml_attrs[1].value, Eq("Hello World!"));
|
||||
}
|
||||
|
||||
TEST_F(LinkTest, LocaleConfigVerification) {
|
||||
StdErrDiagnostics diag;
|
||||
const std::string compiled_files_dir = GetTestPath("compiled");
|
||||
|
||||
// Normal case
|
||||
ASSERT_TRUE(CompileFile(GetTestPath("res/xml/locales_config.xml"), R"(
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name="en-US"/>
|
||||
<locale android:name="pt"/>
|
||||
<locale android:name="es-419"/>
|
||||
<locale android:name="zh-Hans-SG"/>
|
||||
</locale-config>)",
|
||||
compiled_files_dir, &diag));
|
||||
|
||||
const std::string localeconfig_manifest = GetTestPath("localeconfig_manifest.xml");
|
||||
WriteFile(localeconfig_manifest, android::base::StringPrintf(R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.aapt2.app">
|
||||
|
||||
<application
|
||||
android:localeConfig="@xml/locales_config">
|
||||
</application>
|
||||
</manifest>)"));
|
||||
|
||||
const std::string out_apk = GetTestPath("out.apk");
|
||||
|
||||
auto link_args = LinkCommandBuilder(this)
|
||||
.SetManifestFile(localeconfig_manifest)
|
||||
.AddCompiledResDir(compiled_files_dir, &diag)
|
||||
.Build(out_apk);
|
||||
ASSERT_TRUE(Link(link_args, &diag));
|
||||
|
||||
// Empty locale list
|
||||
ASSERT_TRUE(CompileFile(GetTestPath("res/xml/empty_locales_config.xml"), R"(
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
</locale-config>)",
|
||||
compiled_files_dir, &diag));
|
||||
|
||||
const std::string empty_localeconfig_manifest = GetTestPath("empty_localeconfig_manifest.xml");
|
||||
WriteFile(empty_localeconfig_manifest, android::base::StringPrintf(R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.aapt2.app">
|
||||
|
||||
<application
|
||||
android:localeConfig="@xml/empty_locales_config">
|
||||
</application>
|
||||
</manifest>)"));
|
||||
|
||||
auto link1_args = LinkCommandBuilder(this)
|
||||
.SetManifestFile(empty_localeconfig_manifest)
|
||||
.AddCompiledResDir(compiled_files_dir, &diag)
|
||||
.Build(out_apk);
|
||||
ASSERT_TRUE(Link(link1_args, &diag));
|
||||
}
|
||||
|
||||
TEST_F(LinkTest, LocaleConfigWrongTag) {
|
||||
StdErrDiagnostics diag;
|
||||
const std::string compiled_files_dir = GetTestPath("compiled");
|
||||
|
||||
// Invalid element: locale1-config
|
||||
ASSERT_TRUE(CompileFile(GetTestPath("res/xml/wrong_locale_config.xml"), R"(
|
||||
<locale1-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name="en-US"/>
|
||||
<locale android:name="pt"/>
|
||||
<locale android:name="es-419"/>
|
||||
<locale android:name="zh-Hans-SG"/>
|
||||
</locale1-config>)",
|
||||
compiled_files_dir, &diag));
|
||||
|
||||
const std::string locale1config_manifest = GetTestPath("locale1config_manifest.xml");
|
||||
WriteFile(locale1config_manifest, android::base::StringPrintf(R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.aapt2.app">
|
||||
|
||||
<application
|
||||
android:localeConfig="@xml/wrong_locale_config">
|
||||
</application>
|
||||
</manifest>)"));
|
||||
|
||||
const std::string out_apk = GetTestPath("out.apk");
|
||||
auto link_args = LinkCommandBuilder(this)
|
||||
.SetManifestFile(locale1config_manifest)
|
||||
.AddCompiledResDir(compiled_files_dir, &diag)
|
||||
.Build(out_apk);
|
||||
ASSERT_FALSE(Link(link_args, &diag));
|
||||
|
||||
// Invalid element: locale1
|
||||
ASSERT_TRUE(CompileFile(GetTestPath("res/xml/wrong_locale.xml"), R"(
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale1 android:name="en-US"/>
|
||||
<locale android:name="pt"/>
|
||||
<locale android:name="es-419"/>
|
||||
<locale android:name="zh-Hans-SG"/>
|
||||
</locale-config>)",
|
||||
compiled_files_dir, &diag));
|
||||
|
||||
const std::string locale1_manifest = GetTestPath("locale1_manifest.xml");
|
||||
WriteFile(locale1_manifest, android::base::StringPrintf(R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.aapt2.app">
|
||||
|
||||
<application
|
||||
android:localeConfig="@xml/wrong_locale">
|
||||
</application>
|
||||
</manifest>)"));
|
||||
|
||||
auto link1_args = LinkCommandBuilder(this)
|
||||
.SetManifestFile(locale1_manifest)
|
||||
.AddCompiledResDir(compiled_files_dir, &diag)
|
||||
.Build(out_apk);
|
||||
ASSERT_FALSE(Link(link1_args, &diag));
|
||||
|
||||
// Invalid attribute: android:name1
|
||||
ASSERT_TRUE(CompileFile(GetTestPath("res/xml/wrong_attribute.xml"), R"(
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name1="en-US"/>
|
||||
<locale android:name="pt"/>
|
||||
<locale android:name="es-419"/>
|
||||
<locale android:name="zh-Hans-SG"/>
|
||||
</locale-config>)",
|
||||
compiled_files_dir, &diag));
|
||||
|
||||
const std::string wrong_attribute_manifest = GetTestPath("wrong_attribute_manifest.xml");
|
||||
WriteFile(wrong_attribute_manifest, android::base::StringPrintf(R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.aapt2.app">
|
||||
|
||||
<application
|
||||
android:localeConfig="@xml/wrong_attribute">
|
||||
</application>
|
||||
</manifest>)"));
|
||||
|
||||
auto link2_args = LinkCommandBuilder(this)
|
||||
.SetManifestFile(wrong_attribute_manifest)
|
||||
.AddCompiledResDir(compiled_files_dir, &diag)
|
||||
.Build(out_apk);
|
||||
ASSERT_FALSE(Link(link2_args, &diag));
|
||||
}
|
||||
|
||||
TEST_F(LinkTest, LocaleConfigWrongLocaleFormat) {
|
||||
StdErrDiagnostics diag;
|
||||
const std::string compiled_files_dir = GetTestPath("compiled");
|
||||
|
||||
// Invalid locale: en-U
|
||||
ASSERT_TRUE(CompileFile(GetTestPath("res/xml/wrong_locale.xml"), R"(
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name="en-U"/>
|
||||
<locale android:name="pt"/>
|
||||
<locale android:name="es-419"/>
|
||||
<locale android:name="zh-Hans-SG"/>
|
||||
</locale-config>)",
|
||||
compiled_files_dir, &diag));
|
||||
|
||||
const std::string wrong_locale_manifest = GetTestPath("wrong_locale_manifest.xml");
|
||||
WriteFile(wrong_locale_manifest, android::base::StringPrintf(R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.aapt2.app">
|
||||
|
||||
<application
|
||||
android:localeConfig="@xml/wrong_locale">
|
||||
</application>
|
||||
</manifest>)"));
|
||||
|
||||
const std::string out_apk = GetTestPath("out.apk");
|
||||
auto link_args = LinkCommandBuilder(this)
|
||||
.SetManifestFile(wrong_locale_manifest)
|
||||
.AddCompiledResDir(compiled_files_dir, &diag)
|
||||
.Build(out_apk);
|
||||
ASSERT_FALSE(Link(link_args, &diag));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
Binary file not shown.
@@ -105,9 +105,9 @@ bool CommandTestFixture::Link(const std::vector<std::string>& args, android::IDi
|
||||
}
|
||||
|
||||
// Link against the android SDK
|
||||
std::string android_sdk = file::BuildPath({android::base::GetExecutableDirectory(),
|
||||
"integration-tests", "CommandTests",
|
||||
"android-28.jar"});
|
||||
std::string android_sdk =
|
||||
file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests", "CommandTests",
|
||||
"android-33.jar"});
|
||||
link_args.insert(link_args.end(), {"-I", android_sdk});
|
||||
|
||||
return LinkCommand(diag).Execute(link_args, &std::cerr) == 0;
|
||||
@@ -121,9 +121,9 @@ bool CommandTestFixture::Link(const std::vector<std::string>& args,
|
||||
}
|
||||
|
||||
// Link against the android SDK
|
||||
std::string android_sdk = file::BuildPath({android::base::GetExecutableDirectory(),
|
||||
"integration-tests", "CommandTests",
|
||||
"android-28.jar"});
|
||||
std::string android_sdk =
|
||||
file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests", "CommandTests",
|
||||
"android-33.jar"});
|
||||
link_args.insert(link_args.end(), {"-I", android_sdk});
|
||||
|
||||
// Add the files from the compiled resources directory to the link file arguments
|
||||
|
||||
Reference in New Issue
Block a user