diff options
| author | Inseob Kim <inseob@google.com> | 2019-07-30 18:36:28 +0900 |
|---|---|---|
| committer | Inseob Kim <inseob@google.com> | 2019-08-09 19:01:19 +0900 |
| commit | a9565afdb024c77b8192f0b7720847ee92a058d9 (patch) | |
| tree | 5fdbb6b80416b59237b0885264b4f773a242b733 | |
| parent | 0dfe426db387e86f659dec16687dff4249d782b1 (diff) | |
| download | platform_system_tools_sysprop-a9565afdb024c77b8192f0b7720847ee92a058d9.tar.gz platform_system_tools_sysprop-a9565afdb024c77b8192f0b7720847ee92a058d9.tar.bz2 platform_system_tools_sysprop-a9565afdb024c77b8192f0b7720847ee92a058d9.zip | |
Implement sysprop_library API stability check
sysprop_library now checks the API stability itself, cutting dependency
on java_sdk_library. Under the directory {module_dir}/api,
{module_name}-current.txt and {module_name}-latest.txt hold API
signatures.
When sysprop_library is built, or a user run "m {module_name}-check-api"
command, API check is performed. First, current.txt must have exactly
same signature with built sysprop_library module. Second, current.txt
must be compatible with latest.txt.
Build system emits a handy error message to generate/update those API
files, in case of missing or mismatching. Also, a script file for
freezing API files is introduced.
Bug: 131637873
Test: 1) m && boot blueline
Test: 2) m {sysprop_library} performs API check
Test: 3) manual test for check-api, freezing api
Change-Id: I95e0e38a24e78bb9526248ea7efc12d7fbf2d35a
| -rw-r--r-- | Android.bp | 20 | ||||
| -rw-r--r-- | JavaGen.cpp | 58 | ||||
| -rw-r--r-- | JavaMain.cpp | 20 | ||||
| -rw-r--r-- | include/JavaGen.h | 5 | ||||
| -rw-r--r-- | stub/android/os/SystemProperties.java | 28 | ||||
| -rw-r--r-- | tests/JavaGenTest.cpp | 192 |
6 files changed, 245 insertions, 78 deletions
@@ -57,3 +57,23 @@ cc_test_host { "JavaGen.cpp", "tests/*.cpp"], } + +java_defaults { + name: "sysprop-library-stub-defaults", + srcs: [ + "stub/android/os/SystemProperties.java", + ], + installable: false, + sdk_version: "core_current", +} + +java_library { + name: "sysprop-library-stub-platform", + defaults: ["sysprop-library-stub-defaults"], +} + +java_library { + name: "sysprop-library-stub-vendor", + defaults: ["sysprop-library-stub-defaults"], + soc_specific: true, +} diff --git a/JavaGen.cpp b/JavaGen.cpp index ba7409d..5cf9c5c 100644 --- a/JavaGen.cpp +++ b/JavaGen.cpp @@ -40,8 +40,7 @@ namespace { constexpr const char* kIndent = " "; constexpr const char* kJavaFileImports = - R"(import android.annotation.SystemApi; -import android.os.SystemProperties; + R"(import android.os.SystemProperties; import java.util.ArrayList; import java.util.function.Function; @@ -157,8 +156,8 @@ std::string GetJavaPackageName(const sysprop::Properties& props); std::string GetJavaClassName(const sysprop::Properties& props); std::string GetParsingExpression(const sysprop::Property& prop); std::string GetFormattingExpression(const sysprop::Property& prop); -void WriteJavaAnnotation(CodeWriter& writer, sysprop::Scope scope); -std::string GenerateJavaClass(const sysprop::Properties& props); +std::string GenerateJavaClass(const sysprop::Properties& props, + sysprop::Scope scope); std::string GetJavaEnumTypeName(const sysprop::Property& prop) { return ApiNameToIdentifier(prop.api_name()) + "_values"; @@ -277,33 +276,8 @@ std::string GetJavaClassName(const sysprop::Properties& props) { return module.substr(module.rfind('.') + 1); } -void WriteJavaAnnotation(CodeWriter& writer, sysprop::Scope scope) { - switch (scope) { - // This is to restrict access from modules linking against SDK. - // TODO(b/131637873): remove this after cutting dependency on - // java_sdk_library - case sysprop::Public: - writer.Write("/** @hide */\n"); - writer.Write("@SystemApi\n"); - break; - case sysprop::Internal: - writer.Write("/** @hide */\n"); - break; - default: - break; - } -} - -std::string GenerateJavaClass(const sysprop::Properties& props) { - sysprop::Scope classScope = sysprop::Internal; - - for (int i = 0; i < props.prop_size(); ++i) { - // Get least restrictive scope among props. For example, if all props - // are internal, class can be as well internal. However, class should - // be public or system if at least one prop is so. - classScope = std::min(classScope, props.prop(i).scope()); - } - +std::string GenerateJavaClass(const sysprop::Properties& props, + sysprop::Scope scope) { std::string package_name = GetJavaPackageName(props); std::string class_name = GetJavaClassName(props); @@ -311,24 +285,23 @@ std::string GenerateJavaClass(const sysprop::Properties& props) { writer.Write("%s", kGeneratedFileFooterComments); writer.Write("package %s;\n\n", package_name.c_str()); writer.Write("%s", kJavaFileImports); - WriteJavaAnnotation(writer, classScope); writer.Write("public final class %s {\n", class_name.c_str()); writer.Indent(); writer.Write("private %s () {}\n\n", class_name.c_str()); writer.Write("%s", kJavaParsersAndFormatters); for (int i = 0; i < props.prop_size(); ++i) { - writer.Write("\n"); - const sysprop::Property& prop = props.prop(i); + // skip if scope is internal and we are generating public class + if (prop.scope() > scope) continue; + + writer.Write("\n"); + std::string prop_id = ApiNameToIdentifier(prop.api_name()).c_str(); std::string prop_type = GetJavaTypeName(prop); if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) { - if (prop.scope() != classScope) { - WriteJavaAnnotation(writer, prop.scope()); - } writer.Write("public static enum %s {\n", GetJavaEnumTypeName(prop).c_str()); writer.Indent(); @@ -361,9 +334,6 @@ std::string GenerateJavaClass(const sysprop::Properties& props) { writer.Write("}\n\n"); } - if (prop.scope() != classScope) { - WriteJavaAnnotation(writer, prop.scope()); - } if (prop.deprecated()) { writer.Write("@Deprecated\n"); } @@ -389,11 +359,8 @@ std::string GenerateJavaClass(const sysprop::Properties& props) { writer.Write("}\n"); } - if (prop.access() != sysprop::Readonly) { + if (prop.access() != sysprop::Readonly && scope == sysprop::Internal) { writer.Write("\n"); - if (classScope != sysprop::Internal) { - WriteJavaAnnotation(writer, sysprop::Internal); - } if (prop.deprecated()) { writer.Write("@Deprecated\n"); } @@ -417,6 +384,7 @@ std::string GenerateJavaClass(const sysprop::Properties& props) { } // namespace Result<void> GenerateJavaLibrary(const std::string& input_file_path, + sysprop::Scope scope, const std::string& java_output_dir) { sysprop::Properties props; @@ -426,7 +394,7 @@ Result<void> GenerateJavaLibrary(const std::string& input_file_path, return res.error(); } - std::string java_result = GenerateJavaClass(props); + std::string java_result = GenerateJavaClass(props, scope); std::string package_name = GetJavaPackageName(props); std::string java_package_dir = java_output_dir + "/" + std::regex_replace(package_name, kRegexDot, "/"); diff --git a/JavaMain.cpp b/JavaMain.cpp index 5dc5d27..723e2d3 100644 --- a/JavaMain.cpp +++ b/JavaMain.cpp @@ -25,6 +25,7 @@ #include <getopt.h> #include "JavaGen.h" +#include "sysprop.pb.h" using android::base::Errorf; using android::base::Result; @@ -34,10 +35,14 @@ namespace { struct Arguments { std::string input_file_path; std::string java_output_dir; + sysprop::Scope scope; }; [[noreturn]] void PrintUsage(const char* exe_name) { - std::printf("Usage: %s [--java-output-dir dir] sysprop_file\n", exe_name); + std::printf( + "Usage: %s --scope (internal|public) --java-output-dir dir " + "sysprop_file\n", + exe_name); std::exit(EXIT_FAILURE); } @@ -45,6 +50,7 @@ Result<void> ParseArgs(int argc, char* argv[], Arguments* args) { for (;;) { static struct option long_options[] = { {"java-output-dir", required_argument, 0, 'j'}, + {"scope", required_argument, 0, 's'}, }; int opt = getopt_long_only(argc, argv, "", long_options, nullptr); @@ -54,6 +60,15 @@ Result<void> ParseArgs(int argc, char* argv[], Arguments* args) { case 'j': args->java_output_dir = optarg; break; + case 's': + if (strcmp(optarg, "public") == 0) { + args->scope = sysprop::Scope::Public; + } else if (strcmp(optarg, "internal") == 0) { + args->scope = sysprop::Scope::Internal; + } else { + return Errorf("Invalid option {} for scope", optarg); + } + break; default: PrintUsage(argv[0]); } @@ -83,7 +98,8 @@ int main(int argc, char* argv[]) { PrintUsage(argv[0]); } - if (auto res = GenerateJavaLibrary(args.input_file_path, args.java_output_dir); + if (auto res = GenerateJavaLibrary(args.input_file_path, args.scope, + args.java_output_dir); !res) { LOG(FATAL) << "Error during generating java sysprop from " << args.input_file_path << ": " << res.error(); diff --git a/include/JavaGen.h b/include/JavaGen.h index 71dc407..9794626 100644 --- a/include/JavaGen.h +++ b/include/JavaGen.h @@ -19,5 +19,8 @@ #include <android-base/result.h> #include <string> +#include "sysprop.pb.h" + android::base::Result<void> GenerateJavaLibrary( - const std::string& input_file_path, const std::string& java_output_dir);
\ No newline at end of file + const std::string& input_file_path, sysprop::Scope scope, + const std::string& java_output_dir);
\ No newline at end of file diff --git a/stub/android/os/SystemProperties.java b/stub/android/os/SystemProperties.java new file mode 100644 index 0000000..7309abc --- /dev/null +++ b/stub/android/os/SystemProperties.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +// Stub for cutting dependency from sysprop_library to framework.jar +public class SystemProperties { + public static String get(String key) { + return null; + } + public static void set(String key, String val) { + } + private SystemProperties() { + } +} diff --git a/tests/JavaGenTest.cpp b/tests/JavaGenTest.cpp index f148e2c..a96d103 100644 --- a/tests/JavaGenTest.cpp +++ b/tests/JavaGenTest.cpp @@ -100,12 +100,155 @@ prop { } )"; -constexpr const char* kExpectedJavaOutput = +constexpr const char* kExpectedPublicOutput = + R"(// Generated by the sysprop generator. DO NOT EDIT! + +package com.somecompany; + +import android.os.SystemProperties; + +import java.util.ArrayList; +import java.util.function.Function; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.StringJoiner; +import java.util.stream.Collectors; + +public final class TestProperties { + private TestProperties () {} + + private static Boolean tryParseBoolean(String str) { + switch (str.toLowerCase(Locale.US)) { + case "1": + case "true": + return Boolean.TRUE; + case "0": + case "false": + return Boolean.FALSE; + default: + return null; + } + } + + private static Integer tryParseInteger(String str) { + try { + return Integer.valueOf(str); + } catch (NumberFormatException e) { + return null; + } + } + + private static Long tryParseLong(String str) { + try { + return Long.valueOf(str); + } catch (NumberFormatException e) { + return null; + } + } + + private static Double tryParseDouble(String str) { + try { + return Double.valueOf(str); + } catch (NumberFormatException e) { + return null; + } + } + + private static String tryParseString(String str) { + return "".equals(str) ? null : str; + } + + private static <T extends Enum<T>> T tryParseEnum(Class<T> enumType, String str) { + try { + return Enum.valueOf(enumType, str.toUpperCase(Locale.US)); + } catch (IllegalArgumentException e) { + return null; + } + } + + private static <T> List<T> tryParseList(Function<String, T> elementParser, String str) { + if ("".equals(str)) return new ArrayList<>(); + + List<T> ret = new ArrayList<>(); + + for (String element : str.split(",")) { + ret.add(elementParser.apply(element)); + } + + return ret; + } + + private static <T extends Enum<T>> List<T> tryParseEnumList(Class<T> enumType, String str) { + if ("".equals(str)) return new ArrayList<>(); + + List<T> ret = new ArrayList<>(); + + for (String element : str.split(",")) { + ret.add(tryParseEnum(enumType, element)); + } + + return ret; + } + + private static <T> String formatList(List<T> list) { + StringJoiner joiner = new StringJoiner(","); + + for (T element : list) { + joiner.add(element == null ? "" : element.toString()); + } + + return joiner.toString(); + } + + private static <T extends Enum<T>> String formatEnumList(List<T> list, Function<T, String> elementFormatter) { + StringJoiner joiner = new StringJoiner(","); + + for (T element : list) { + joiner.add(element == null ? "" : elementFormatter.apply(element)); + } + + return joiner.toString(); + } + + public static Optional<Integer> test_int() { + String value = SystemProperties.get("vendor.test_int"); + return Optional.ofNullable(tryParseInteger(value)); + } + + public static Optional<String> test_string() { + String value = SystemProperties.get("vendor.test.string"); + return Optional.ofNullable(tryParseString(value)); + } + + public static Optional<Boolean> test_BOOLeaN() { + String value = SystemProperties.get("ro.vendor.test.b"); + return Optional.ofNullable(tryParseBoolean(value)); + } + + public static Optional<Long> vendor_os_test_long() { + String value = SystemProperties.get("vendor.vendor.os_test-long"); + return Optional.ofNullable(tryParseLong(value)); + } + + public static List<Integer> test_list_int() { + String value = SystemProperties.get("vendor.test_list_int"); + return tryParseList(v -> tryParseInteger(v), value); + } + + @Deprecated + public static List<String> test_strlist() { + String value = SystemProperties.get("vendor.test.strlist"); + return tryParseList(v -> tryParseString(v), value); + } +} +)"; + +constexpr const char* kExpectedInternalOutput = R"(// Generated by the sysprop generator. DO NOT EDIT! package com.somecompany; -import android.annotation.SystemApi; import android.os.SystemProperties; import java.util.ArrayList; @@ -116,8 +259,6 @@ import java.util.Optional; import java.util.StringJoiner; import java.util.stream.Collectors; -/** @hide */ -@SystemApi public final class TestProperties { private TestProperties () {} @@ -214,13 +355,11 @@ public final class TestProperties { return joiner.toString(); } - /** @hide */ public static Optional<Double> test_double() { String value = SystemProperties.get("vendor.test_double"); return Optional.ofNullable(tryParseDouble(value)); } - /** @hide */ public static void test_double(Double value) { SystemProperties.set("vendor.test_double", value == null ? "" : value.toString()); } @@ -230,7 +369,6 @@ public final class TestProperties { return Optional.ofNullable(tryParseInteger(value)); } - /** @hide */ public static void test_int(Integer value) { SystemProperties.set("vendor.test_int", value == null ? "" : value.toString()); } @@ -240,12 +378,10 @@ public final class TestProperties { return Optional.ofNullable(tryParseString(value)); } - /** @hide */ public static void test_string(String value) { SystemProperties.set("vendor.test.string", value == null ? "" : value.toString()); } - /** @hide */ public static enum test_enum_values { A("a"), B("b"), @@ -263,13 +399,11 @@ public final class TestProperties { } } - /** @hide */ public static Optional<test_enum_values> test_enum() { String value = SystemProperties.get("vendor.test.enum"); return Optional.ofNullable(tryParseEnum(test_enum_values.class, value)); } - /** @hide */ public static void test_enum(test_enum_values value) { SystemProperties.set("vendor.test.enum", value == null ? "" : value.getPropValue()); } @@ -279,7 +413,6 @@ public final class TestProperties { return Optional.ofNullable(tryParseBoolean(value)); } - /** @hide */ public static void test_BOOLeaN(Boolean value) { SystemProperties.set("ro.vendor.test.b", value == null ? "" : value.toString()); } @@ -289,18 +422,15 @@ public final class TestProperties { return Optional.ofNullable(tryParseLong(value)); } - /** @hide */ public static void vendor_os_test_long(Long value) { SystemProperties.set("vendor.vendor.os_test-long", value == null ? "" : value.toString()); } - /** @hide */ public static List<Double> test_double_list() { String value = SystemProperties.get("vendor.test_double_list"); return tryParseList(v -> tryParseDouble(v), value); } - /** @hide */ public static void test_double_list(List<Double> value) { SystemProperties.set("vendor.test_double_list", value == null ? "" : formatList(value)); } @@ -310,7 +440,6 @@ public final class TestProperties { return tryParseList(v -> tryParseInteger(v), value); } - /** @hide */ public static void test_list_int(List<Integer> value) { SystemProperties.set("vendor.test_list_int", value == null ? "" : formatList(value)); } @@ -321,13 +450,11 @@ public final class TestProperties { return tryParseList(v -> tryParseString(v), value); } - /** @hide */ @Deprecated public static void test_strlist(List<String> value) { SystemProperties.set("vendor.test.strlist", value == null ? "" : formatList(value)); } - /** @hide */ public static enum el_values { ENU("enu"), MVA("mva"), @@ -341,14 +468,12 @@ public final class TestProperties { } } - /** @hide */ @Deprecated public static List<el_values> el() { String value = SystemProperties.get("vendor.el"); return tryParseEnumList(el_values.class, value); } - /** @hide */ @Deprecated public static void el(List<el_values> value) { SystemProperties.set("vendor.el", value == null ? "" : formatEnumList(value, el_values::getPropValue)); @@ -371,17 +496,24 @@ TEST(SyspropTest, JavaGenTest) { TemporaryDir temp_dir; - ASSERT_TRUE(GenerateJavaLibrary(temp_file.path, temp_dir.path)); + std::pair<sysprop::Scope, const char*> tests[] = { + {sysprop::Scope::Internal, kExpectedInternalOutput}, + {sysprop::Scope::Public, kExpectedPublicOutput}, + }; + + for (auto [scope, expected_output] : tests) { + ASSERT_TRUE(GenerateJavaLibrary(temp_file.path, scope, temp_dir.path)); - std::string java_output_path = - temp_dir.path + "/com/somecompany/TestProperties.java"s; + std::string java_output_path = + temp_dir.path + "/com/somecompany/TestProperties.java"s; - std::string java_output; - ASSERT_TRUE( - android::base::ReadFileToString(java_output_path, &java_output, true)); - EXPECT_EQ(java_output, kExpectedJavaOutput); + std::string java_output; + ASSERT_TRUE( + android::base::ReadFileToString(java_output_path, &java_output, true)); + EXPECT_EQ(java_output, expected_output); - unlink(java_output_path.c_str()); - rmdir((temp_dir.path + "/com/somecompany"s).c_str()); - rmdir((temp_dir.path + "/com"s).c_str()); + unlink(java_output_path.c_str()); + rmdir((temp_dir.path + "/com/somecompany"s).c_str()); + rmdir((temp_dir.path + "/com"s).c_str()); + } } |
