diff options
| -rw-r--r-- | derive_classpath/derive_classpath.cpp | 83 | ||||
| -rw-r--r-- | derive_classpath/derive_classpath.h | 4 | ||||
| -rw-r--r-- | derive_classpath/derive_classpath_test.cpp | 34 | ||||
| -rw-r--r-- | derive_classpath/main.cpp | 59 |
4 files changed, 137 insertions, 43 deletions
diff --git a/derive_classpath/derive_classpath.cpp b/derive_classpath/derive_classpath.cpp index 0ff246c..0c9c361 100644 --- a/derive_classpath/derive_classpath.cpp +++ b/derive_classpath/derive_classpath.cpp @@ -34,34 +34,51 @@ using Classpaths = std::unordered_map<Classpath, Filepaths>; static const std::regex kBindMountedApex("^/apex/[^/]+@[0-9]+/"); static const std::regex kApexPathRegex("(/apex/[^/]+)/"); -// Defines the order of individual fragments to be merged for BOOTCLASSPATH: -// 1. Jars in ART module always come first; -// 2. Jars defined as part of /system/etc/classpaths; -// 3. Jars defined in all non-ART apexes that expose /apex/*/etc/classpaths fragments. -// -// Notes: -// - Relative order in the individual fragment files is not changed when merging. -// - If a fragment file is matched by multiple globs, the first one is used; i.e. ART module -// fragment is only parsed once, even if there is a "/apex/*/" pattern later. -// - If there are multiple files matched for a glob pattern with wildcards, the results are sorted -// by pathname (default glob behaviour); i.e. all fragment files are sorted within a single -// "pattern block". -static const std::vector<std::string> kBootclasspathFragmentGlobPatterns = { - // ART module is a special case and must come first before any other classpath entries. - "/apex/com.android.art/etc/classpaths/bootclasspath.pb", - "/system/etc/classpaths/bootclasspath.pb", - "/apex/*/etc/classpaths/bootclasspath.pb", -}; +std::vector<std::string> getBootclasspathFragmentGlobPatterns(const Args& args) { + // Defines the order of individual fragments to be merged for BOOTCLASSPATH: + // 1. Jars in ART module always come first; + // 2. Jars defined as part of /system/etc/classpaths; + // 3. Jars defined in all non-ART apexes that expose /apex/*/etc/classpaths fragments. + // + // Notes: + // - Relative order in the individual fragment files is not changed when merging. + // - If a fragment file is matched by multiple globs, the first one is used; i.e. ART module + // fragment is only parsed once, even if there is a "/apex/*/" pattern later. + // - If there are multiple files matched for a glob pattern with wildcards, the results are sorted + // by pathname (default glob behaviour); i.e. all fragment files are sorted within a single + // "pattern block". + std::vector<std::string> patterns = { + // ART module is a special case and must come first before any other classpath entries. + "/apex/com.android.art/etc/classpaths/bootclasspath.pb", + }; + if (args.system_bootclasspath_fragment.empty()) { + patterns.emplace_back("/system/etc/classpaths/bootclasspath.pb"); + } else { + // TODO: Avoid applying glob(3) expansion later to this path. Although the caller should not + // provide a path that contains '*', it can technically happen. Instead of checking the string + // format, we should just avoid the glob(3) for this string. + patterns.emplace_back(args.system_bootclasspath_fragment); + } + patterns.emplace_back("/apex/*/etc/classpaths/bootclasspath.pb"); + return patterns; +} -// Defines the order of individual fragments to be merged for SYSTEMSERVERCLASSPATH. -// -// ART system server jars are not special in this case, and are considered to be part of all the -// other apexes that may expose system server jars. -// -// All notes from kBootclasspathFragmentGlobPatterns apply here. -static const std::vector<std::string> kSystemserverclasspathFragmentGlobPatterns = { - "/system/etc/classpaths/systemserverclasspath.pb", - "/apex/*/etc/classpaths/systemserverclasspath.pb", +std::vector<std::string> getSystemserverclasspathFragmentGlobPatterns(const Args& args) { + // Defines the order of individual fragments to be merged for SYSTEMSERVERCLASSPATH. + // + // ART system server jars are not special in this case, and are considered to be part of all the + // other apexes that may expose system server jars. + // + // All notes from getBootclasspathFragmentGlobPatterns apply here. + std::vector<std::string> patterns; + if (args.system_systemserverclasspath_fragment.empty()) { + patterns.emplace_back("/system/etc/classpaths/systemserverclasspath.pb"); + } else { + // TODO: Avoid applying glob(3) expansion later to this path. See above. + patterns.emplace_back(args.system_systemserverclasspath_fragment); + } + patterns.emplace_back("/apex/*/etc/classpaths/systemserverclasspath.pb"); + return patterns; }; // Finds all classpath fragment files that match the glob pattern and appends them to `fragments`. @@ -149,15 +166,15 @@ std::string GetAllowedJarPathPrefix(const std::string& fragment_path) { } // Finds and parses all classpath fragments on device matching given glob patterns. -bool ParseFragments(const std::string& globPatternPrefix, Classpaths& classpaths, bool boot_jars) { +bool ParseFragments(const Args& args, Classpaths& classpaths, bool boot_jars) { LOG(INFO) << "ParseFragments for " << (boot_jars ? "bootclasspath" : "systemserverclasspath"); - auto glob_patterns = - boot_jars ? kBootclasspathFragmentGlobPatterns : kSystemserverclasspathFragmentGlobPatterns; + auto glob_patterns = boot_jars ? getBootclasspathFragmentGlobPatterns(args) + : getSystemserverclasspathFragmentGlobPatterns(args); Filepaths fragments; for (const auto& pattern : glob_patterns) { - if (!GlobClasspathFragments(&fragments, globPatternPrefix + pattern)) { + if (!GlobClasspathFragments(&fragments, args.glob_pattern_prefix + pattern)) { return false; } } @@ -194,11 +211,11 @@ bool GenerateClasspathExports(const Args& args) { << "derive_classpath must only be run on Android 12 or above"; Classpaths classpaths; - if (!ParseFragments(args.glob_pattern_prefix, classpaths, /*boot_jars=*/true)) { + if (!ParseFragments(args, classpaths, /*boot_jars=*/true)) { LOG(ERROR) << "Failed to parse BOOTCLASSPATH fragments"; return false; } - if (!ParseFragments(args.glob_pattern_prefix, classpaths, /*boot_jars=*/false)) { + if (!ParseFragments(args, classpaths, /*boot_jars=*/false)) { LOG(ERROR) << "Failed to parse SYSTEMSERVERCLASSPATH fragments"; return false; } diff --git a/derive_classpath/derive_classpath.h b/derive_classpath/derive_classpath.h index 9b53254..b0716f3 100644 --- a/derive_classpath/derive_classpath.h +++ b/derive_classpath/derive_classpath.h @@ -27,6 +27,10 @@ constexpr std::string_view kGeneratedClasspathExportsFilepath = "/data/system/en struct Args { std::string_view output_path; + // Alternative *classpath.pb files if provided. + std::string system_bootclasspath_fragment; + std::string system_systemserverclasspath_fragment; + // Test only. glob_pattern_prefix is appended to each glob pattern to allow adding mock configs in // /data/local/tmp for example. std::string glob_pattern_prefix; diff --git a/derive_classpath/derive_classpath_test.cpp b/derive_classpath/derive_classpath_test.cpp index 277a452..6e503f3 100644 --- a/derive_classpath/derive_classpath_test.cpp +++ b/derive_classpath/derive_classpath_test.cpp @@ -216,6 +216,40 @@ TEST_F(DeriveClasspathTest, CustomOutputLocation) { EXPECT_EQ(expectedJars, exportValue); } +// Test alternative .pb for bootclasspath and systemclasspath. +TEST_F(DeriveClasspathTest, CustomInputLocation) { + AddJarToClasspath("/other", "/other/bcp-jar", BOOTCLASSPATH); + AddJarToClasspath("/other", "/other/systemserver-jar", SYSTEMSERVERCLASSPATH); + AddJarToClasspath("/apex/com.android.art", "/apex/com.android.art/javalib/art", BOOTCLASSPATH); + AddJarToClasspath("/apex/com.android.foo", "/apex/com.android.foo/javalib/foo", BOOTCLASSPATH); + AddJarToClasspath("/apex/com.android.baz", "/apex/com.android.baz/javalib/baz", + SYSTEMSERVERCLASSPATH); + + Args args = default_args_with_test_dir_; + args.system_bootclasspath_fragment = "/other/etc/classpaths/bootclasspath.pb"; + args.system_systemserverclasspath_fragment = "/other/etc/classpaths/systemserverclasspath.pb"; + + ASSERT_TRUE(GenerateClasspathExports(args)); + + const std::vector<std::string> exportLines = ParseExportsFile(); + + std::vector<std::string> splitExportLine; + splitExportLine = SplitClasspathExportLine(exportLines[0]); + EXPECT_EQ("BOOTCLASSPATH", splitExportLine[1]); + const std::string expectedBcpJars( + "/apex/com.android.art/javalib/art" + ":/other/bcp-jar" + ":/apex/com.android.foo/javalib/foo"); + EXPECT_EQ(expectedBcpJars, splitExportLine[2]); + + splitExportLine = SplitClasspathExportLine(exportLines[2]); + EXPECT_EQ("SYSTEMSERVERCLASSPATH", splitExportLine[1]); + const std::string expectedSystemServerJars( + "/other/systemserver-jar" + ":/apex/com.android.baz/javalib/baz"); + EXPECT_EQ(expectedSystemServerJars, splitExportLine[2]); +} + // Test output location that can't be written to. TEST_F(DeriveClasspathTest, NonWriteableOutputLocation) { AddJarToClasspath("/apex/com.android.art", "/apex/com.android.art/javalib/art", BOOTCLASSPATH); diff --git a/derive_classpath/main.cpp b/derive_classpath/main.cpp index d589680..8807cdb 100644 --- a/derive_classpath/main.cpp +++ b/derive_classpath/main.cpp @@ -21,18 +21,57 @@ #include "derive_classpath.h" -int main(int argc, char** argv) { - // Default args - android::derive_classpath::Args args = { - .output_path = android::derive_classpath::kGeneratedClasspathExportsFilepath, - .glob_pattern_prefix = "", - }; - if (argc == 1) { +bool ArgumentMatches(std::string_view argument, std::string_view prefix, std::string_view* value) { + if (android::base::StartsWith(argument, prefix)) { + *value = argument.substr(prefix.size()); + return true; + } + return false; +} + +// Command line flags need to be considered as a de facto API since there may be callers outside +// of the SdkExtensions APEX, which needs to run on older Android versions. For example, otapreopt +// currently executes derive_classpath with a single output file. When changing the flags, make sure +// it won't break on older Android. +bool ParseArgs(android::derive_classpath::Args& args, int argc, char** argv) { + // Parse flags + std::vector<std::string_view> positional_args; + for (int i = 1; i < argc; ++i) { + const std::string_view arg = argv[i]; + std::string_view value; + if (ArgumentMatches(arg, "--bootclasspath-fragment=", &value)) { + if (!args.system_bootclasspath_fragment.empty()) { + LOG(ERROR) << "Duplicated flag --bootclasspath-fragment is specified"; + return false; + } + args.system_bootclasspath_fragment = value; + } else if (ArgumentMatches(arg, "--systemserverclasspath-fragment=", &value)) { + if (!args.system_bootclasspath_fragment.empty()) { + LOG(ERROR) << "Duplicated flag --systemserverclasspath-fragment is specified"; + return false; + } + args.system_systemserverclasspath_fragment = value; + } else { + positional_args.emplace_back(arg); + } + } + + // Handle positional args + if (positional_args.size() == 0) { args.output_path = android::derive_classpath::kGeneratedClasspathExportsFilepath; - } else if (argc == 2) { - args.output_path = argv[1]; + } else if (positional_args.size() == 1) { + args.output_path = positional_args[0]; } else { - LOG(ERROR) << "too many arguments " << argc; + LOG(ERROR) << "Unrecognized positional arguments: " + << android::base::Join(positional_args, ' '); + return false; + } + return true; +} + +int main(int argc, char** argv) { + android::derive_classpath::Args args; + if (!ParseArgs(args, argc, argv)) { return EXIT_FAILURE; } if (!android::derive_classpath::GenerateClasspathExports(args)) { |
