diff --git a/android/api_domain.go b/android/api_domain.go index 3265148ea..bdd4e6fa5 100644 --- a/android/api_domain.go +++ b/android/api_domain.go @@ -59,8 +59,14 @@ type apiDomain struct { type apiDomainProperties struct { // cc library contributions (.h files/.map.txt) of this API domain - // This dependency is a no-op in Soong, but the corresponding Bazel target in the bp2build workspace will provide a `CcApiContributionInfo` provider + // This dependency is a no-op in Soong, but the corresponding Bazel target in the api_bp2build workspace + // will provide a `CcApiContributionInfo` provider Cc_api_contributions []string + + // java library contributions (as .txt) of this API domain + // This dependency is a no-op in Soong, but the corresponding Bazel target in the api_bp2build workspace + // will provide a `JavaApiContributionInfo` provider + Java_api_contributions []string } func ApiDomainFactory() Module { @@ -102,7 +108,8 @@ func contributionBazelAttributes(ctx TopDownMutatorContext, contributions []stri } type bazelApiDomainAttributes struct { - Cc_api_contributions bazel.LabelListAttribute + Cc_api_contributions bazel.LabelListAttribute + Java_api_contributions bazel.LabelListAttribute } var _ ApiProvider = (*apiDomain)(nil) @@ -113,7 +120,8 @@ func (a *apiDomain) ConvertWithApiBp2build(ctx TopDownMutatorContext) { Bzl_load_location: "//build/bazel/rules/apis:api_domain.bzl", } attrs := &bazelApiDomainAttributes{ - Cc_api_contributions: contributionBazelAttributes(ctx, a.properties.Cc_api_contributions), + Cc_api_contributions: contributionBazelAttributes(ctx, a.properties.Cc_api_contributions), + Java_api_contributions: contributionBazelAttributes(ctx, a.properties.Java_api_contributions), } ctx.CreateBazelTargetModule(props, CommonAttributes{ Name: ctx.ModuleName(), diff --git a/android/sdk_version.go b/android/sdk_version.go index c188c4808..d73c9125d 100644 --- a/android/sdk_version.go +++ b/android/sdk_version.go @@ -44,6 +44,7 @@ const ( SdkNone SdkCore SdkCorePlatform + SdkIntraCore // API surface provided by one core module to another SdkPublic SdkSystem SdkTest @@ -69,6 +70,8 @@ func (k SdkKind) String() string { return "core" case SdkCorePlatform: return "core_platform" + case SdkIntraCore: + return "intracore" case SdkModule: return "module-lib" case SdkSystemServer: diff --git a/bp2build/Android.bp b/bp2build/Android.bp index 2e0178955..72d16fad0 100644 --- a/bp2build/Android.bp +++ b/bp2build/Android.bp @@ -55,6 +55,7 @@ bootstrap_go_package { "cc_test_conversion_test.go", "cc_yasm_conversion_test.go", "conversion_test.go", + "droidstubs_conversion_test.go", "filegroup_conversion_test.go", "genrule_conversion_test.go", "gensrcs_conversion_test.go", diff --git a/bp2build/droidstubs_conversion_test.go b/bp2build/droidstubs_conversion_test.go new file mode 100644 index 000000000..12c1cfe32 --- /dev/null +++ b/bp2build/droidstubs_conversion_test.go @@ -0,0 +1,104 @@ +// Copyright 2022 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 bp2build + +import ( + "testing" + + "android/soong/android" + "android/soong/java" +) + +func registerJavaApiModules(ctx android.RegistrationContext) { + java.RegisterSdkLibraryBuildComponents(ctx) + java.RegisterStubsBuildComponents(ctx) +} + +func TestDroidstubsApiContributions(t *testing.T) { + bp := ` + droidstubs { + name: "framework-stubs", + check_api: { + current: { + api_file: "framework.current.txt", + }, + }, + } + + // Modules without check_api should not generate a Bazel API target + droidstubs { + name: "framework-docs", + } + + // java_sdk_library is a macro that creates droidstubs + java_sdk_library { + name: "module-stubs", + srcs: ["A.java"], + + // These api surfaces are added by default, but add them explicitly to make + // this test hermetic + public: { + enabled: true, + }, + system: { + enabled: true, + }, + + // Disable other api surfaces to keep unit test scope limited + module_lib: { + enabled: false, + }, + test: { + enabled: false, + }, + } + ` + expectedBazelTargets := []string{ + MakeBazelTargetNoRestrictions( + "java_api_contribution", + "framework-stubs.contribution", + AttrNameToString{ + "api": `"framework.current.txt"`, + "api_surface": `"publicapi"`, + "target_compatible_with": `["//build/bazel/platforms/os:android"]`, + }), + MakeBazelTargetNoRestrictions( + "java_api_contribution", + "module-stubs.stubs.source.contribution", + AttrNameToString{ + "api": `"api/current.txt"`, + "api_surface": `"publicapi"`, + "target_compatible_with": `["//build/bazel/platforms/os:android"]`, + }), + MakeBazelTargetNoRestrictions( + "java_api_contribution", + "module-stubs.stubs.source.system.contribution", + AttrNameToString{ + "api": `"api/system-current.txt"`, + "api_surface": `"systemapi"`, + "target_compatible_with": `["//build/bazel/platforms/os:android"]`, + }), + } + RunApiBp2BuildTestCase(t, registerJavaApiModules, Bp2buildTestCase{ + Blueprint: bp, + ExpectedBazelTargets: expectedBazelTargets, + Filesystem: map[string]string{ + "api/current.txt": "", + "api/removed.txt": "", + "api/system-current.txt": "", + "api/system-removed.txt": "", + }, + }) +} diff --git a/java/droidstubs.go b/java/droidstubs.go index 5777b185c..2ad29693d 100644 --- a/java/droidstubs.go +++ b/java/droidstubs.go @@ -18,11 +18,13 @@ import ( "fmt" "path/filepath" "regexp" + "sort" "strings" "github.com/google/blueprint/proptools" "android/soong/android" + "android/soong/bazel" "android/soong/java/config" "android/soong/remoteexec" ) @@ -834,6 +836,74 @@ func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) { } } +var _ android.ApiProvider = (*Droidstubs)(nil) + +type bazelJavaApiContributionAttributes struct { + Api bazel.LabelAttribute + Api_surface *string +} + +func (d *Droidstubs) ConvertWithApiBp2build(ctx android.TopDownMutatorContext) { + props := bazel.BazelTargetModuleProperties{ + Rule_class: "java_api_contribution", + Bzl_load_location: "//build/bazel/rules/apis:java_api_contribution.bzl", + } + apiFile := d.properties.Check_api.Current.Api_file + // Do not generate a target if check_api is not set + if apiFile == nil { + return + } + attrs := &bazelJavaApiContributionAttributes{ + Api: *bazel.MakeLabelAttribute( + android.BazelLabelForModuleSrcSingle(ctx, proptools.String(apiFile)).Label, + ), + Api_surface: proptools.StringPtr(bazelApiSurfaceName(d.Name())), + } + ctx.CreateBazelTargetModule(props, android.CommonAttributes{ + Name: android.ApiContributionTargetName(ctx.ModuleName()), + }, attrs) +} + +// TODO (b/262014796): Export the API contributions of CorePlatformApi +// A map to populate the api surface of a droidstub from a substring appearing in its name +// This map assumes that droidstubs (either checked-in or created by java_sdk_library) +// use a strict naming convention +var ( + droidstubsModuleNamingToSdkKind = map[string]android.SdkKind{ + //public is commented out since the core libraries use public in their java_sdk_library names + "intracore": android.SdkIntraCore, + "intra.core": android.SdkIntraCore, + "system_server": android.SdkSystemServer, + "system-server": android.SdkSystemServer, + "system": android.SdkSystem, + "module_lib": android.SdkModule, + "module-lib": android.SdkModule, + "test": android.SdkTest, + } +) + +// A helper function that returns the api surface of the corresponding java_api_contribution Bazel target +// The api_surface is populated using the naming convention of the droidstubs module. +func bazelApiSurfaceName(name string) string { + // Sort the keys so that longer strings appear first + // Otherwise substrings like system will match both system and system_server + sortedKeys := make([]string, 0) + for key := range droidstubsModuleNamingToSdkKind { + sortedKeys = append(sortedKeys, key) + } + sort.Slice(sortedKeys, func(i, j int) bool { + return len(sortedKeys[i]) > len(sortedKeys[j]) + }) + for _, sortedKey := range sortedKeys { + if strings.Contains(name, sortedKey) { + sdkKind := droidstubsModuleNamingToSdkKind[sortedKey] + return sdkKind.String() + "api" + } + } + // Default is publicapi + return android.SdkPublic.String() + "api" +} + func StubsDefaultsFactory() android.Module { module := &DocDefaults{} diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go index 25f8c8667..ef2e6dc8a 100644 --- a/java/droidstubs_test.go +++ b/java/droidstubs_test.go @@ -304,3 +304,45 @@ func TestDroidstubsWithSdkExtensions(t *testing.T) { 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 TestApiSurfaceFromDroidStubsName(t *testing.T) { + testCases := []struct { + desc string + name string + expectedApiSurface string + }{ + { + desc: "Default is publicapi", + name: "mydroidstubs", + expectedApiSurface: "publicapi", + }, + { + desc: "name contains system substring", + name: "mydroidstubs.system.suffix", + expectedApiSurface: "systemapi", + }, + { + desc: "name contains system_server substring", + name: "mydroidstubs.system_server.suffix", + expectedApiSurface: "system-serverapi", + }, + { + desc: "name contains module_lib substring", + name: "mydroidstubs.module_lib.suffix", + expectedApiSurface: "module-libapi", + }, + { + desc: "name contains test substring", + name: "mydroidstubs.test.suffix", + expectedApiSurface: "testapi", + }, + { + desc: "name contains intra.core substring", + name: "mydroidstubs.intra.core.suffix", + expectedApiSurface: "intracoreapi", + }, + } + for _, tc := range testCases { + android.AssertStringEquals(t, tc.desc, tc.expectedApiSurface, bazelApiSurfaceName(tc.name)) + } +}