platform_build_soong/java/droidstubs_test.go
Jihoon Kang 3c89f04777 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
2024-01-08 07:56:07 +00:00

448 lines
13 KiB
Go

// 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"],
}
`
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",
}
},
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 TestDroidstubsHideFlaggedApi(t *testing.T) {
result := android.GroupFixturePreparers(
prepareForJavaTest,
android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
variables.NextReleaseHideFlaggedApi = proptools.BoolPtr(true)
variables.Release_expose_flagged_api = proptools.BoolPtr(false)
}),
android.FixtureMergeMockFs(map[string][]byte{
"a/A.java": nil,
"a/current.txt": nil,
"a/removed.txt": nil,
}),
).RunTestWithBp(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",
}
},
}
`)
m := result.ModuleForTests("foo", "android_common")
manifest := m.Output("metalava.sbox.textproto")
cmdline := String(android.RuleBuilderSboxProtoForTests(t, result.TestContext, manifest).Commands[0].Command)
android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "--revert-annotation android.annotation.FlaggedApi")
}
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")
// 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")
android.AssertStringDoesContain(t, "foo generates exportable stubs jar",
strings.Join(m.AllOutputs(), ""), "exportable/foo-stubs.srcjar")
}