// 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 ( "fmt" "path" "path/filepath" "reflect" "regexp" "sort" "strings" "sync" "github.com/google/blueprint" "github.com/google/blueprint/proptools" "android/soong/android" ) const ( sdkXmlFileSuffix = ".xml" permissionsTemplate = `\n` + `\n` + `\n` + ` \n` + `\n` ) // 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, 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, dep) if err != nil { ctx.ModuleErrorf("has an invalid {scopeDependencyTag: %s} dependency on module %s: %s", tag.name, ctx.OtherModuleName(dep), err.Error()) } } // 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. extends *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 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 // The scope specific prefix to add to the api file base of "current.txt" or "removed.txt". apiFilePrefix string // The scope specific prefix 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 // 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 } // 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.stubsTag = scopeDependencyTag{ name: name + "-stubs", 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 } func (scope *apiScope) stubsLibraryModuleName(baseName string) string { return baseName + ".stubs" + scope.moduleSuffix } 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 } 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 } 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", }) 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", droidstubsArgs: []string{"-showAnnotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)"}, }) apiScopeTest = initApiScope(&apiScope{ name: "test", extends: apiScopePublic, legacyEnabledStatus: (*SdkLibrary).generateTestAndSystemScopesByDefault, scopeSpecificProperties: func(module *SdkLibrary) *ApiScopeProperties { return &module.sdkLibraryProperties.Test }, apiFilePrefix: "test-", moduleSuffix: ".test", sdkVersion: "test_current", droidstubsArgs: []string{"-showAnnotation android.annotation.TestApi"}, unstable: true, }) apiScopeModuleLib = initApiScope(&apiScope{ name: "module-lib", extends: apiScopeSystem, // 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", droidstubsArgs: []string{ "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\)", }, }) allApiScopes = apiScopes{ apiScopePublic, apiScopeSystem, apiScopeTest, apiScopeModuleLib, } ) 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(&sdkLibrarySdkMemberType{ android.SdkMemberTypeBase{ PropertyName: "java_sdk_libs", SupportsSdk: true, }, }) } 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 } type sdkLibraryProperties struct { // 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 stubs Stub_only_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 // If set to true there is no runtime library. 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