java_sdk_library: Allow separate api/stubs source modules

The API file for a scope represents the differences between the API
provided by that scope and that provided by the scope that it extends.
On the other hand the stubs source for a scope represents the union of
the API provided by the scope and the scope it extends (all the way
back to public).

Unfortunately, while metalava supports this behavior for scopes that
extend public (e.g. system and test) it does not support this behavior
for scopes that extend others, e.g. module_lib which extends system.
This is because it always assumes that the baseline for the API file
is 'public' and so has no way to defined other baselines.

This change works around that by having separate droidstubs modules to
generate the API and stubs sources for scopes that require different
arguments to generate the API and stubs sources.

Test: m checkapi
Bug: 155164730
Change-Id: Iea7d59852d7aeb503120acf3c44e08eb0d9d07b9
This commit is contained in:
Paul Duffin 2020-04-29 13:30:54 +01:00
parent fced20c3b4
commit 0ff08bdb07
2 changed files with 179 additions and 54 deletions

View file

@ -34,6 +34,7 @@ const (
sdkSystemApiSuffix = ".system"
sdkTestApiSuffix = ".test"
sdkStubsSourceSuffix = ".stubs.source"
sdkApiSuffix = ".api"
sdkXmlFileSuffix = ".xml"
permissionsTemplate = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n` +
`<!-- Copyright (C) 2018 The Android Open Source Project\n` +
@ -99,6 +100,12 @@ type apiScope struct {
// The tag to use to depend on the stubs library module.
stubsTag scopeDependencyTag
// The tag to use to depend on the stubs source module (if separate from the API module).
stubsSourceTag scopeDependencyTag
// The tag to use to depend on the API file generating module (if separate from the stubs source module).
apiFileTag scopeDependencyTag
// The tag to use to depend on the stubs source and API module.
stubsSourceAndApiTag scopeDependencyTag
@ -117,6 +124,22 @@ type apiScope struct {
// Extra arguments to pass to droidstubs for this scope.
droidstubsArgs []string
// The args that must be passed to droidstubs to generate the stubs source
// for this scope.
//
// The stubs source must include the definitions of everything that is in this
// api scope and all the scopes that this one extends.
droidstubsArgsForGeneratingStubsSource []string
// The args that must be passed to droidstubs to generate the API for this scope.
//
// The API only includes the additional members that this scope adds over the scope
// that it extends.
droidstubsArgsForGeneratingApi []string
// True if the stubs source and api can be created by the same metalava invocation.
createStubsSourceAndApiTogether bool
// Whether the api scope can be treated as unstable, and should skip compat checks.
unstable bool
}
@ -130,11 +153,41 @@ func initApiScope(scope *apiScope) *apiScope {
apiScope: scope,
depInfoExtractor: (*scopePaths).extractStubsLibraryInfoFromDependency,
}
scope.stubsSourceTag = scopeDependencyTag{
name: name + "-stubs-source",
apiScope: scope,
depInfoExtractor: (*scopePaths).extractStubsSourceInfoFromDep,
}
scope.apiFileTag = scopeDependencyTag{
name: name + "-api",
apiScope: scope,
depInfoExtractor: (*scopePaths).extractApiInfoFromDep,
}
scope.stubsSourceAndApiTag = scopeDependencyTag{
name: name + "-stubs-source-and-api",
apiScope: scope,
depInfoExtractor: (*scopePaths).extractStubsSourceAndApiInfoFromApiStubsProvider,
}
// To get the args needed to generate the stubs source append all the args from
// this scope and all the scopes it extends as each set of args adds additional
// members to the stubs.
var stubsSourceArgs []string
for s := scope; s != nil; s = s.extends {
stubsSourceArgs = append(stubsSourceArgs, s.droidstubsArgs...)
}
scope.droidstubsArgsForGeneratingStubsSource = stubsSourceArgs
// Currently the args needed to generate the API are the same as the args
// needed to add additional members.
apiArgs := scope.droidstubsArgs
scope.droidstubsArgsForGeneratingApi = apiArgs
// If the args needed to generate the stubs and API are the same then they
// can be generated in a single invocation of metalava, otherwise they will
// need separate invocations.
scope.createStubsSourceAndApiTogether = reflect.DeepEqual(stubsSourceArgs, apiArgs)
return scope
}
@ -146,6 +199,10 @@ func (scope *apiScope) stubsSourceModuleName(baseName string) string {
return baseName + sdkStubsSourceSuffix + scope.moduleSuffix
}
func (scope *apiScope) apiModuleName(baseName string) string {
return baseName + sdkApiSuffix + scope.moduleSuffix
}
func (scope *apiScope) String() string {
return scope.name
}
@ -379,17 +436,47 @@ func (paths *scopePaths) extractStubsLibraryInfoFromDependency(dep android.Modul
}
}
func (paths *scopePaths) extractStubsSourceAndApiInfoFromApiStubsProvider(dep android.Module) error {
if provider, ok := dep.(ApiStubsProvider); ok {
paths.currentApiFilePath = provider.ApiFilePath()
paths.removedApiFilePath = provider.RemovedApiFilePath()
paths.stubsSrcJar = provider.StubsSrcJar()
func (paths *scopePaths) treatDepAsApiStubsProvider(dep android.Module, action func(provider ApiStubsProvider)) error {
if apiStubsProvider, ok := dep.(ApiStubsProvider); ok {
action(apiStubsProvider)
return nil
} else {
return fmt.Errorf("expected module that implements ApiStubsProvider, e.g. droidstubs")
}
}
func (paths *scopePaths) extractApiInfoFromApiStubsProvider(provider ApiStubsProvider) {
paths.currentApiFilePath = provider.ApiFilePath()
paths.removedApiFilePath = provider.RemovedApiFilePath()
}
func (paths *scopePaths) extractApiInfoFromDep(dep android.Module) error {
return paths.treatDepAsApiStubsProvider(dep, func(provider ApiStubsProvider) {
paths.extractApiInfoFromApiStubsProvider(provider)
})
}
func (paths *scopePaths) extractStubsSourceInfoFromApiStubsProviders(provider ApiStubsProvider) {
paths.stubsSrcJar = provider.StubsSrcJar()
}
func (paths *scopePaths) extractStubsSourceInfoFromDep(dep android.Module) error {
return paths.treatDepAsApiStubsProvider(dep, func(provider ApiStubsProvider) {
paths.extractStubsSourceInfoFromApiStubsProviders(provider)
})
}
func (paths *scopePaths) extractStubsSourceAndApiInfoFromApiStubsProvider(dep android.Module) error {
return paths.treatDepAsApiStubsProvider(dep, func(provider ApiStubsProvider) {
paths.extractApiInfoFromApiStubsProvider(provider)
paths.extractStubsSourceInfoFromApiStubsProviders(provider)
})
}
type commonToSdkLibraryAndImportProperties struct {
Naming_scheme *string
}
// Common code between sdk library and sdk library import
type commonToSdkLibraryAndImport struct {
scopePaths map[*apiScope]*scopePaths
@ -488,8 +575,16 @@ func (module *SdkLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
// Add dependencies to the stubs library
ctx.AddVariationDependencies(nil, apiScope.stubsTag, module.stubsName(apiScope))
// And the stubs source and api files
// If the stubs source and API cannot be generated together then add an additional dependency on
// the API module.
if apiScope.createStubsSourceAndApiTogether {
// Add a dependency on the stubs source in order to access both stubs source and api information.
ctx.AddVariationDependencies(nil, apiScope.stubsSourceAndApiTag, module.stubsSourceName(apiScope))
} else {
// Add separate dependencies on the creators of the stubs source files and the API.
ctx.AddVariationDependencies(nil, apiScope.stubsSourceTag, module.stubsSourceName(apiScope))
ctx.AddVariationDependencies(nil, apiScope.apiFileTag, module.apiName(apiScope))
}
}
if !proptools.Bool(module.sdkLibraryProperties.Api_only) {
@ -539,12 +634,18 @@ func (module *SdkLibrary) stubsName(apiScope *apiScope) string {
return apiScope.stubsModuleName(module.BaseModuleName())
}
// // Name of the droidstubs module that generates the stubs source and
// generates/checks the API.
// Name of the droidstubs module that generates the stubs source and may also
// generate/check the API.
func (module *SdkLibrary) stubsSourceName(apiScope *apiScope) string {
return apiScope.stubsSourceModuleName(module.BaseModuleName())
}
// Name of the droidstubs module that generates/checks the API. Only used if it
// requires different arts to the stubs source generating module.
func (module *SdkLibrary) apiName(apiScope *apiScope) string {
return apiScope.apiModuleName(module.BaseModuleName())
}
// Module name of the runtime implementation library
func (module *SdkLibrary) implName() string {
return module.BaseModuleName()
@ -665,7 +766,7 @@ func (module *SdkLibrary) createStubsLibrary(mctx android.DefaultableHookContext
// Creates a droidstubs module that creates stubs source files from the given full source
// files and also updates and checks the API specification files.
func (module *SdkLibrary) createStubsSourcesAndApi(mctx android.DefaultableHookContext, apiScope *apiScope) {
func (module *SdkLibrary) createStubsSourcesAndApi(mctx android.DefaultableHookContext, apiScope *apiScope, name string, createStubSources, createApi bool, scopeSpecificDroidstubsArgs []string) {
props := struct {
Name *string
Visibility []string
@ -679,6 +780,7 @@ func (module *SdkLibrary) createStubsSourcesAndApi(mctx android.DefaultableHookC
Java_version *string
Merge_annotations_dirs []string
Merge_inclusion_annotations_dirs []string
Generate_stubs *bool
Check_api struct {
Current ApiToCheck
Last_released ApiToCheck
@ -707,7 +809,7 @@ func (module *SdkLibrary) createStubsSourcesAndApi(mctx android.DefaultableHookC
// * system_modules
// * libs (static_libs/libs)
props.Name = proptools.StringPtr(module.stubsSourceName(apiScope))
props.Name = proptools.StringPtr(name)
// If stubs_source_visibility is not set then the created module will use the
// visibility of this module.
@ -751,11 +853,17 @@ func (module *SdkLibrary) createStubsSourcesAndApi(mctx android.DefaultableHookC
}
droidstubsArgs = append(droidstubsArgs, android.JoinWithPrefix(disabledWarnings, "--hide "))
if !createStubSources {
// Stubs are not required.
props.Generate_stubs = proptools.BoolPtr(false)
}
// Add in scope specific arguments.
droidstubsArgs = append(droidstubsArgs, apiScope.droidstubsArgs...)
droidstubsArgs = append(droidstubsArgs, scopeSpecificDroidstubsArgs...)
props.Arg_files = module.sdkLibraryProperties.Droiddoc_option_files
props.Args = proptools.StringPtr(strings.Join(droidstubsArgs, " "))
if createApi {
// List of APIs identified from the provided source files are created. They are later
// compared against to the not-yet-released (a.k.a current) list of APIs and to the
// last-released (a.k.a numbered) list of API.
@ -803,6 +911,7 @@ func (module *SdkLibrary) createStubsSourcesAndApi(mctx android.DefaultableHookC
props.Dist.Dest = proptools.StringPtr(fmt.Sprintf("%v.txt", module.BaseModuleName()))
props.Dist.Dir = proptools.StringPtr(path.Join(module.apiDistPath(apiScope), "api"))
}
}
mctx.CreateModule(DroidstubsFactory, &props)
}
@ -989,8 +1098,24 @@ func (module *SdkLibrary) CreateInternalModules(mctx android.DefaultableHookCont
}
for _, scope := range generatedScopes {
stubsSourceArgs := scope.droidstubsArgsForGeneratingStubsSource
stubsSourceModuleName := module.stubsSourceName(scope)
// If the args needed to generate the stubs and API are the same then they
// can be generated in a single invocation of metalava, otherwise they will
// need separate invocations.
if scope.createStubsSourceAndApiTogether {
// Use the stubs source name for legacy reasons.
module.createStubsSourcesAndApi(mctx, scope, stubsSourceModuleName, true, true, stubsSourceArgs)
} else {
module.createStubsSourcesAndApi(mctx, scope, stubsSourceModuleName, true, false, stubsSourceArgs)
apiArgs := scope.droidstubsArgsForGeneratingApi
apiName := module.apiName(scope)
module.createStubsSourcesAndApi(mctx, scope, apiName, false, true, apiArgs)
}
module.createStubsLibrary(mctx, scope)
module.createStubsSourcesAndApi(mctx, scope)
}
if !proptools.Bool(module.sdkLibraryProperties.Api_only) {

View file

@ -1314,8 +1314,8 @@ sdk_snapshot {
.intermediates/myjavalib.stubs.source.system/android_common/myjavalib.stubs.source.system_api.txt -> sdk_library/system/myjavalib.txt
.intermediates/myjavalib.stubs.source.system/android_common/myjavalib.stubs.source.system_api.txt -> sdk_library/system/myjavalib-removed.txt
.intermediates/myjavalib.stubs.module_lib/android_common/javac/myjavalib.stubs.module_lib.jar -> sdk_library/module_lib/myjavalib-stubs.jar
.intermediates/myjavalib.stubs.source.module_lib/android_common/myjavalib.stubs.source.module_lib_api.txt -> sdk_library/module_lib/myjavalib.txt
.intermediates/myjavalib.stubs.source.module_lib/android_common/myjavalib.stubs.source.module_lib_api.txt -> sdk_library/module_lib/myjavalib-removed.txt
.intermediates/myjavalib.api.module_lib/android_common/myjavalib.api.module_lib_api.txt -> sdk_library/module_lib/myjavalib.txt
.intermediates/myjavalib.api.module_lib/android_common/myjavalib.api.module_lib_api.txt -> sdk_library/module_lib/myjavalib-removed.txt
`),
checkMergeZips(
".intermediates/mysdk/common_os/tmp/sdk_library/public/myjavalib_stub_sources.zip",