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" sdkSystemApiSuffix = ".system"
sdkTestApiSuffix = ".test" sdkTestApiSuffix = ".test"
sdkStubsSourceSuffix = ".stubs.source" sdkStubsSourceSuffix = ".stubs.source"
sdkApiSuffix = ".api"
sdkXmlFileSuffix = ".xml" sdkXmlFileSuffix = ".xml"
permissionsTemplate = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n` + permissionsTemplate = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n` +
`<!-- Copyright (C) 2018 The Android Open Source Project\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. // The tag to use to depend on the stubs library module.
stubsTag scopeDependencyTag 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. // The tag to use to depend on the stubs source and API module.
stubsSourceAndApiTag scopeDependencyTag stubsSourceAndApiTag scopeDependencyTag
@ -117,6 +124,22 @@ type apiScope struct {
// Extra arguments to pass to droidstubs for this scope. // Extra arguments to pass to droidstubs for this scope.
droidstubsArgs []string 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. // Whether the api scope can be treated as unstable, and should skip compat checks.
unstable bool unstable bool
} }
@ -130,11 +153,41 @@ func initApiScope(scope *apiScope) *apiScope {
apiScope: scope, apiScope: scope,
depInfoExtractor: (*scopePaths).extractStubsLibraryInfoFromDependency, 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{ scope.stubsSourceAndApiTag = scopeDependencyTag{
name: name + "-stubs-source-and-api", name: name + "-stubs-source-and-api",
apiScope: scope, apiScope: scope,
depInfoExtractor: (*scopePaths).extractStubsSourceAndApiInfoFromApiStubsProvider, 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 return scope
} }
@ -146,6 +199,10 @@ func (scope *apiScope) stubsSourceModuleName(baseName string) string {
return baseName + sdkStubsSourceSuffix + scope.moduleSuffix return baseName + sdkStubsSourceSuffix + scope.moduleSuffix
} }
func (scope *apiScope) apiModuleName(baseName string) string {
return baseName + sdkApiSuffix + scope.moduleSuffix
}
func (scope *apiScope) String() string { func (scope *apiScope) String() string {
return scope.name return scope.name
} }
@ -379,17 +436,47 @@ func (paths *scopePaths) extractStubsLibraryInfoFromDependency(dep android.Modul
} }
} }
func (paths *scopePaths) extractStubsSourceAndApiInfoFromApiStubsProvider(dep android.Module) error { func (paths *scopePaths) treatDepAsApiStubsProvider(dep android.Module, action func(provider ApiStubsProvider)) error {
if provider, ok := dep.(ApiStubsProvider); ok { if apiStubsProvider, ok := dep.(ApiStubsProvider); ok {
paths.currentApiFilePath = provider.ApiFilePath() action(apiStubsProvider)
paths.removedApiFilePath = provider.RemovedApiFilePath()
paths.stubsSrcJar = provider.StubsSrcJar()
return nil return nil
} else { } else {
return fmt.Errorf("expected module that implements ApiStubsProvider, e.g. droidstubs") 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 // Common code between sdk library and sdk library import
type commonToSdkLibraryAndImport struct { type commonToSdkLibraryAndImport struct {
scopePaths map[*apiScope]*scopePaths scopePaths map[*apiScope]*scopePaths
@ -488,8 +575,16 @@ func (module *SdkLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
// Add dependencies to the stubs library // Add dependencies to the stubs library
ctx.AddVariationDependencies(nil, apiScope.stubsTag, module.stubsName(apiScope)) 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
ctx.AddVariationDependencies(nil, apiScope.stubsSourceAndApiTag, module.stubsSourceName(apiScope)) // 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) { if !proptools.Bool(module.sdkLibraryProperties.Api_only) {
@ -539,12 +634,18 @@ func (module *SdkLibrary) stubsName(apiScope *apiScope) string {
return apiScope.stubsModuleName(module.BaseModuleName()) return apiScope.stubsModuleName(module.BaseModuleName())
} }
// // Name of the droidstubs module that generates the stubs source and // Name of the droidstubs module that generates the stubs source and may also
// generates/checks the API. // generate/check the API.
func (module *SdkLibrary) stubsSourceName(apiScope *apiScope) string { func (module *SdkLibrary) stubsSourceName(apiScope *apiScope) string {
return apiScope.stubsSourceModuleName(module.BaseModuleName()) 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 // Module name of the runtime implementation library
func (module *SdkLibrary) implName() string { func (module *SdkLibrary) implName() string {
return module.BaseModuleName() 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 // Creates a droidstubs module that creates stubs source files from the given full source
// files and also updates and checks the API specification files. // 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 { props := struct {
Name *string Name *string
Visibility []string Visibility []string
@ -679,6 +780,7 @@ func (module *SdkLibrary) createStubsSourcesAndApi(mctx android.DefaultableHookC
Java_version *string Java_version *string
Merge_annotations_dirs []string Merge_annotations_dirs []string
Merge_inclusion_annotations_dirs []string Merge_inclusion_annotations_dirs []string
Generate_stubs *bool
Check_api struct { Check_api struct {
Current ApiToCheck Current ApiToCheck
Last_released ApiToCheck Last_released ApiToCheck
@ -707,7 +809,7 @@ func (module *SdkLibrary) createStubsSourcesAndApi(mctx android.DefaultableHookC
// * system_modules // * system_modules
// * libs (static_libs/libs) // * 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 // If stubs_source_visibility is not set then the created module will use the
// visibility of this module. // visibility of this module.
@ -751,57 +853,64 @@ func (module *SdkLibrary) createStubsSourcesAndApi(mctx android.DefaultableHookC
} }
droidstubsArgs = append(droidstubsArgs, android.JoinWithPrefix(disabledWarnings, "--hide ")) droidstubsArgs = append(droidstubsArgs, android.JoinWithPrefix(disabledWarnings, "--hide "))
if !createStubSources {
// Stubs are not required.
props.Generate_stubs = proptools.BoolPtr(false)
}
// Add in scope specific arguments. // Add in scope specific arguments.
droidstubsArgs = append(droidstubsArgs, apiScope.droidstubsArgs...) droidstubsArgs = append(droidstubsArgs, scopeSpecificDroidstubsArgs...)
props.Arg_files = module.sdkLibraryProperties.Droiddoc_option_files props.Arg_files = module.sdkLibraryProperties.Droiddoc_option_files
props.Args = proptools.StringPtr(strings.Join(droidstubsArgs, " ")) props.Args = proptools.StringPtr(strings.Join(droidstubsArgs, " "))
// List of APIs identified from the provided source files are created. They are later if createApi {
// compared against to the not-yet-released (a.k.a current) list of APIs and to the // List of APIs identified from the provided source files are created. They are later
// last-released (a.k.a numbered) list of API. // compared against to the not-yet-released (a.k.a current) list of APIs and to the
currentApiFileName := apiScope.apiFilePrefix + "current.txt" // last-released (a.k.a numbered) list of API.
removedApiFileName := apiScope.apiFilePrefix + "removed.txt" currentApiFileName := apiScope.apiFilePrefix + "current.txt"
apiDir := module.getApiDir() removedApiFileName := apiScope.apiFilePrefix + "removed.txt"
currentApiFileName = path.Join(apiDir, currentApiFileName) apiDir := module.getApiDir()
removedApiFileName = path.Join(apiDir, removedApiFileName) currentApiFileName = path.Join(apiDir, currentApiFileName)
removedApiFileName = path.Join(apiDir, removedApiFileName)
// check against the not-yet-release API // check against the not-yet-release API
props.Check_api.Current.Api_file = proptools.StringPtr(currentApiFileName) props.Check_api.Current.Api_file = proptools.StringPtr(currentApiFileName)
props.Check_api.Current.Removed_api_file = proptools.StringPtr(removedApiFileName) props.Check_api.Current.Removed_api_file = proptools.StringPtr(removedApiFileName)
if !apiScope.unstable { if !apiScope.unstable {
// check against the latest released API // check against the latest released API
latestApiFilegroupName := proptools.StringPtr(module.latestApiFilegroupName(apiScope)) latestApiFilegroupName := proptools.StringPtr(module.latestApiFilegroupName(apiScope))
props.Check_api.Last_released.Api_file = latestApiFilegroupName props.Check_api.Last_released.Api_file = latestApiFilegroupName
props.Check_api.Last_released.Removed_api_file = proptools.StringPtr( props.Check_api.Last_released.Removed_api_file = proptools.StringPtr(
module.latestRemovedApiFilegroupName(apiScope)) module.latestRemovedApiFilegroupName(apiScope))
props.Check_api.Ignore_missing_latest_api = proptools.BoolPtr(true) props.Check_api.Ignore_missing_latest_api = proptools.BoolPtr(true)
if proptools.Bool(module.sdkLibraryProperties.Api_lint.Enabled) { if proptools.Bool(module.sdkLibraryProperties.Api_lint.Enabled) {
// Enable api lint. // Enable api lint.
props.Check_api.Api_lint.Enabled = proptools.BoolPtr(true) props.Check_api.Api_lint.Enabled = proptools.BoolPtr(true)
props.Check_api.Api_lint.New_since = latestApiFilegroupName props.Check_api.Api_lint.New_since = latestApiFilegroupName
// If it exists then pass a lint-baseline.txt through to droidstubs. // If it exists then pass a lint-baseline.txt through to droidstubs.
baselinePath := path.Join(apiDir, apiScope.apiFilePrefix+"lint-baseline.txt") baselinePath := path.Join(apiDir, apiScope.apiFilePrefix+"lint-baseline.txt")
baselinePathRelativeToRoot := path.Join(mctx.ModuleDir(), baselinePath) baselinePathRelativeToRoot := path.Join(mctx.ModuleDir(), baselinePath)
paths, err := mctx.GlobWithDeps(baselinePathRelativeToRoot, nil) paths, err := mctx.GlobWithDeps(baselinePathRelativeToRoot, nil)
if err != nil { if err != nil {
mctx.ModuleErrorf("error checking for presence of %s: %s", baselinePathRelativeToRoot, err) mctx.ModuleErrorf("error checking for presence of %s: %s", baselinePathRelativeToRoot, err)
} }
if len(paths) == 1 { if len(paths) == 1 {
props.Check_api.Api_lint.Baseline_file = proptools.StringPtr(baselinePath) props.Check_api.Api_lint.Baseline_file = proptools.StringPtr(baselinePath)
} else if len(paths) != 0 { } else if len(paths) != 0 {
mctx.ModuleErrorf("error checking for presence of %s: expected one path, found: %v", baselinePathRelativeToRoot, paths) mctx.ModuleErrorf("error checking for presence of %s: expected one path, found: %v", baselinePathRelativeToRoot, paths)
}
} }
} }
}
// Dist the api txt artifact for sdk builds. // Dist the api txt artifact for sdk builds.
if !Bool(module.sdkLibraryProperties.No_dist) { if !Bool(module.sdkLibraryProperties.No_dist) {
props.Dist.Targets = []string{"sdk", "win_sdk"} props.Dist.Targets = []string{"sdk", "win_sdk"}
props.Dist.Dest = proptools.StringPtr(fmt.Sprintf("%v.txt", module.BaseModuleName())) props.Dist.Dest = proptools.StringPtr(fmt.Sprintf("%v.txt", module.BaseModuleName()))
props.Dist.Dir = proptools.StringPtr(path.Join(module.apiDistPath(apiScope), "api")) props.Dist.Dir = proptools.StringPtr(path.Join(module.apiDistPath(apiScope), "api"))
}
} }
mctx.CreateModule(DroidstubsFactory, &props) mctx.CreateModule(DroidstubsFactory, &props)
@ -989,8 +1098,24 @@ func (module *SdkLibrary) CreateInternalModules(mctx android.DefaultableHookCont
} }
for _, scope := range generatedScopes { 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.createStubsLibrary(mctx, scope)
module.createStubsSourcesAndApi(mctx, scope)
} }
if !proptools.Bool(module.sdkLibraryProperties.Api_only) { 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.txt
.intermediates/myjavalib.stubs.source.system/android_common/myjavalib.stubs.source.system_api.txt -> sdk_library/system/myjavalib-removed.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.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.api.module_lib/android_common/myjavalib.api.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-removed.txt
`), `),
checkMergeZips( checkMergeZips(
".intermediates/mysdk/common_os/tmp/sdk_library/public/myjavalib_stub_sources.zip", ".intermediates/mysdk/common_os/tmp/sdk_library/public/myjavalib_stub_sources.zip",