// Copyright 2018 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 ( "errors" "fmt" "path" "path/filepath" "reflect" "regexp" "sort" "strings" "sync" "github.com/google/blueprint" "github.com/google/blueprint/proptools" "android/soong/android" "android/soong/dexpreopt" "android/soong/etc" ) const ( sdkXmlFileSuffix = ".xml" ) // A tag to associated a dependency with a specific api scope. type scopeDependencyTag struct { blueprint.BaseDependencyTag name string apiScope *apiScope // Function for extracting appropriate path information from the dependency. depInfoExtractor func(paths *scopePaths, ctx android.ModuleContext, dep android.Module) error } // Extract tag specific information from the dependency. func (tag scopeDependencyTag) extractDepInfo(ctx android.ModuleContext, dep android.Module, paths *scopePaths) { err := tag.depInfoExtractor(paths, ctx, dep) if err != nil { ctx.ModuleErrorf("has an invalid {scopeDependencyTag: %s} dependency on module %s: %s", tag.name, ctx.OtherModuleName(dep), err.Error()) } } var _ android.ReplaceSourceWithPrebuilt = (*scopeDependencyTag)(nil) func (tag scopeDependencyTag) ReplaceSourceWithPrebuilt() bool { return false } // Provides information about an api scope, e.g. public, system, test. type apiScope struct { // The name of the api scope, e.g. public, system, test name string // The api scope that this scope extends. // // This organizes the scopes into an extension hierarchy. // // If set this means that the API provided by this scope includes the API provided by the scope // set in this field. extends *apiScope // The next api scope that a library that uses this scope can access. // // This organizes the scopes into an access hierarchy. // // If set this means that a library that can access this API can also access the API provided by // the scope set in this field. // // A module that sets sdk_version: "_current" should have access to the API of // every java_sdk_library that it depends on. If the library does not provide an API for // then it will traverse up this access hierarchy to find an API that it does provide. // // If this is not set then it defaults to the scope set in extends. canAccess *apiScope // The legacy enabled status for a specific scope can be dependent on other // properties that have been specified on the library so it is provided by // a function that can determine the status by examining those properties. legacyEnabledStatus func(module *SdkLibrary) bool // The default enabled status for non-legacy behavior, which is triggered by // explicitly enabling at least one api scope. defaultEnabledStatus bool // Gets a pointer to the scope specific properties. scopeSpecificProperties func(module *SdkLibrary) *ApiScopeProperties // The name of the field in the dynamically created structure. fieldName string // The name of the property in the java_sdk_library_import propertyName string // The tag to use to depend on the prebuilt stubs library module prebuiltStubsTag scopeDependencyTag // The tag to use to depend on the everything stubs library module. everythingStubsTag scopeDependencyTag // The tag to use to depend on the exportable stubs library module. exportableStubsTag 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 // The tag to use to depend on the module that provides the latest version of the API .txt file. latestApiModuleTag scopeDependencyTag // The tag to use to depend on the module that provides the latest version of the API removed.txt // file. latestRemovedApiModuleTag scopeDependencyTag // The scope specific prefix to add to the api file base of "current.txt" or "removed.txt". apiFilePrefix string // The scope specific suffix to add to the sdk library module name to construct a scope specific // module name. moduleSuffix string // SDK version that the stubs library is built against. Note that this is always // *current. Older stubs library built with a numbered SDK version is created from // the prebuilt jar. sdkVersion string // The annotation that identifies this API level, empty for the public API scope. annotation string // Extra arguments to pass to droidstubs for this scope. // // This is not used directly but is used to construct the droidstubsArgs. extraArgs []string // The args that must be passed to droidstubs to generate the API and stubs source // for this scope, constructed dynamically by initApiScope(). // // The API only includes the additional members that this scope adds over the scope // that it extends. // // The stubs source must include the definitions of everything that is in this // api scope and all the scopes that this one extends. droidstubsArgs []string // Whether the api scope can be treated as unstable, and should skip compat checks. unstable bool // Represents the SDK kind of this scope. kind android.SdkKind } // Initialize a scope, creating and adding appropriate dependency tags func initApiScope(scope *apiScope) *apiScope { name := scope.name scopeByName[name] = scope allScopeNames = append(allScopeNames, name) scope.propertyName = strings.ReplaceAll(name, "-", "_") scope.fieldName = proptools.FieldNameForProperty(scope.propertyName) scope.prebuiltStubsTag = scopeDependencyTag{ name: name + "-stubs", apiScope: scope, depInfoExtractor: (*scopePaths).extractStubsLibraryInfoFromDependency, } scope.everythingStubsTag = scopeDependencyTag{ name: name + "-stubs-everything", apiScope: scope, depInfoExtractor: (*scopePaths).extractEverythingStubsLibraryInfoFromDependency, } scope.exportableStubsTag = scopeDependencyTag{ name: name + "-stubs-exportable", apiScope: scope, depInfoExtractor: (*scopePaths).extractExportableStubsLibraryInfoFromDependency, } 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, } scope.latestApiModuleTag = scopeDependencyTag{ name: name + "-latest-api", apiScope: scope, depInfoExtractor: (*scopePaths).extractLatestApiPath, } scope.latestRemovedApiModuleTag = scopeDependencyTag{ name: name + "-latest-removed-api", apiScope: scope, depInfoExtractor: (*scopePaths).extractLatestRemovedApiPath, } // 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 scopeSpecificArgs []string if scope.annotation != "" { scopeSpecificArgs = []string{"--show-annotation", scope.annotation} } for s := scope; s != nil; s = s.extends { scopeSpecificArgs = append(scopeSpecificArgs, s.extraArgs...) // Ensure that the generated stubs includes all the API elements from the API scope // that this scope extends. if s != scope && s.annotation != "" { scopeSpecificArgs = append(scopeSpecificArgs, "--show-for-stub-purposes-annotation", s.annotation) } } // By default, a library that can access a scope can also access the scope it extends. if scope.canAccess == nil { scope.canAccess = scope.extends } // Escape any special characters in the arguments. This is needed because droidstubs // passes these directly to the shell command. scope.droidstubsArgs = proptools.ShellEscapeList(scopeSpecificArgs) return scope } func (scope *apiScope) stubsLibraryModuleNameSuffix() string { return ".stubs" + scope.moduleSuffix } func (scope *apiScope) exportableStubsLibraryModuleNameSuffix() string { return ".stubs.exportable" + scope.moduleSuffix } func (scope *apiScope) apiLibraryModuleName(baseName string) string { return scope.stubsLibraryModuleName(baseName) + ".from-text" } func (scope *apiScope) sourceStubLibraryModuleName(baseName string) string { return scope.stubsLibraryModuleName(baseName) + ".from-source" } func (scope *apiScope) exportableSourceStubsLibraryModuleName(baseName string) string { return scope.exportableStubsLibraryModuleName(baseName) + ".from-source" } func (scope *apiScope) stubsLibraryModuleName(baseName string) string { return baseName + scope.stubsLibraryModuleNameSuffix() } func (scope *apiScope) exportableStubsLibraryModuleName(baseName string) string { return baseName + scope.exportableStubsLibraryModuleNameSuffix() } func (scope *apiScope) stubsSourceModuleName(baseName string) string { return baseName + ".stubs.source" + scope.moduleSuffix } func (scope *apiScope) apiModuleName(baseName string) string { return baseName + ".api" + scope.moduleSuffix } func (scope *apiScope) String() string { return scope.name } // snapshotRelativeDir returns the snapshot directory into which the files related to scopes will // be stored. func (scope *apiScope) snapshotRelativeDir() string { return filepath.Join("sdk_library", scope.name) } // snapshotRelativeCurrentApiTxtPath returns the snapshot path to the API .txt file for the named // library. func (scope *apiScope) snapshotRelativeCurrentApiTxtPath(name string) string { return filepath.Join(scope.snapshotRelativeDir(), name+".txt") } // snapshotRelativeRemovedApiTxtPath returns the snapshot path to the removed API .txt file for the // named library. func (scope *apiScope) snapshotRelativeRemovedApiTxtPath(name string) string { return filepath.Join(scope.snapshotRelativeDir(), name+"-removed.txt") } type apiScopes []*apiScope func (scopes apiScopes) Strings(accessor func(*apiScope) string) []string { var list []string for _, scope := range scopes { list = append(list, accessor(scope)) } return list } // Method that maps the apiScopes properties to the index of each apiScopes elements. // apiScopes property to be used as the key can be specified with the input accessor. // Only a string property of apiScope can be used as the key of the map. func (scopes apiScopes) MapToIndex(accessor func(*apiScope) string) map[string]int { ret := make(map[string]int) for i, scope := range scopes { ret[accessor(scope)] = i } return ret } var ( scopeByName = make(map[string]*apiScope) allScopeNames []string apiScopePublic = initApiScope(&apiScope{ name: "public", // Public scope is enabled by default for both legacy and non-legacy modes. legacyEnabledStatus: func(module *SdkLibrary) bool { return true }, defaultEnabledStatus: true, scopeSpecificProperties: func(module *SdkLibrary) *ApiScopeProperties { return &module.sdkLibraryProperties.Public }, sdkVersion: "current", kind: android.SdkPublic, }) apiScopeSystem = initApiScope(&apiScope{ name: "system", extends: apiScopePublic, legacyEnabledStatus: (*SdkLibrary).generateTestAndSystemScopesByDefault, scopeSpecificProperties: func(module *SdkLibrary) *ApiScopeProperties { return &module.sdkLibraryProperties.System }, apiFilePrefix: "system-", moduleSuffix: ".system", sdkVersion: "system_current", annotation: "android.annotation.SystemApi(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS)", kind: android.SdkSystem, }) apiScopeTest = initApiScope(&apiScope{ name: "test", extends: apiScopeSystem, legacyEnabledStatus: (*SdkLibrary).generateTestAndSystemScopesByDefault, scopeSpecificProperties: func(module *SdkLibrary) *ApiScopeProperties { return &module.sdkLibraryProperties.Test }, apiFilePrefix: "test-", moduleSuffix: ".test", sdkVersion: "test_current", annotation: "android.annotation.TestApi", unstable: true, kind: android.SdkTest, }) apiScopeModuleLib = initApiScope(&apiScope{ name: "module-lib", extends: apiScopeSystem, // The module-lib scope is disabled by default in legacy mode. // // Enabling this would break existing usages. legacyEnabledStatus: func(module *SdkLibrary) bool { return false }, scopeSpecificProperties: func(module *SdkLibrary) *ApiScopeProperties { return &module.sdkLibraryProperties.Module_lib }, apiFilePrefix: "module-lib-", moduleSuffix: ".module_lib", sdkVersion: "module_current", annotation: "android.annotation.SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)", kind: android.SdkModule, }) apiScopeSystemServer = initApiScope(&apiScope{ name: "system-server", extends: apiScopePublic, // The system-server scope can access the module-lib scope. // // A module that provides a system-server API is appended to the standard bootclasspath that is // used by the system server. So, it should be able to access module-lib APIs provided by // libraries on the bootclasspath. canAccess: apiScopeModuleLib, // The system-server scope is disabled by default in legacy mode. // // Enabling this would break existing usages. legacyEnabledStatus: func(module *SdkLibrary) bool { return false }, scopeSpecificProperties: func(module *SdkLibrary) *ApiScopeProperties { return &module.sdkLibraryProperties.System_server }, apiFilePrefix: "system-server-", moduleSuffix: ".system_server", sdkVersion: "system_server_current", annotation: "android.annotation.SystemApi(client=android.annotation.SystemApi.Client.SYSTEM_SERVER)", extraArgs: []string{ "--hide-annotation", "android.annotation.Hide", // com.android.* classes are okay in this interface" "--hide", "InternalClasses", }, kind: android.SdkSystemServer, }) allApiScopes = apiScopes{ apiScopePublic, apiScopeSystem, apiScopeTest, apiScopeModuleLib, apiScopeSystemServer, } apiLibraryAdditionalProperties = map[string]struct { FullApiSurfaceStubLib string AdditionalApiContribution string }{ "legacy.i18n.module.platform.api": { FullApiSurfaceStubLib: "legacy.core.platform.api.stubs", AdditionalApiContribution: "i18n.module.public.api.stubs.source.api.contribution", }, "stable.i18n.module.platform.api": { FullApiSurfaceStubLib: "stable.core.platform.api.stubs", AdditionalApiContribution: "i18n.module.public.api.stubs.source.api.contribution", }, "conscrypt.module.platform.api": { FullApiSurfaceStubLib: "stable.core.platform.api.stubs", AdditionalApiContribution: "conscrypt.module.public.api.stubs.source.api.contribution", }, } ) var ( javaSdkLibrariesLock sync.Mutex ) // TODO: these are big features that are currently missing // 1) disallowing linking to the runtime shared lib // 2) HTML generation func init() { RegisterSdkLibraryBuildComponents(android.InitRegistrationContext) android.RegisterMakeVarsProvider(pctx, func(ctx android.MakeVarsContext) { javaSdkLibraries := javaSdkLibraries(ctx.Config()) sort.Strings(*javaSdkLibraries) ctx.Strict("JAVA_SDK_LIBRARIES", strings.Join(*javaSdkLibraries, " ")) }) // Register sdk member types. android.RegisterSdkMemberType(javaSdkLibrarySdkMemberType) } func RegisterSdkLibraryBuildComponents(ctx android.RegistrationContext) { ctx.RegisterModuleType("java_sdk_library", SdkLibraryFactory) ctx.RegisterModuleType("java_sdk_library_import", sdkLibraryImportFactory) } // Properties associated with each api scope. type ApiScopeProperties struct { // Indicates whether the api surface is generated. // // If this is set for any scope then all scopes must explicitly specify if they // are enabled. This is to prevent new usages from depending on legacy behavior. // // Otherwise, if this is not set for any scope then the default behavior is // scope specific so please refer to the scope specific property documentation. Enabled *bool // The sdk_version to use for building the stubs. // // If not specified then it will use an sdk_version determined as follows: // // 1) If the sdk_version specified on the java_sdk_library is none then this // will be none. This is used for java_sdk_library instances that are used // to create stubs that contribute to the core_current sdk version. // 2) Otherwise, it is assumed that this library extends but does not // contribute directly to a specific sdk_version and so this uses the // sdk_version appropriate for the api scope. e.g. public will use // sdk_version: current, system will use sdk_version: system_current, etc. // // This does not affect the sdk_version used for either generating the stubs source // or the API file. They both have to use the same sdk_version as is used for // compiling the implementation library. Sdk_version *string // Extra libs used when compiling stubs for this scope. Libs []string } type sdkLibraryProperties struct { // List of source files that are needed to compile the API, but are not part of runtime library. Api_srcs []string `android:"arch_variant"` // Visibility for impl library module. If not specified then defaults to the // visibility property. Impl_library_visibility []string // Visibility for stubs library modules. If not specified then defaults to the // visibility property. Stubs_library_visibility []string // Visibility for stubs source modules. If not specified then defaults to the // visibility property. Stubs_source_visibility []string // List of Java libraries that will be in the classpath when building the implementation lib Impl_only_libs []string `android:"arch_variant"` // List of Java libraries that will included in the implementation lib. Impl_only_static_libs []string `android:"arch_variant"` // List of Java libraries that will be in the classpath when building stubs Stub_only_libs []string `android:"arch_variant"` // List of Java libraries that will included in stub libraries Stub_only_static_libs []string `android:"arch_variant"` // list of package names that will be documented and publicized as API. // This allows the API to be restricted to a subset of the source files provided. // If this is unspecified then all the source files will be treated as being part // of the API. Api_packages []string // list of package names that must be hidden from the API Hidden_api_packages []string // the relative path to the directory containing the api specification files. // Defaults to "api". Api_dir *string // Determines whether a runtime implementation library is built; defaults to false. // // If true then it also prevents the module from being used as a shared module, i.e. // it is as if shared_library: false, was set. Api_only *bool // local files that are used within user customized droiddoc options. Droiddoc_option_files []string // additional droiddoc options. // Available variables for substitution: // // $(location