1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "cmd/Util.h" 18 19 #include <vector> 20 21 #include "android-base/logging.h" 22 #include "androidfw/ConfigDescription.h" 23 #include "androidfw/Locale.h" 24 #include "ResourceUtils.h" 25 #include "ValueVisitor.h" 26 #include "split/TableSplitter.h" 27 28 #include "util/Util.h" 29 30 using ::android::ConfigDescription; 31 using ::android::LocaleValue; 32 using ::android::StringPiece; 33 using ::android::base::StringPrintf; 34 35 namespace aapt { 36 ParseFlag(std::optional<std::string_view> flag_text)37 std::optional<FeatureFlagAttribute> ParseFlag(std::optional<std::string_view> flag_text) { 38 if (!flag_text || flag_text->empty()) { 39 return {}; 40 } 41 FeatureFlagAttribute flag; 42 if (flag_text->starts_with('!')) { 43 flag.negated = true; 44 flag.name = flag_text->substr(1); 45 } else { 46 flag.name = flag_text.value(); 47 } 48 return flag; 49 } 50 GetFlagStatus(const std::optional<FeatureFlagAttribute> & flag,const FeatureFlagValues & feature_flag_values,std::string * out_err)51 std::optional<FlagStatus> GetFlagStatus(const std::optional<FeatureFlagAttribute>& flag, 52 const FeatureFlagValues& feature_flag_values, 53 std::string* out_err) { 54 if (!flag) { 55 return FlagStatus::NoFlag; 56 } 57 auto flag_it = feature_flag_values.find(flag->name); 58 if (flag_it == feature_flag_values.end()) { 59 *out_err = "Resource flag value undefined: " + flag->name; 60 return {}; 61 } 62 const auto& flag_properties = flag_it->second; 63 if (!flag_properties.read_only) { 64 *out_err = "Only read only flags may be used with resources: " + flag->name; 65 return {}; 66 } 67 if (!flag_properties.enabled.has_value()) { 68 *out_err = "Only flags with a value may be used with resources: " + flag->name; 69 return {}; 70 } 71 return (flag_properties.enabled.value() != flag->negated) ? FlagStatus::Enabled 72 : FlagStatus::Disabled; 73 } 74 ParseTargetDensityParameter(StringPiece arg,android::IDiagnostics * diag)75 std::optional<uint16_t> ParseTargetDensityParameter(StringPiece arg, android::IDiagnostics* diag) { 76 ConfigDescription preferred_density_config; 77 if (!ConfigDescription::Parse(arg, &preferred_density_config)) { 78 diag->Error(android::DiagMessage() 79 << "invalid density '" << arg << "' for --preferred-density option"); 80 return {}; 81 } 82 83 // Clear the version that can be automatically added. 84 preferred_density_config.sdkVersion = 0; 85 86 if (preferred_density_config.diff(ConfigDescription::DefaultConfig()) != 87 ConfigDescription::CONFIG_DENSITY) { 88 diag->Error(android::DiagMessage() << "invalid preferred density '" << arg << "'. " 89 << "Preferred density must only be a density value"); 90 return {}; 91 } 92 return preferred_density_config.density; 93 } 94 ParseSplitParameter(StringPiece arg,android::IDiagnostics * diag,std::string * out_path,SplitConstraints * out_split)95 bool ParseSplitParameter(StringPiece arg, android::IDiagnostics* diag, std::string* out_path, 96 SplitConstraints* out_split) { 97 CHECK(diag != nullptr); 98 CHECK(out_path != nullptr); 99 CHECK(out_split != nullptr); 100 101 #ifdef _WIN32 102 const char sSeparator = ';'; 103 #else 104 const char sSeparator = ':'; 105 #endif 106 107 std::vector<std::string> parts = util::Split(arg, sSeparator); 108 if (parts.size() != 2) { 109 diag->Error(android::DiagMessage() << "invalid split parameter '" << arg << "'"); 110 diag->Note(android::DiagMessage() << "should be --split path/to/output.apk" << sSeparator 111 << "<config>[,<config>...]."); 112 return false; 113 } 114 115 *out_path = parts[0]; 116 out_split->name = parts[1]; 117 for (StringPiece config_str : util::Tokenize(parts[1], ',')) { 118 ConfigDescription config; 119 if (!ConfigDescription::Parse(config_str, &config)) { 120 diag->Error(android::DiagMessage() 121 << "invalid config '" << config_str << "' in split parameter '" << arg << "'"); 122 return false; 123 } 124 out_split->configs.insert(config); 125 } 126 return true; 127 } 128 ParseConfigFilterParameters(const std::vector<std::string> & args,android::IDiagnostics * diag)129 std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std::string>& args, 130 android::IDiagnostics* diag) { 131 std::unique_ptr<AxisConfigFilter> filter = util::make_unique<AxisConfigFilter>(); 132 for (const std::string& config_arg : args) { 133 for (StringPiece config_str : util::Tokenize(config_arg, ',')) { 134 ConfigDescription config; 135 LocaleValue lv; 136 if (lv.InitFromFilterString(config_str)) { 137 lv.WriteTo(&config); 138 } else if (!ConfigDescription::Parse(config_str, &config)) { 139 diag->Error(android::DiagMessage() 140 << "invalid config '" << config_str << "' for -c option"); 141 return {}; 142 } 143 144 if (config.density != 0) { 145 diag->Warn(android::DiagMessage() << "ignoring density '" << config << "' for -c option"); 146 } else { 147 filter->AddConfig(config); 148 } 149 } 150 } 151 return std::move(filter); 152 } 153 ParseFeatureFlagsParameter(StringPiece arg,android::IDiagnostics * diag,FeatureFlagValues * out_feature_flag_values)154 bool ParseFeatureFlagsParameter(StringPiece arg, android::IDiagnostics* diag, 155 FeatureFlagValues* out_feature_flag_values) { 156 if (arg.empty()) { 157 return true; 158 } 159 160 for (StringPiece flag_and_value : util::Tokenize(arg, ',')) { 161 std::vector<std::string> parts = util::Split(flag_and_value, '='); 162 if (parts.empty()) { 163 continue; 164 } 165 166 if (parts.size() > 2) { 167 diag->Error(android::DiagMessage() 168 << "Invalid feature flag and optional value '" << flag_and_value 169 << "'. Must be in the format 'flag_name[:ro][=true|false]"); 170 return false; 171 } 172 173 StringPiece flag_name = util::TrimWhitespace(parts[0]); 174 if (flag_name.empty()) { 175 diag->Error(android::DiagMessage() << "No name given for one or more flags in: " << arg); 176 return false; 177 } 178 179 std::vector<std::string> name_parts = util::Split(flag_name, ':'); 180 if (name_parts.size() > 2) { 181 diag->Error(android::DiagMessage() 182 << "Invalid feature flag and optional value '" << flag_and_value 183 << "'. Must be in the format 'flag_name[:READ_ONLY|READ_WRITE][=true|false]"); 184 return false; 185 } 186 flag_name = name_parts[0]; 187 bool read_only = false; 188 if (name_parts.size() == 2) { 189 if (name_parts[1] == "ro" || name_parts[1] == "READ_ONLY") { 190 read_only = true; 191 } else if (name_parts[1] == "READ_WRITE") { 192 read_only = false; 193 } else { 194 diag->Error(android::DiagMessage() 195 << "Invalid feature flag and optional value '" << flag_and_value 196 << "'. Must be in the format 'flag_name[:READ_ONLY|READ_WRITE][=true|false]"); 197 return false; 198 } 199 } 200 201 std::optional<bool> flag_value = {}; 202 if (parts.size() == 2) { 203 StringPiece str_flag_value = util::TrimWhitespace(parts[1]); 204 if (!str_flag_value.empty()) { 205 flag_value = ResourceUtils::ParseBool(parts[1]); 206 if (!flag_value.has_value()) { 207 diag->Error(android::DiagMessage() << "Invalid value for feature flag '" << flag_and_value 208 << "'. Value must be 'true' or 'false'"); 209 return false; 210 } 211 } 212 } 213 214 auto ffp = FeatureFlagProperties{read_only, flag_value}; 215 if (auto [it, inserted] = out_feature_flag_values->try_emplace(std::string(flag_name), ffp); 216 !inserted) { 217 // We are allowing the same flag to appear multiple times, last value wins. 218 diag->Note(android::DiagMessage() 219 << "Value for feature flag '" << flag_name << "' was given more than once"); 220 it->second = ffp; 221 } 222 } 223 return true; 224 } 225 226 // Adjust the SplitConstraints so that their SDK version is stripped if it 227 // is less than or equal to the minSdk. Otherwise the resources that have had 228 // their SDK version stripped due to minSdk won't ever match. AdjustSplitConstraintsForMinSdk(int min_sdk,const std::vector<SplitConstraints> & split_constraints)229 std::vector<SplitConstraints> AdjustSplitConstraintsForMinSdk( 230 int min_sdk, const std::vector<SplitConstraints>& split_constraints) { 231 std::vector<SplitConstraints> adjusted_constraints; 232 adjusted_constraints.reserve(split_constraints.size()); 233 for (const SplitConstraints& constraints : split_constraints) { 234 SplitConstraints constraint; 235 for (const ConfigDescription& config : constraints.configs) { 236 const ConfigDescription &configToInsert = (config.sdkVersion <= min_sdk) 237 ? config.CopyWithoutSdkVersion() 238 : config; 239 // only add the config if it actually selects something 240 if (configToInsert != ConfigDescription::DefaultConfig()) { 241 constraint.configs.insert(configToInsert); 242 } 243 } 244 constraint.name = constraints.name; 245 adjusted_constraints.push_back(std::move(constraint)); 246 } 247 return adjusted_constraints; 248 } 249 CreateAttributeWithId(const ResourceId & id)250 static xml::AaptAttribute CreateAttributeWithId(const ResourceId& id) { 251 return xml::AaptAttribute(Attribute(), id); 252 } 253 CreateAndroidNamespaceDecl()254 static xml::NamespaceDecl CreateAndroidNamespaceDecl() { 255 xml::NamespaceDecl decl; 256 decl.prefix = "android"; 257 decl.uri = xml::kSchemaAndroid; 258 return decl; 259 } 260 261 // Returns a copy of 'name' which conforms to the regex '[a-zA-Z]+[a-zA-Z0-9_]*' by 262 // replacing nonconforming characters with underscores. 263 // 264 // See frameworks/base/core/java/android/content/pm/PackageParser.java which 265 // checks this at runtime. MakePackageSafeName(const std::string & name)266 std::string MakePackageSafeName(const std::string &name) { 267 std::string result(name); 268 bool first = true; 269 for (char &c : result) { 270 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { 271 first = false; 272 continue; 273 } 274 if (!first) { 275 if (c >= '0' && c <= '9') { 276 continue; 277 } 278 } 279 280 c = '_'; 281 first = false; 282 } 283 return result; 284 } 285 GenerateSplitManifest(const AppInfo & app_info,const SplitConstraints & constraints)286 std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, 287 const SplitConstraints& constraints) { 288 const ResourceId kVersionCode(0x0101021b); 289 const ResourceId kVersionCodeMajor(0x01010576); 290 const ResourceId kRevisionCode(0x010104d5); 291 const ResourceId kHasCode(0x0101000c); 292 293 std::unique_ptr<xml::Element> manifest_el = util::make_unique<xml::Element>(); 294 manifest_el->namespace_decls.push_back(CreateAndroidNamespaceDecl()); 295 manifest_el->name = "manifest"; 296 manifest_el->attributes.push_back(xml::Attribute{"", "package", app_info.package}); 297 298 if (app_info.version_code) { 299 const uint32_t version_code = app_info.version_code.value(); 300 manifest_el->attributes.push_back(xml::Attribute{ 301 xml::kSchemaAndroid, "versionCode", std::to_string(version_code), 302 CreateAttributeWithId(kVersionCode), 303 util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_DEC, version_code)}); 304 } 305 306 if (app_info.version_code_major) { 307 const uint32_t version_code_major = app_info.version_code_major.value(); 308 manifest_el->attributes.push_back(xml::Attribute{ 309 xml::kSchemaAndroid, "versionCodeMajor", std::to_string(version_code_major), 310 CreateAttributeWithId(kVersionCodeMajor), 311 util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_DEC, version_code_major)}); 312 } 313 314 if (app_info.revision_code) { 315 const uint32_t revision_code = app_info.revision_code.value(); 316 manifest_el->attributes.push_back(xml::Attribute{ 317 xml::kSchemaAndroid, "revisionCode", std::to_string(revision_code), 318 CreateAttributeWithId(kRevisionCode), 319 util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_DEC, revision_code)}); 320 } 321 322 std::stringstream split_name; 323 if (app_info.split_name) { 324 split_name << app_info.split_name.value() << "."; 325 } 326 std::vector<std::string> sanitized_config_names; 327 for (const auto &config : constraints.configs) { 328 sanitized_config_names.push_back(MakePackageSafeName(config.toString().c_str())); 329 } 330 split_name << "config." << util::Joiner(sanitized_config_names, "_"); 331 332 manifest_el->attributes.push_back(xml::Attribute{"", "split", split_name.str()}); 333 334 if (app_info.split_name) { 335 manifest_el->attributes.push_back( 336 xml::Attribute{"", "configForSplit", app_info.split_name.value()}); 337 } 338 339 // Splits may contain more configurations than originally desired (fall-back densities, etc.). 340 // This makes programmatic discovery of split targeting difficult. Encode the original 341 // split constraints intended for this split. 342 std::stringstream target_config_str; 343 target_config_str << util::Joiner(constraints.configs, ","); 344 manifest_el->attributes.push_back(xml::Attribute{"", "targetConfig", target_config_str.str()}); 345 346 std::unique_ptr<xml::Element> application_el = util::make_unique<xml::Element>(); 347 application_el->name = "application"; 348 application_el->attributes.push_back( 349 xml::Attribute{xml::kSchemaAndroid, "hasCode", "false", CreateAttributeWithId(kHasCode), 350 util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_BOOLEAN, 0u)}); 351 352 manifest_el->AppendChild(std::move(application_el)); 353 354 std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>(); 355 doc->root = std::move(manifest_el); 356 return doc; 357 } 358 ExtractCompiledString(const xml::Attribute & attr,std::string * out_error)359 static std::optional<std::string> ExtractCompiledString(const xml::Attribute& attr, 360 std::string* out_error) { 361 if (attr.compiled_value != nullptr) { 362 const String* compiled_str = ValueCast<String>(attr.compiled_value.get()); 363 if (compiled_str != nullptr) { 364 if (!compiled_str->value->empty()) { 365 return *compiled_str->value; 366 } else { 367 *out_error = "compiled value is an empty string"; 368 return {}; 369 } 370 } 371 *out_error = "compiled value is not a string"; 372 return {}; 373 } 374 375 // Fallback to the plain text value if there is one. 376 if (!attr.value.empty()) { 377 return attr.value; 378 } 379 *out_error = "value is an empty string"; 380 return {}; 381 } 382 ExtractCompiledInt(const xml::Attribute & attr,std::string * out_error)383 static std::optional<uint32_t> ExtractCompiledInt(const xml::Attribute& attr, 384 std::string* out_error) { 385 if (attr.compiled_value != nullptr) { 386 const BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr.compiled_value.get()); 387 if (compiled_prim != nullptr) { 388 if (compiled_prim->value.dataType >= android::Res_value::TYPE_FIRST_INT && 389 compiled_prim->value.dataType <= android::Res_value::TYPE_LAST_INT) { 390 return compiled_prim->value.data; 391 } 392 } 393 *out_error = "compiled value is not an integer"; 394 return {}; 395 } 396 397 // Fallback to the plain text value if there is one. 398 std::optional<uint32_t> integer = ResourceUtils::ParseInt(attr.value); 399 if (integer) { 400 return integer; 401 } 402 std::stringstream error_msg; 403 error_msg << "'" << attr.value << "' is not a valid integer"; 404 *out_error = error_msg.str(); 405 return {}; 406 } 407 ExtractSdkVersion(const xml::Attribute & attr,std::string * out_error)408 static std::optional<int> ExtractSdkVersion(const xml::Attribute& attr, std::string* out_error) { 409 if (attr.compiled_value != nullptr) { 410 const BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr.compiled_value.get()); 411 if (compiled_prim != nullptr) { 412 if (compiled_prim->value.dataType >= android::Res_value::TYPE_FIRST_INT && 413 compiled_prim->value.dataType <= android::Res_value::TYPE_LAST_INT) { 414 return compiled_prim->value.data; 415 } 416 *out_error = "compiled value is not an integer or string"; 417 return {}; 418 } 419 420 const String* compiled_str = ValueCast<String>(attr.compiled_value.get()); 421 if (compiled_str != nullptr) { 422 std::optional<int> sdk_version = ResourceUtils::ParseSdkVersion(*compiled_str->value); 423 if (sdk_version) { 424 return sdk_version; 425 } 426 427 *out_error = "compiled string value is not a valid SDK version"; 428 return {}; 429 } 430 *out_error = "compiled value is not an integer or string"; 431 return {}; 432 } 433 434 // Fallback to the plain text value if there is one. 435 std::optional<int> sdk_version = ResourceUtils::ParseSdkVersion(attr.value); 436 if (sdk_version) { 437 return sdk_version; 438 } 439 std::stringstream error_msg; 440 error_msg << "'" << attr.value << "' is not a valid SDK version"; 441 *out_error = error_msg.str(); 442 return {}; 443 } 444 ExtractAppInfoFromBinaryManifest(const xml::XmlResource & xml_res,android::IDiagnostics * diag)445 std::optional<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res, 446 android::IDiagnostics* diag) { 447 // Make sure the first element is <manifest> with package attribute. 448 const xml::Element* manifest_el = xml_res.root.get(); 449 if (manifest_el == nullptr) { 450 return {}; 451 } 452 453 AppInfo app_info; 454 455 if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") { 456 diag->Error(android::DiagMessage(xml_res.file.source) << "root tag must be <manifest>"); 457 return {}; 458 } 459 460 const xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package"); 461 if (!package_attr) { 462 diag->Error(android::DiagMessage(xml_res.file.source) 463 << "<manifest> must have a 'package' attribute"); 464 return {}; 465 } 466 467 std::string error_msg; 468 std::optional<std::string> maybe_package = ExtractCompiledString(*package_attr, &error_msg); 469 if (!maybe_package) { 470 diag->Error(android::DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) 471 << "invalid package name: " << error_msg); 472 return {}; 473 } 474 app_info.package = maybe_package.value(); 475 476 if (const xml::Attribute* version_code_attr = 477 manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) { 478 std::optional<uint32_t> maybe_code = ExtractCompiledInt(*version_code_attr, &error_msg); 479 if (!maybe_code) { 480 diag->Error(android::DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) 481 << "invalid android:versionCode: " << error_msg); 482 return {}; 483 } 484 app_info.version_code = maybe_code.value(); 485 } 486 487 if (const xml::Attribute* version_code_major_attr = 488 manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor")) { 489 std::optional<uint32_t> maybe_code = ExtractCompiledInt(*version_code_major_attr, &error_msg); 490 if (!maybe_code) { 491 diag->Error(android::DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) 492 << "invalid android:versionCodeMajor: " << error_msg); 493 return {}; 494 } 495 app_info.version_code_major = maybe_code.value(); 496 } 497 498 if (const xml::Attribute* revision_code_attr = 499 manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) { 500 std::optional<uint32_t> maybe_code = ExtractCompiledInt(*revision_code_attr, &error_msg); 501 if (!maybe_code) { 502 diag->Error(android::DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) 503 << "invalid android:revisionCode: " << error_msg); 504 return {}; 505 } 506 app_info.revision_code = maybe_code.value(); 507 } 508 509 if (const xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) { 510 std::optional<std::string> maybe_split_name = 511 ExtractCompiledString(*split_name_attr, &error_msg); 512 if (!maybe_split_name) { 513 diag->Error(android::DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) 514 << "invalid split name: " << error_msg); 515 return {}; 516 } 517 app_info.split_name = maybe_split_name.value(); 518 } 519 520 if (const xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) { 521 if (const xml::Attribute* min_sdk = 522 uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) { 523 std::optional<int> maybe_sdk = ExtractSdkVersion(*min_sdk, &error_msg); 524 if (!maybe_sdk) { 525 diag->Error(android::DiagMessage(xml_res.file.source.WithLine(uses_sdk_el->line_number)) 526 << "invalid android:minSdkVersion: " << error_msg); 527 return {}; 528 } 529 app_info.min_sdk_version = maybe_sdk.value(); 530 } 531 } 532 return app_info; 533 } 534 SetLongVersionCode(xml::Element * manifest,uint64_t version)535 void SetLongVersionCode(xml::Element* manifest, uint64_t version) { 536 // Write the low bits of the version code to android:versionCode 537 auto version_code = manifest->FindOrCreateAttribute(xml::kSchemaAndroid, "versionCode"); 538 version_code->value = StringPrintf("0x%08x", (uint32_t) (version & 0xffffffff)); 539 version_code->compiled_value = ResourceUtils::TryParseInt(version_code->value); 540 541 auto version_high = (uint32_t) (version >> 32); 542 if (version_high != 0) { 543 // Write the high bits of the version code to android:versionCodeMajor 544 auto version_major = manifest->FindOrCreateAttribute(xml::kSchemaAndroid, "versionCodeMajor"); 545 version_major->value = StringPrintf("0x%08x", version_high); 546 version_major->compiled_value = ResourceUtils::TryParseInt(version_major->value); 547 } else { 548 manifest->RemoveAttribute(xml::kSchemaAndroid, "versionCodeMajor"); 549 } 550 } 551 GetRegularExpression(const std::string & input)552 std::regex GetRegularExpression(const std::string &input) { 553 // Standard ECMAScript grammar. 554 std::regex case_insensitive( 555 input, std::regex_constants::ECMAScript); 556 return case_insensitive; 557 } 558 ParseResourceConfig(const std::string & content,IAaptContext * context,std::unordered_set<ResourceName> & out_resource_exclude_list,std::set<ResourceName> & out_name_collapse_exemptions,std::set<ResourceName> & out_path_shorten_exemptions)559 bool ParseResourceConfig(const std::string& content, IAaptContext* context, 560 std::unordered_set<ResourceName>& out_resource_exclude_list, 561 std::set<ResourceName>& out_name_collapse_exemptions, 562 std::set<ResourceName>& out_path_shorten_exemptions) { 563 for (StringPiece line : util::Tokenize(content, '\n')) { 564 line = util::TrimWhitespace(line); 565 if (line.empty()) { 566 continue; 567 } 568 569 auto split_line = util::Split(line, '#'); 570 if (split_line.size() < 2) { 571 context->GetDiagnostics()->Error(android::DiagMessage(line) << "No # found in line"); 572 return false; 573 } 574 StringPiece resource_string = split_line[0]; 575 StringPiece directives = split_line[1]; 576 ResourceNameRef resource_name; 577 if (!ResourceUtils::ParseResourceName(resource_string, &resource_name)) { 578 context->GetDiagnostics()->Error(android::DiagMessage(line) << "Malformed resource name"); 579 return false; 580 } 581 if (!resource_name.package.empty()) { 582 context->GetDiagnostics()->Error(android::DiagMessage(line) 583 << "Package set for resource. Only use type/name"); 584 return false; 585 } 586 for (StringPiece directive : util::Tokenize(directives, ',')) { 587 if (directive == "remove") { 588 out_resource_exclude_list.insert(resource_name.ToResourceName()); 589 } else if (directive == "no_collapse" || directive == "no_obfuscate") { 590 out_name_collapse_exemptions.insert(resource_name.ToResourceName()); 591 } else if (directive == "no_path_shorten") { 592 out_path_shorten_exemptions.insert(resource_name.ToResourceName()); 593 } 594 } 595 } 596 return true; 597 } 598 599 } // namespace aapt 600