Loading tools/aapt2/cmd/Link.cpp +22 −0 Original line number Diff line number Diff line Loading @@ -2513,6 +2513,28 @@ int LinkCommand::Action(const std::vector<std::string>& args) { } } // Parse the feature flag values. An argument that starts with '@' points to a file to read flag // values from. std::vector<std::string> all_feature_flags_args; for (const std::string& arg : feature_flags_args_) { if (util::StartsWith(arg, "@")) { const std::string path = arg.substr(1, arg.size() - 1); std::string error; if (!file::AppendArgsFromFile(path, &all_feature_flags_args, &error)) { context.GetDiagnostics()->Error(android::DiagMessage(path) << error); return 1; } } else { all_feature_flags_args.push_back(arg); } } for (const std::string& arg : all_feature_flags_args) { if (ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) { return 1; } } if (context.GetPackageType() != PackageType::kStaticLib && stable_id_file_path_) { if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path_.value(), &options_.stable_id_map)) { Loading tools/aapt2/cmd/Link.h +7 −0 Original line number Diff line number Diff line Loading @@ -17,11 +17,17 @@ #ifndef AAPT2_LINK_H #define AAPT2_LINK_H #include <optional> #include <regex> #include <string> #include <unordered_map> #include <unordered_set> #include <vector> #include "Command.h" #include "Resource.h" #include "androidfw/IDiagnostics.h" #include "cmd/Util.h" #include "format/binary/TableFlattener.h" #include "format/proto/ProtoSerialize.h" #include "link/ManifestFixer.h" Loading Loading @@ -72,6 +78,7 @@ struct LinkOptions { bool use_sparse_encoding = false; std::unordered_set<std::string> extensions_to_not_compress; std::optional<std::regex> regex_to_not_compress; FeatureFlagValues feature_flag_values; // Static lib options. bool no_static_lib_packages = false; Loading tools/aapt2/cmd/Util.cpp +50 −0 Original line number Diff line number Diff line Loading @@ -113,6 +113,56 @@ std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std return std::move(filter); } bool ParseFeatureFlagsParameter(StringPiece arg, android::IDiagnostics* diag, FeatureFlagValues* out_feature_flag_values) { if (arg.empty()) { return true; } for (StringPiece flag_and_value : util::Tokenize(arg, ',')) { std::vector<std::string> parts = util::Split(flag_and_value, '='); if (parts.empty()) { continue; } if (parts.size() > 2) { diag->Error(android::DiagMessage() << "Invalid feature flag and optional value '" << flag_and_value << "'. Must be in the format 'flag_name[=true|false]"); return false; } StringPiece flag_name = util::TrimWhitespace(parts[0]); if (flag_name.empty()) { diag->Error(android::DiagMessage() << "No name given for one or more flags in: " << arg); return false; } std::optional<bool> flag_value = {}; if (parts.size() == 2) { StringPiece str_flag_value = util::TrimWhitespace(parts[1]); if (!str_flag_value.empty()) { flag_value = ResourceUtils::ParseBool(parts[1]); if (!flag_value.has_value()) { diag->Error(android::DiagMessage() << "Invalid value for feature flag '" << flag_and_value << "'. Value must be 'true' or 'false'"); return false; } } } if (auto [it, inserted] = out_feature_flag_values->try_emplace(std::string(flag_name), flag_value); !inserted) { // We are allowing the same flag to appear multiple times, last value wins. diag->Note(android::DiagMessage() << "Value for feature flag '" << flag_name << "' was given more than once"); it->second = flag_value; } } return true; } // Adjust the SplitConstraints so that their SDK version is stripped if it // is less than or equal to the minSdk. Otherwise the resources that have had // their SDK version stripped due to minSdk won't ever match. Loading tools/aapt2/cmd/Util.h +14 −0 Original line number Diff line number Diff line Loading @@ -17,8 +17,13 @@ #ifndef AAPT_SPLIT_UTIL_H #define AAPT_SPLIT_UTIL_H #include <functional> #include <map> #include <memory> #include <optional> #include <regex> #include <set> #include <string> #include <unordered_set> #include "AppInfo.h" Loading @@ -32,6 +37,8 @@ namespace aapt { using FeatureFlagValues = std::map<std::string, std::optional<bool>, std::less<>>; // Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc). // Returns Nothing and logs a human friendly error message if the string was not legal. std::optional<uint16_t> ParseTargetDensityParameter(android::StringPiece arg, Loading @@ -48,6 +55,13 @@ bool ParseSplitParameter(android::StringPiece arg, android::IDiagnostics* diag, std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std::string>& args, android::IDiagnostics* diag); // Parses a feature flags parameter, which can contain one or more pairs of flag names and optional // values, and fills in `out_feature_flag_values` with the parsed values. The pairs in the argument // are separated by ',' and the name is separated from the value by '=' if there is a value given. // Example arg: "flag1=true,flag2=false,flag3=,flag4" where flag3 and flag4 have no given value. bool ParseFeatureFlagsParameter(android::StringPiece arg, android::IDiagnostics* diag, FeatureFlagValues* out_feature_flag_values); // Adjust the SplitConstraints so that their SDK version is stripped if it // is less than or equal to the min_sdk. Otherwise the resources that have had // their SDK version stripped due to min_sdk won't ever match. Loading tools/aapt2/cmd/Util_test.cpp +46 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ #include "util/Files.h" using ::android::ConfigDescription; using testing::Pair; using testing::UnorderedElementsAre; namespace aapt { Loading Loading @@ -354,6 +355,51 @@ TEST (UtilTest, ParseSplitParameters) { EXPECT_CONFIG_EQ(constraints, expected_configuration); } TEST(UtilTest, ParseFeatureFlagsParameter_Empty) { auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); FeatureFlagValues feature_flag_values; ASSERT_TRUE(ParseFeatureFlagsParameter("", diagnostics, &feature_flag_values)); EXPECT_TRUE(feature_flag_values.empty()); } TEST(UtilTest, ParseFeatureFlagsParameter_TooManyParts) { auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); FeatureFlagValues feature_flag_values; ASSERT_FALSE(ParseFeatureFlagsParameter("foo=bar=baz", diagnostics, &feature_flag_values)); } TEST(UtilTest, ParseFeatureFlagsParameter_NoNameGiven) { auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); FeatureFlagValues feature_flag_values; ASSERT_FALSE(ParseFeatureFlagsParameter("foo=true,=false", diagnostics, &feature_flag_values)); } TEST(UtilTest, ParseFeatureFlagsParameter_InvalidValue) { auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); FeatureFlagValues feature_flag_values; ASSERT_FALSE(ParseFeatureFlagsParameter("foo=true,bar=42", diagnostics, &feature_flag_values)); } TEST(UtilTest, ParseFeatureFlagsParameter_DuplicateFlag) { auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); FeatureFlagValues feature_flag_values; ASSERT_TRUE( ParseFeatureFlagsParameter("foo=true,bar=true,foo=false", diagnostics, &feature_flag_values)); EXPECT_THAT(feature_flag_values, UnorderedElementsAre(Pair("foo", std::optional<bool>(false)), Pair("bar", std::optional<bool>(true)))); } TEST(UtilTest, ParseFeatureFlagsParameter_Valid) { auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); FeatureFlagValues feature_flag_values; ASSERT_TRUE(ParseFeatureFlagsParameter("foo= true, bar =FALSE,baz=, quux", diagnostics, &feature_flag_values)); EXPECT_THAT(feature_flag_values, UnorderedElementsAre(Pair("foo", std::optional<bool>(true)), Pair("bar", std::optional<bool>(false)), Pair("baz", std::nullopt), Pair("quux", std::nullopt))); } TEST (UtilTest, AdjustSplitConstraintsForMinSdk) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); Loading Loading
tools/aapt2/cmd/Link.cpp +22 −0 Original line number Diff line number Diff line Loading @@ -2513,6 +2513,28 @@ int LinkCommand::Action(const std::vector<std::string>& args) { } } // Parse the feature flag values. An argument that starts with '@' points to a file to read flag // values from. std::vector<std::string> all_feature_flags_args; for (const std::string& arg : feature_flags_args_) { if (util::StartsWith(arg, "@")) { const std::string path = arg.substr(1, arg.size() - 1); std::string error; if (!file::AppendArgsFromFile(path, &all_feature_flags_args, &error)) { context.GetDiagnostics()->Error(android::DiagMessage(path) << error); return 1; } } else { all_feature_flags_args.push_back(arg); } } for (const std::string& arg : all_feature_flags_args) { if (ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) { return 1; } } if (context.GetPackageType() != PackageType::kStaticLib && stable_id_file_path_) { if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path_.value(), &options_.stable_id_map)) { Loading
tools/aapt2/cmd/Link.h +7 −0 Original line number Diff line number Diff line Loading @@ -17,11 +17,17 @@ #ifndef AAPT2_LINK_H #define AAPT2_LINK_H #include <optional> #include <regex> #include <string> #include <unordered_map> #include <unordered_set> #include <vector> #include "Command.h" #include "Resource.h" #include "androidfw/IDiagnostics.h" #include "cmd/Util.h" #include "format/binary/TableFlattener.h" #include "format/proto/ProtoSerialize.h" #include "link/ManifestFixer.h" Loading Loading @@ -72,6 +78,7 @@ struct LinkOptions { bool use_sparse_encoding = false; std::unordered_set<std::string> extensions_to_not_compress; std::optional<std::regex> regex_to_not_compress; FeatureFlagValues feature_flag_values; // Static lib options. bool no_static_lib_packages = false; Loading
tools/aapt2/cmd/Util.cpp +50 −0 Original line number Diff line number Diff line Loading @@ -113,6 +113,56 @@ std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std return std::move(filter); } bool ParseFeatureFlagsParameter(StringPiece arg, android::IDiagnostics* diag, FeatureFlagValues* out_feature_flag_values) { if (arg.empty()) { return true; } for (StringPiece flag_and_value : util::Tokenize(arg, ',')) { std::vector<std::string> parts = util::Split(flag_and_value, '='); if (parts.empty()) { continue; } if (parts.size() > 2) { diag->Error(android::DiagMessage() << "Invalid feature flag and optional value '" << flag_and_value << "'. Must be in the format 'flag_name[=true|false]"); return false; } StringPiece flag_name = util::TrimWhitespace(parts[0]); if (flag_name.empty()) { diag->Error(android::DiagMessage() << "No name given for one or more flags in: " << arg); return false; } std::optional<bool> flag_value = {}; if (parts.size() == 2) { StringPiece str_flag_value = util::TrimWhitespace(parts[1]); if (!str_flag_value.empty()) { flag_value = ResourceUtils::ParseBool(parts[1]); if (!flag_value.has_value()) { diag->Error(android::DiagMessage() << "Invalid value for feature flag '" << flag_and_value << "'. Value must be 'true' or 'false'"); return false; } } } if (auto [it, inserted] = out_feature_flag_values->try_emplace(std::string(flag_name), flag_value); !inserted) { // We are allowing the same flag to appear multiple times, last value wins. diag->Note(android::DiagMessage() << "Value for feature flag '" << flag_name << "' was given more than once"); it->second = flag_value; } } return true; } // Adjust the SplitConstraints so that their SDK version is stripped if it // is less than or equal to the minSdk. Otherwise the resources that have had // their SDK version stripped due to minSdk won't ever match. Loading
tools/aapt2/cmd/Util.h +14 −0 Original line number Diff line number Diff line Loading @@ -17,8 +17,13 @@ #ifndef AAPT_SPLIT_UTIL_H #define AAPT_SPLIT_UTIL_H #include <functional> #include <map> #include <memory> #include <optional> #include <regex> #include <set> #include <string> #include <unordered_set> #include "AppInfo.h" Loading @@ -32,6 +37,8 @@ namespace aapt { using FeatureFlagValues = std::map<std::string, std::optional<bool>, std::less<>>; // Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc). // Returns Nothing and logs a human friendly error message if the string was not legal. std::optional<uint16_t> ParseTargetDensityParameter(android::StringPiece arg, Loading @@ -48,6 +55,13 @@ bool ParseSplitParameter(android::StringPiece arg, android::IDiagnostics* diag, std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std::string>& args, android::IDiagnostics* diag); // Parses a feature flags parameter, which can contain one or more pairs of flag names and optional // values, and fills in `out_feature_flag_values` with the parsed values. The pairs in the argument // are separated by ',' and the name is separated from the value by '=' if there is a value given. // Example arg: "flag1=true,flag2=false,flag3=,flag4" where flag3 and flag4 have no given value. bool ParseFeatureFlagsParameter(android::StringPiece arg, android::IDiagnostics* diag, FeatureFlagValues* out_feature_flag_values); // Adjust the SplitConstraints so that their SDK version is stripped if it // is less than or equal to the min_sdk. Otherwise the resources that have had // their SDK version stripped due to min_sdk won't ever match. Loading
tools/aapt2/cmd/Util_test.cpp +46 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ #include "util/Files.h" using ::android::ConfigDescription; using testing::Pair; using testing::UnorderedElementsAre; namespace aapt { Loading Loading @@ -354,6 +355,51 @@ TEST (UtilTest, ParseSplitParameters) { EXPECT_CONFIG_EQ(constraints, expected_configuration); } TEST(UtilTest, ParseFeatureFlagsParameter_Empty) { auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); FeatureFlagValues feature_flag_values; ASSERT_TRUE(ParseFeatureFlagsParameter("", diagnostics, &feature_flag_values)); EXPECT_TRUE(feature_flag_values.empty()); } TEST(UtilTest, ParseFeatureFlagsParameter_TooManyParts) { auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); FeatureFlagValues feature_flag_values; ASSERT_FALSE(ParseFeatureFlagsParameter("foo=bar=baz", diagnostics, &feature_flag_values)); } TEST(UtilTest, ParseFeatureFlagsParameter_NoNameGiven) { auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); FeatureFlagValues feature_flag_values; ASSERT_FALSE(ParseFeatureFlagsParameter("foo=true,=false", diagnostics, &feature_flag_values)); } TEST(UtilTest, ParseFeatureFlagsParameter_InvalidValue) { auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); FeatureFlagValues feature_flag_values; ASSERT_FALSE(ParseFeatureFlagsParameter("foo=true,bar=42", diagnostics, &feature_flag_values)); } TEST(UtilTest, ParseFeatureFlagsParameter_DuplicateFlag) { auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); FeatureFlagValues feature_flag_values; ASSERT_TRUE( ParseFeatureFlagsParameter("foo=true,bar=true,foo=false", diagnostics, &feature_flag_values)); EXPECT_THAT(feature_flag_values, UnorderedElementsAre(Pair("foo", std::optional<bool>(false)), Pair("bar", std::optional<bool>(true)))); } TEST(UtilTest, ParseFeatureFlagsParameter_Valid) { auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); FeatureFlagValues feature_flag_values; ASSERT_TRUE(ParseFeatureFlagsParameter("foo= true, bar =FALSE,baz=, quux", diagnostics, &feature_flag_values)); EXPECT_THAT(feature_flag_values, UnorderedElementsAre(Pair("foo", std::optional<bool>(true)), Pair("bar", std::optional<bool>(false)), Pair("baz", std::nullopt), Pair("quux", std::nullopt))); } TEST (UtilTest, AdjustSplitConstraintsForMinSdk) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); Loading