platform_build_soong/java/droidstubs_test.go

463 lines
13 KiB
Go
Raw Normal View History

// Copyright 2021 Google Inc. All rights reserved.
//
// 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 java
import (
"fmt"
"reflect"
"regexp"
"strings"
"testing"
"android/soong/android"
"github.com/google/blueprint/proptools"
)
func TestDroidstubs(t *testing.T) {
ctx, _ := testJavaWithFS(t, `
droiddoc_exported_dir {
name: "droiddoc-templates-sdk",
path: ".",
}
droidstubs {
name: "bar-stubs",
srcs: ["bar-doc/a.java"],
api_levels_annotations_dirs: ["droiddoc-templates-sdk"],
api_levels_annotations_enabled: true,
}
droidstubs {
name: "bar-stubs-other",
srcs: ["bar-doc/a.java"],
high_mem: true,
api_levels_annotations_dirs: ["droiddoc-templates-sdk"],
api_levels_annotations_enabled: true,
api_levels_jar_filename: "android.other.jar",
}
droidstubs {
name: "stubs-applying-api-versions",
srcs: ["bar-doc/a.java"],
api_levels_module: "bar-stubs-other",
}
`,
map[string][]byte{
"bar-doc/a.java": nil,
})
testcases := []struct {
moduleName string
expectedJarFilename string
generate_xml bool
high_mem bool
}{
{
moduleName: "bar-stubs",
generate_xml: true,
expectedJarFilename: "android.jar",
high_mem: false,
},
{
moduleName: "bar-stubs-other",
generate_xml: true,
expectedJarFilename: "android.other.jar",
high_mem: true,
},
{
moduleName: "stubs-applying-api-versions",
generate_xml: false,
},
}
for _, c := range testcases {
m := ctx.ModuleForTests(c.moduleName, "android_common")
manifest := m.Output("metalava.sbox.textproto")
sboxProto := android.RuleBuilderSboxProtoForTests(t, ctx, manifest)
cmdline := String(sboxProto.Commands[0].Command)
android.AssertStringContainsEquals(t, "api-versions generation flag", cmdline, "--generate-api-levels", c.generate_xml)
if c.expectedJarFilename != "" {
expected := "--android-jar-pattern ./%/public/" + c.expectedJarFilename
if !strings.Contains(cmdline, expected) {
t.Errorf("For %q, expected metalava argument %q, but was not found %q", c.moduleName, expected, cmdline)
}
}
metalava := m.Rule("metalava")
rp := metalava.RuleParams
if actual := rp.Pool != nil && strings.Contains(rp.Pool.String(), "highmem"); actual != c.high_mem {
t.Errorf("Expected %q high_mem to be %v, was %v", c.moduleName, c.high_mem, actual)
}
}
}
// runs a test for droidstubs with a customizable sdkType argument and returns
// the list of jar patterns that is passed as `--android-jar-pattern`
func getAndroidJarPatternsForDroidstubs(t *testing.T, sdkType string) []string {
ctx, _ := testJavaWithFS(t, fmt.Sprintf(`
droiddoc_exported_dir {
name: "some-exported-dir",
path: "somedir",
}
droiddoc_exported_dir {
name: "some-other-exported-dir",
path: "someotherdir",
}
droidstubs {
name: "foo-stubs",
srcs: ["foo-doc/a.java"],
api_levels_annotations_dirs: [
"some-exported-dir",
"some-other-exported-dir",
],
api_levels_annotations_enabled: true,
api_levels_sdk_type: "%s",
}
`, sdkType),
map[string][]byte{
"foo-doc/a.java": nil,
})
m := ctx.ModuleForTests("foo-stubs", "android_common")
manifest := m.Output("metalava.sbox.textproto")
cmd := String(android.RuleBuilderSboxProtoForTests(t, ctx, manifest).Commands[0].Command)
r := regexp.MustCompile(`--android-jar-pattern [^ ]+/android.jar`)
return r.FindAllString(cmd, -1)
}
func TestPublicDroidstubs(t *testing.T) {
patterns := getAndroidJarPatternsForDroidstubs(t, "public")
android.AssertArrayString(t, "order of patterns", []string{
"--android-jar-pattern somedir/%/public/android.jar",
"--android-jar-pattern someotherdir/%/public/android.jar",
}, patterns)
}
func TestSystemDroidstubs(t *testing.T) {
patterns := getAndroidJarPatternsForDroidstubs(t, "system")
android.AssertArrayString(t, "order of patterns", []string{
"--android-jar-pattern somedir/%/system/android.jar",
"--android-jar-pattern someotherdir/%/system/android.jar",
"--android-jar-pattern somedir/%/public/android.jar",
"--android-jar-pattern someotherdir/%/public/android.jar",
}, patterns)
}
func TestModuleLibDroidstubs(t *testing.T) {
patterns := getAndroidJarPatternsForDroidstubs(t, "module-lib")
android.AssertArrayString(t, "order of patterns", []string{
"--android-jar-pattern somedir/%/module-lib/android.jar",
"--android-jar-pattern someotherdir/%/module-lib/android.jar",
"--android-jar-pattern somedir/%/system/android.jar",
"--android-jar-pattern someotherdir/%/system/android.jar",
"--android-jar-pattern somedir/%/public/android.jar",
"--android-jar-pattern someotherdir/%/public/android.jar",
}, patterns)
}
func TestSystemServerDroidstubs(t *testing.T) {
patterns := getAndroidJarPatternsForDroidstubs(t, "system-server")
android.AssertArrayString(t, "order of patterns", []string{
"--android-jar-pattern somedir/%/system-server/android.jar",
"--android-jar-pattern someotherdir/%/system-server/android.jar",
"--android-jar-pattern somedir/%/module-lib/android.jar",
"--android-jar-pattern someotherdir/%/module-lib/android.jar",
"--android-jar-pattern somedir/%/system/android.jar",
"--android-jar-pattern someotherdir/%/system/android.jar",
"--android-jar-pattern somedir/%/public/android.jar",
"--android-jar-pattern someotherdir/%/public/android.jar",
}, patterns)
}
func TestDroidstubsSandbox(t *testing.T) {
ctx, _ := testJavaWithFS(t, `
genrule {
name: "foo",
out: ["foo.txt"],
cmd: "touch $(out)",
}
droidstubs {
name: "bar-stubs",
srcs: ["bar-doc/a.java"],
args: "--reference $(location :foo)",
arg_files: [":foo"],
}
`,
map[string][]byte{
"bar-doc/a.java": nil,
})
m := ctx.ModuleForTests("bar-stubs", "android_common")
metalava := m.Rule("metalava")
if g, w := metalava.Inputs.Strings(), []string{"bar-doc/a.java"}; !reflect.DeepEqual(w, g) {
t.Errorf("Expected inputs %q, got %q", w, g)
}
manifest := android.RuleBuilderSboxProtoForTests(t, ctx, m.Output("metalava.sbox.textproto"))
if g, w := manifest.Commands[0].GetCommand(), "reference __SBOX_SANDBOX_DIR__/out/soong/.intermediates/foo/gen/foo.txt"; !strings.Contains(g, w) {
t.Errorf("Expected command to contain %q, got %q", w, g)
}
}
func TestDroidstubsWithSystemModules(t *testing.T) {
ctx, _ := testJava(t, `
droidstubs {
name: "stubs-source-system-modules",
srcs: [
"bar-doc/a.java",
],
sdk_version: "none",
system_modules: "source-system-modules",
}
java_library {
name: "source-jar",
srcs: [
"a.java",
],
}
java_system_modules {
name: "source-system-modules",
libs: ["source-jar"],
}
droidstubs {
name: "stubs-prebuilt-system-modules",
srcs: [
"bar-doc/a.java",
],
sdk_version: "none",
system_modules: "prebuilt-system-modules",
}
java_import {
name: "prebuilt-jar",
jars: ["a.jar"],
}
java_system_modules_import {
name: "prebuilt-system-modules",
libs: ["prebuilt-jar"],
}
`)
checkSystemModulesUseByDroidstubs(t, ctx, "stubs-source-system-modules", "source-jar.jar")
checkSystemModulesUseByDroidstubs(t, ctx, "stubs-prebuilt-system-modules", "prebuilt-jar.jar")
}
func checkSystemModulesUseByDroidstubs(t *testing.T, ctx *android.TestContext, moduleName string, systemJar string) {
metalavaRule := ctx.ModuleForTests(moduleName, "android_common").Rule("metalava")
var systemJars []string
for _, i := range metalavaRule.Implicits {
systemJars = append(systemJars, i.Base())
}
if len(systemJars) < 1 || systemJars[0] != systemJar {
t.Errorf("inputs of %q must be []string{%q}, but was %#v.", moduleName, systemJar, systemJars)
}
}
func TestDroidstubsWithSdkExtensions(t *testing.T) {
ctx, _ := testJavaWithFS(t, `
droiddoc_exported_dir {
name: "sdk-dir",
path: "sdk",
}
droidstubs {
name: "baz-stubs",
api_levels_annotations_dirs: ["sdk-dir"],
api_levels_annotations_enabled: true,
extensions_info_file: ":info-file",
}
filegroup {
name: "info-file",
srcs: ["sdk/extensions/info.txt"],
}
`,
map[string][]byte{
"sdk/extensions/1/public/some-mainline-module-stubs.jar": nil,
"sdk/extensions/info.txt": nil,
})
m := ctx.ModuleForTests("baz-stubs", "android_common")
manifest := m.Output("metalava.sbox.textproto")
cmdline := String(android.RuleBuilderSboxProtoForTests(t, ctx, manifest).Commands[0].Command)
android.AssertStringDoesContain(t, "sdk-extensions-root present", cmdline, "--sdk-extensions-root sdk/extensions")
android.AssertStringDoesContain(t, "sdk-extensions-info present", cmdline, "--sdk-extensions-info sdk/extensions/info.txt")
}
func TestDroidStubsApiContributionGeneration(t *testing.T) {
ctx, _ := testJavaWithFS(t, `
droidstubs {
name: "foo",
srcs: ["A/a.java"],
api_surface: "public",
check_api: {
current: {
api_file: "A/current.txt",
removed_api_file: "A/removed.txt",
}
}
}
`,
map[string][]byte{
"A/a.java": nil,
"A/current.txt": nil,
"A/removed.txt": nil,
},
)
ctx.ModuleForTests("foo.api.contribution", "")
}
func TestGeneratedApiContributionVisibilityTest(t *testing.T) {
library_bp := `
java_api_library {
name: "bar",
api_surface: "public",
api_contributions: ["foo.api.contribution"],
stubs_type: "everything",
}
`
ctx, _ := testJavaWithFS(t, `
droidstubs {
name: "foo",
srcs: ["A/a.java"],
api_surface: "public",
check_api: {
current: {
api_file: "A/current.txt",
removed_api_file: "A/removed.txt",
}
},
Add current api check as validation for from-text stub generation Currently, there is no build action ensuring that the API text files are up to date, unless a user runs `m checkapi` or `m update-api`. This means that the user must run `m update-api` after making a change that modifies api surface(s), so that the API text file reflects the local change. This adds additional layer of action to developers, and it is not guaranteed that the developer will always run `m update-api` after making an api surface-affecting changes. To prevent such mistake, this change adds droidstub-level api check as validation for from-text stub generation. With this change, the build will fail if the API text file is not up to date and the user must run `m update-api` when making api surface-affecting local changes. The validation is done by adding all droidstubs modules associated with the java_api_contributions passed to java_api_library via `api_contributions` as dependency and setting the current api timestamp files as the validations for the from-text stubs generating build rule. The full api surface libraries will not run the validations to avoid circular dependency. However, all java_sdk_library generated java_api_library modules will run validations, mapped to the droidstubs in the same api domaion. If the user sets the environment variable `DISABLE_STUB_VALIDATION=true`, validation actions are not run. Validation actions run by default. Test: m nothing --build-from-text-stub and run ninja query to verify `check_current_api.timestamp`s are listed as validation \ DISABLE_STUB_VALIDATION=true m nothing --build-from-text-stub and run ninja query to verify that validation actions are not added Bug: 288624417 Change-Id: I329e6438fe8f3ac30d8c6a971d57853ed6b0d150
2023-06-28 03:16:23 +02:00
visibility: ["//a", "//b"],
}
`,
map[string][]byte{
"a/a.java": nil,
"a/current.txt": nil,
"a/removed.txt": nil,
"b/Android.bp": []byte(library_bp),
},
)
ctx.ModuleForTests("bar", "android_common")
}
func TestAconfigDeclarations(t *testing.T) {
result := android.GroupFixturePreparers(
prepareForJavaTest,
android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
}),
android.FixtureMergeMockFs(map[string][]byte{
"a/A.java": nil,
"a/current.txt": nil,
"a/removed.txt": nil,
}),
).RunTestWithBp(t, `
aconfig_declarations {
name: "bar",
package: "com.example.package",
srcs: [
"bar.aconfig",
],
}
droidstubs {
name: "foo",
srcs: ["a/A.java"],
api_surface: "public",
check_api: {
current: {
api_file: "a/current.txt",
removed_api_file: "a/removed.txt",
}
},
aconfig_declarations: [
"bar",
],
}
`)
// Check that droidstubs depend on aconfig_declarations
android.AssertBoolEquals(t, "foo expected to depend on bar",
CheckModuleHasDependency(t, result.TestContext, "foo", "android_common", "bar"), true)
m := result.ModuleForTests("foo", "android_common")
android.AssertStringDoesContain(t, "foo generates revert annotations file",
strings.Join(m.AllOutputs(), ""), "revert-annotations-exportable.txt")
Generate "exportable" stubs in droidstubs This change generates rules for "exportable" stubs in the droidstubs module. Given that there are a lot of overlap between the currently existing "everything" stubs rule and the newly introducing "exportable" stubs rule, the currently existing metalava rule commands are modularized to be utilized in the "exportable" stubs rule commands. The currently existing build actions are modularized in the followings: - commonMetalavaStubCmd(...): metalava commands that are required for generation of both "everything", "exportable", and potentially "runtime" stubs - everythingOptionalCmd(...): metalava commands that are dependent on "everything" stubs and not dependent on flagged apis annotations, such as api lint, released api check Based on this modularization, the "everything" stubs are now generated in everythingStubCmd(...), which calls commonMetalavaStubCmd(...) and everythingOptionalCmd(...). Similarly, the "exportable" stubs are generated in optionalStubCmd(stubsType=Exportable, ...), which calls commonMetalavaStubCmd(...) and appends additional flags. Runtime stubs can be generated similarly in the future with optionalStubCmd(stubsType=Runtime, ...). "everything"-related artifacts will now be created in `everything/` subdirectory, and "exportable"-related artifacts will be created in `exportable/` subdirectory. For example, the outdir of a droidstubs module "foo" would look like below: ``` foo |-- everything | |-- foo_api.txt | |-- foo-stubs.srcjar | |-- exportable |-- foo_api.txt |-- foo-stubs.srcjar ``` The module generates the build rules for the "exportable" stubs regardless of whether the module defines the `aconfig_declarations` property or not. All APIs marked with `@FlaggedApis` annotations are stripped out for the "exportable" stubs when the `aconfig_declarations` property is not defined. On the other hand, only the `@FlaggedApis` that are specified in the aconfig_declarations module and are enabled will be included (and all others are stripped) when the `aconfig_declarations` propety is defined. Test: go test ./java && BUILD_FROM_SOURCE_STUBS=true m Bug: 315490657 Change-Id: I300273cd2a62fa978b046c0268e3a67c35e22b08
2023-12-19 03:40:22 +01:00
// revert-annotations.txt passed to exportable stubs generation metalava command
manifest := m.Output("metalava_exportable.sbox.textproto")
cmdline := String(android.RuleBuilderSboxProtoForTests(t, result.TestContext, manifest).Commands[0].Command)
android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "revert-annotations-exportable.txt")
Generate "exportable" stubs in droidstubs This change generates rules for "exportable" stubs in the droidstubs module. Given that there are a lot of overlap between the currently existing "everything" stubs rule and the newly introducing "exportable" stubs rule, the currently existing metalava rule commands are modularized to be utilized in the "exportable" stubs rule commands. The currently existing build actions are modularized in the followings: - commonMetalavaStubCmd(...): metalava commands that are required for generation of both "everything", "exportable", and potentially "runtime" stubs - everythingOptionalCmd(...): metalava commands that are dependent on "everything" stubs and not dependent on flagged apis annotations, such as api lint, released api check Based on this modularization, the "everything" stubs are now generated in everythingStubCmd(...), which calls commonMetalavaStubCmd(...) and everythingOptionalCmd(...). Similarly, the "exportable" stubs are generated in optionalStubCmd(stubsType=Exportable, ...), which calls commonMetalavaStubCmd(...) and appends additional flags. Runtime stubs can be generated similarly in the future with optionalStubCmd(stubsType=Runtime, ...). "everything"-related artifacts will now be created in `everything/` subdirectory, and "exportable"-related artifacts will be created in `exportable/` subdirectory. For example, the outdir of a droidstubs module "foo" would look like below: ``` foo |-- everything | |-- foo_api.txt | |-- foo-stubs.srcjar | |-- exportable |-- foo_api.txt |-- foo-stubs.srcjar ``` The module generates the build rules for the "exportable" stubs regardless of whether the module defines the `aconfig_declarations` property or not. All APIs marked with `@FlaggedApis` annotations are stripped out for the "exportable" stubs when the `aconfig_declarations` property is not defined. On the other hand, only the `@FlaggedApis` that are specified in the aconfig_declarations module and are enabled will be included (and all others are stripped) when the `aconfig_declarations` propety is defined. Test: go test ./java && BUILD_FROM_SOURCE_STUBS=true m Bug: 315490657 Change-Id: I300273cd2a62fa978b046c0268e3a67c35e22b08
2023-12-19 03:40:22 +01:00
android.AssertStringDoesContain(t, "foo generates exportable stubs jar",
strings.Join(m.AllOutputs(), ""), "exportable/foo-stubs.srcjar")
}
Enable exportable stubs to include READ_WRITE aconfig flagged apis Currently in SDK build, "exportable" stubs are used to generate the android.jar and the corresponding build artifacts, as well as the hiddenapi flags. "exportable" stubs only include the flagged apis that are "enabled" and "read only", and exclude all other flagged apis. This will be replaced with "runtime" stubs in the long run, which include "read_write" flagged apis on top of the "enabled" and "read only" flags. Prior to Trunk Stable, the SDK build did not distinguish the target audience of the SDK. That is, the identical build target was used to generate the SDK targeted toward the Google3 developers (i.e. Google3 SDK drop) and the SDK targeted toward the public (i.e. Developer Preview). However, given that we now have "experimental" apis with Trunk Stable, there are demands to differentiate the SDK based on the target audience, so that the "experimental" APIs are included in the SDK targeted toward Google3 while they are excluded in the public facing SDK. The long term solution to achieve this is to generate the hiddenapi flags and (conditionally) the SDKs using the runtime stubs. However, as this is high priority, this change resolves the problem by modifying the filter condition of the "exportable" stubs to include the "read_write" flagged apis on top of the "enabled" and "read only" flagged apis when the value of the default-false build flag "RELEASE_EXPORT_RUNTIME_APIS" is set to true. Note that this is a temporary solution; However, we might need to keep the "RELEASE_EXPORT_RUNTIME_APIS" build flag even in the long run to determine what set of apis are included in the generated SDK, based on the target audience of the SDK. Test: m nothing --no-skip-soong-tests Bug: 323188988 Change-Id: If0d5fa74b3ba6f4a57c86aade8d340f149a657a2
2024-02-06 23:43:18 +01:00
func TestReleaseExportRuntimeApis(t *testing.T) {
result := android.GroupFixturePreparers(
prepareForJavaTest,
android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
variables.BuildFlags = map[string]string{
"RELEASE_HIDDEN_API_EXPORTABLE_STUBS": "true",
}
variables.ExportRuntimeApis = proptools.BoolPtr(true)
Enable exportable stubs to include READ_WRITE aconfig flagged apis Currently in SDK build, "exportable" stubs are used to generate the android.jar and the corresponding build artifacts, as well as the hiddenapi flags. "exportable" stubs only include the flagged apis that are "enabled" and "read only", and exclude all other flagged apis. This will be replaced with "runtime" stubs in the long run, which include "read_write" flagged apis on top of the "enabled" and "read only" flags. Prior to Trunk Stable, the SDK build did not distinguish the target audience of the SDK. That is, the identical build target was used to generate the SDK targeted toward the Google3 developers (i.e. Google3 SDK drop) and the SDK targeted toward the public (i.e. Developer Preview). However, given that we now have "experimental" apis with Trunk Stable, there are demands to differentiate the SDK based on the target audience, so that the "experimental" APIs are included in the SDK targeted toward Google3 while they are excluded in the public facing SDK. The long term solution to achieve this is to generate the hiddenapi flags and (conditionally) the SDKs using the runtime stubs. However, as this is high priority, this change resolves the problem by modifying the filter condition of the "exportable" stubs to include the "read_write" flagged apis on top of the "enabled" and "read only" flagged apis when the value of the default-false build flag "RELEASE_EXPORT_RUNTIME_APIS" is set to true. Note that this is a temporary solution; However, we might need to keep the "RELEASE_EXPORT_RUNTIME_APIS" build flag even in the long run to determine what set of apis are included in the generated SDK, based on the target audience of the SDK. Test: m nothing --no-skip-soong-tests Bug: 323188988 Change-Id: If0d5fa74b3ba6f4a57c86aade8d340f149a657a2
2024-02-06 23:43:18 +01:00
}),
android.FixtureMergeMockFs(map[string][]byte{
"a/A.java": nil,
"a/current.txt": nil,
"a/removed.txt": nil,
}),
).RunTestWithBp(t, `
aconfig_declarations {
name: "bar",
package: "com.example.package",
srcs: [
"bar.aconfig",
],
}
droidstubs {
name: "foo",
srcs: ["a/A.java"],
api_surface: "public",
check_api: {
current: {
api_file: "a/current.txt",
removed_api_file: "a/removed.txt",
}
},
aconfig_declarations: [
"bar",
],
}
`)
m := result.ModuleForTests("foo", "android_common")
rule := m.Output("released-flagged-apis-exportable.txt")
exposeWritableApisFilter := "--filter='state:ENABLED+permission:READ_ONLY' --filter='permission:READ_WRITE'"
android.AssertStringEquals(t, "Filter argument expected to contain READ_WRITE permissions", exposeWritableApisFilter, rule.Args["filter_args"])
}