// 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 ( "android/soong/android" "android/soong/genrule" "fmt" "io" "path" "sort" "strings" "sync" "github.com/google/blueprint" "github.com/google/blueprint/proptools" ) var ( sdkStubsLibrarySuffix = ".stubs" sdkSystemApiSuffix = ".system" sdkTestApiSuffix = ".test" sdkDocsSuffix = ".docs" sdkImplLibrarySuffix = ".impl" sdkXmlFileSuffix = ".xml" ) type stubsLibraryDependencyTag struct { blueprint.BaseDependencyTag name string } var ( publicApiStubsTag = dependencyTag{name: "public"} systemApiStubsTag = dependencyTag{name: "system"} testApiStubsTag = dependencyTag{name: "test"} implLibTag = dependencyTag{name: "platform"} publicApiFileTag = dependencyTag{name: "publicApi"} systemApiFileTag = dependencyTag{name: "systemApi"} testApiFileTag = dependencyTag{name: "testApi"} ) type apiScope int const ( apiScopePublic apiScope = iota apiScopeSystem apiScopeTest ) var ( javaSdkLibrariesLock sync.Mutex ) // java_sdk_library is to make a Java library that implements optional platform APIs to apps. // It is actually a wrapper of several modules: 1) stubs library that clients are linked against // to, 2) droiddoc module that internally generates API stubs source files, 3) the real runtime // shared library that implements the APIs, and 4) XML file for adding the runtime lib to the // classpath at runtime if requested via . // // TODO: these are big features that are currently missing // 1) disallowing linking to the runtime shared lib // 2) HTML generation func init() { android.RegisterModuleType("java_sdk_library", sdkLibraryFactory) android.PreArchMutators(func(ctx android.RegisterMutatorsContext) { ctx.TopDown("java_sdk_library", sdkLibraryMutator).Parallel() }) android.RegisterMakeVarsProvider(pctx, func(ctx android.MakeVarsContext) { javaSdkLibraries := javaSdkLibraries(ctx.Config()) sort.Strings(*javaSdkLibraries) ctx.Strict("JAVA_SDK_LIBRARIES", strings.Join(*javaSdkLibraries, " ")) }) } type sdkLibraryProperties struct { // list of source files used to compile the Java module. May be .java, .logtags, .proto, // or .aidl files. Srcs []string `android:"arch_variant"` // list of optional source files that are part of API but not part of runtime library. Api_srcs []string `android:"arch_variant"` // list of of java libraries that will be in the classpath Libs []string `android:"arch_variant"` // list of java libraries that will be compiled into the resulting runtime jar. // These libraries are not compiled into the stubs jar. 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 package names that will be documented and publicized as API Api_packages []string // list of package names that must be hidden from the API Hidden_api_packages []string Errorprone struct { // List of javac flags that should only be used when running errorprone. Javacflags []string } // Additional droiddoc options Droiddoc_options []string // If set to true, compile dex regardless of installable. Defaults to false. // This applies to the stubs lib. Compile_dex *bool // the sub dirs under srcs_lib_whitelist_dirs will be scanned for java srcs. // Defaults to "android.annotation". Srcs_lib_whitelist_pkgs []string // if set to true, create stubs through Metalava instead of Doclava. Javadoc/Doclava is // currently still used for documentation generation, and will be replaced by Dokka soon. // Defaults to true. Metalava_enabled *bool // TODO: determines whether to create HTML doc or not //Html_doc *bool } type sdkLibrary struct { android.ModuleBase android.DefaultableModuleBase properties sdkLibraryProperties deviceProperties CompilerDeviceProperties publicApiStubsPath android.Paths systemApiStubsPath android.Paths testApiStubsPath android.Paths implLibPath android.Paths publicApiStubsImplPath android.Paths systemApiStubsImplPath android.Paths testApiStubsImplPath android.Paths implLibImplPath android.Paths publicApiFilePath android.Path systemApiFilePath android.Path testApiFilePath android.Path } func (module *sdkLibrary) DepsMutator(ctx android.BottomUpMutatorContext) { // Add dependencies to the stubs library ctx.AddVariationDependencies(nil, publicApiStubsTag, module.stubsName(apiScopePublic)) ctx.AddVariationDependencies(nil, systemApiStubsTag, module.stubsName(apiScopeSystem)) ctx.AddVariationDependencies(nil, testApiStubsTag, module.stubsName(apiScopeTest)) ctx.AddVariationDependencies(nil, implLibTag, module.implName()) ctx.AddVariationDependencies(nil, publicApiFileTag, module.docsName(apiScopePublic)) ctx.AddVariationDependencies(nil, systemApiFileTag, module.docsName(apiScopeSystem)) ctx.AddVariationDependencies(nil, testApiFileTag, module.docsName(apiScopeTest)) } func (module *sdkLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) { // Record the paths to the header jars of the library (stubs and impl). // When this java_sdk_library is dependened from others via "libs" property, // the recorded paths will be returned depending on the link type of the caller. ctx.VisitDirectDeps(func(to android.Module) { otherName := ctx.OtherModuleName(to) tag := ctx.OtherModuleDependencyTag(to) if lib, ok := to.(Dependency); ok { switch tag { case publicApiStubsTag: module.publicApiStubsPath = lib.HeaderJars() module.publicApiStubsImplPath = lib.ImplementationJars() case systemApiStubsTag: module.systemApiStubsPath = lib.HeaderJars() module.systemApiStubsImplPath = lib.ImplementationJars() case testApiStubsTag: module.testApiStubsPath = lib.HeaderJars() module.testApiStubsImplPath = lib.ImplementationJars() case implLibTag: module.implLibPath = lib.HeaderJars() module.implLibImplPath = lib.ImplementationJars() default: ctx.ModuleErrorf("depends on module %q of unknown tag %q", otherName, tag) } } if doc, ok := to.(ApiFilePath); ok { switch tag { case publicApiFileTag: module.publicApiFilePath = doc.ApiFilePath() case systemApiFileTag: module.systemApiFilePath = doc.ApiFilePath() case testApiFileTag: module.testApiFilePath = doc.ApiFilePath() default: ctx.ModuleErrorf("depends on module %q of unknown tag %q", otherName, tag) } } }) } func (module *sdkLibrary) AndroidMk() android.AndroidMkData { return android.AndroidMkData{ Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { // Create a phony module that installs the impl library, for the case when this lib is // in PRODUCT_PACKAGES. fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)") fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir) fmt.Fprintln(w, "LOCAL_MODULE :=", name) fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES := "+module.implName()) fmt.Fprintln(w, "include $(BUILD_PHONY_PACKAGE)") owner := module.ModuleBase.Owner() if owner == "" { owner = "android" } // Create dist rules to install the stubs libs to the dist dir if len(module.publicApiStubsPath) == 1 { fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+ module.publicApiStubsPath.Strings()[0]+ ":"+path.Join("apistubs", owner, "public", module.BaseModuleName()+".jar")+")") } if len(module.systemApiStubsPath) == 1 { fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+ module.systemApiStubsPath.Strings()[0]+ ":"+path.Join("apistubs", owner, "system", module.BaseModuleName()+".jar")+")") } if len(module.testApiStubsPath) == 1 { fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+ module.testApiStubsPath.Strings()[0]+ ":"+path.Join("apistubs", owner, "test", module.BaseModuleName()+".jar")+")") } if module.publicApiFilePath != nil { fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+ module.publicApiFilePath.String()+ ":"+path.Join("apistubs", owner, "public", "api", module.BaseModuleName()+".txt")+")") } if module.systemApiFilePath != nil { fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+ module.systemApiFilePath.String()+ ":"+path.Join("apistubs", owner, "system", "api", module.BaseModuleName()+".txt")+")") } if module.testApiFilePath != nil { fmt.Fprintln(w, "$(call dist-for-goals,sdk win_sdk,"+ module.testApiFilePath.String()+ ":"+path.Join("apistubs", owner, "test", "api", module.BaseModuleName()+".txt")+")") } }, } } // Module name of the stubs library func (module *sdkLibrary) stubsName(apiScope apiScope) string { stubsName := module.BaseModuleName() + sdkStubsLibrarySuffix switch apiScope { case apiScopeSystem: stubsName = stubsName + sdkSystemApiSuffix case apiScopeTest: stubsName = stubsName + sdkTestApiSuffix } return stubsName } // Module name of the docs func (module *sdkLibrary) docsName(apiScope apiScope) string { docsName := module.BaseModuleName() + sdkDocsSuffix switch apiScope { case apiScopeSystem: docsName = docsName + sdkSystemApiSuffix case apiScopeTest: docsName = docsName + sdkTestApiSuffix } return docsName } // Module name of the runtime implementation library func (module *sdkLibrary) implName() string { return module.BaseModuleName() + sdkImplLibrarySuffix } // File path to the runtime implementation library func (module *sdkLibrary) implPath() string { partition := "system" if module.SocSpecific() { partition = "vendor" } else if module.DeviceSpecific() { partition = "odm" } else if module.ProductSpecific() { partition = "product" } return "/" + partition + "/framework/" + module.implName() + ".jar" } // Module name of the XML file for the lib func (module *sdkLibrary) xmlFileName() string { return module.BaseModuleName() + sdkXmlFileSuffix } // SDK version that the stubs library is built against. Note that this is always // *current. Older stubs library built with a numberd SDK version is created from // the prebuilt jar. func (module *sdkLibrary) sdkVersion(apiScope apiScope) string { switch apiScope { case apiScopePublic: return "current" case apiScopeSystem: return "system_current" case apiScopeTest: return "test_current" default: return "current" } } // $(INTERNAL_PLATFORM__API_FILE) points to the generated // api file for the current source // TODO: remove this when apicheck is done in soong func (module *sdkLibrary) apiTagName(apiScope apiScope) string { apiTagName := strings.Replace(strings.ToUpper(module.BaseModuleName()), ".", "_", -1) switch apiScope { case apiScopeSystem: apiTagName = apiTagName + "_SYSTEM" case apiScopeTest: apiTagName = apiTagName + "_TEST" } return apiTagName } func (module *sdkLibrary) latestApiFilegroupName(apiScope apiScope) string { name := ":" + module.BaseModuleName() + ".api." switch apiScope { case apiScopePublic: name = name + "public" case apiScopeSystem: name = name + "system" case apiScopeTest: name = name + "test" } name = name + ".latest" return name } func (module *sdkLibrary) latestRemovedApiFilegroupName(apiScope apiScope) string { name := ":" + module.BaseModuleName() + "-removed.api." switch apiScope { case apiScopePublic: name = name + "public" case apiScopeSystem: name = name + "system" case apiScopeTest: name = name + "test" } name = name + ".latest" return name } // Creates a static java library that has API stubs func (module *sdkLibrary) createStubsLibrary(mctx android.TopDownMutatorContext, apiScope apiScope) { props := struct { Name *string Srcs []string Sdk_version *string Libs []string Soc_specific *bool Device_specific *bool Product_specific *bool Compile_dex *bool Product_variables struct { Unbundled_build struct { Enabled *bool } Pdk struct { Enabled *bool } } }{} props.Name = proptools.StringPtr(module.stubsName(apiScope)) // sources are generated from the droiddoc props.Srcs = []string{":" + module.docsName(apiScope)} props.Sdk_version = proptools.StringPtr(module.sdkVersion(apiScope)) props.Libs = module.properties.Stub_only_libs // Unbundled apps will use the prebult one from /prebuilts/sdk props.Product_variables.Unbundled_build.Enabled = proptools.BoolPtr(false) props.Product_variables.Pdk.Enabled = proptools.BoolPtr(false) if module.properties.Compile_dex != nil { props.Compile_dex = module.properties.Compile_dex } if module.SocSpecific() { props.Soc_specific = proptools.BoolPtr(true) } else if module.DeviceSpecific() { props.Device_specific = proptools.BoolPtr(true) } else if module.ProductSpecific() { props.Product_specific = proptools.BoolPtr(true) } mctx.CreateModule(android.ModuleFactoryAdaptor(LibraryFactory), &props) } // Creates a droiddoc module that creates stubs source files from the given full source // files func (module *sdkLibrary) createDocs(mctx android.TopDownMutatorContext, apiScope apiScope) { props := struct { Name *string Srcs []string Installable *bool Srcs_lib *string Srcs_lib_whitelist_dirs []string Srcs_lib_whitelist_pkgs []string Libs []string Args *string Api_tag_name *string Api_filename *string Removed_api_filename *string Check_api struct { Current ApiToCheck Last_released ApiToCheck } Aidl struct { Include_dirs []string Local_include_dirs []string } }{} droiddocProps := struct { Custom_template *string }{} props.Name = proptools.StringPtr(module.docsName(apiScope)) props.Srcs = append(props.Srcs, module.properties.Srcs...) props.Srcs = append(props.Srcs, module.properties.Api_srcs...) props.Installable = proptools.BoolPtr(false) // A droiddoc module has only one Libs property and doesn't distinguish between // shared libs and static libs. So we need to add both of these libs to Libs property. props.Libs = module.properties.Libs props.Libs = append(props.Libs, module.properties.Static_libs...) props.Aidl.Include_dirs = module.deviceProperties.Aidl.Include_dirs props.Aidl.Local_include_dirs = module.deviceProperties.Aidl.Local_include_dirs if module.properties.Metalava_enabled == nil { module.properties.Metalava_enabled = proptools.BoolPtr(true) } droiddocArgs := "" if Bool(module.properties.Metalava_enabled) == true { droiddocArgs = " --stub-packages " + strings.Join(module.properties.Api_packages, ":") + " " + android.JoinWithPrefix(module.properties.Hidden_api_packages, " --hide-package ") + " " + android.JoinWithPrefix(module.properties.Droiddoc_options, " ") + " --hide MissingPermission --hide BroadcastBehavior " + "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " + "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo" } else { droiddocProps.Custom_template = proptools.StringPtr("droiddoc-templates-sdk") droiddocArgs = " -stubpackages " + strings.Join(module.properties.Api_packages, ":") + " " + android.JoinWithPrefix(module.properties.Hidden_api_packages, " -hidePackage ") + " " + android.JoinWithPrefix(module.properties.Droiddoc_options, " ") + " -hide 110 -hide 111 -hide 113 -hide 121 -hide 125 -hide 126 -hide 127 -hide 128 -nodocs" } switch apiScope { case apiScopeSystem: droiddocArgs = droiddocArgs + " -showAnnotation android.annotation.SystemApi" case apiScopeTest: droiddocArgs = droiddocArgs + " -showAnnotation android.annotation.TestApi" } props.Args = proptools.StringPtr(droiddocArgs) // 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. currentApiFileName := "current.txt" removedApiFileName := "removed.txt" switch apiScope { case apiScopeSystem: currentApiFileName = "system-" + currentApiFileName removedApiFileName = "system-" + removedApiFileName case apiScopeTest: currentApiFileName = "test-" + currentApiFileName removedApiFileName = "test-" + removedApiFileName } currentApiFileName = path.Join("api", currentApiFileName) removedApiFileName = path.Join("api", removedApiFileName) // TODO(jiyong): remove these three props props.Api_tag_name = proptools.StringPtr(module.apiTagName(apiScope)) props.Api_filename = proptools.StringPtr(currentApiFileName) props.Removed_api_filename = proptools.StringPtr(removedApiFileName) // check against the not-yet-release API props.Check_api.Current.Api_file = proptools.StringPtr(currentApiFileName) props.Check_api.Current.Removed_api_file = proptools.StringPtr(removedApiFileName) // check against the latest released API props.Check_api.Last_released.Api_file = proptools.StringPtr( module.latestApiFilegroupName(apiScope)) props.Check_api.Last_released.Removed_api_file = proptools.StringPtr( module.latestRemovedApiFilegroupName(apiScope)) if Bool(module.properties.Metalava_enabled) == false { // any change is reported as error props.Check_api.Current.Args = proptools.StringPtr("-error 2 -error 3 -error 4 -error 5 " + "-error 6 -error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 " + "-error 14 -error 15 -error 16 -error 17 -error 18 -error 19 -error 20 " + "-error 21 -error 23 -error 24 -error 25 -error 26 -error 27") // backward incompatible changes are reported as error props.Check_api.Last_released.Args = proptools.StringPtr("-hide 2 -hide 3 -hide 4 -hide 5 " + "-hide 6 -hide 24 -hide 25 -hide 26 -hide 27 " + "-error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 " + "-error 15 -error 16 -error 17 -error 18") // Include the part of the framework source. This is required for the case when // API class is extending from the framework class. In that case, doclava needs // to know whether the base class is hidden or not. Since that information is // encoded as @hide string in the comment, we need source files for the classes, // not the compiled ones. props.Srcs_lib = proptools.StringPtr("framework") props.Srcs_lib_whitelist_dirs = []string{"core/java"} // Add android.annotation package to give access to the framework-defined // annotations such as SystemApi, NonNull, etc. if module.properties.Srcs_lib_whitelist_pkgs != nil { props.Srcs_lib_whitelist_pkgs = module.properties.Srcs_lib_whitelist_pkgs } else { props.Srcs_lib_whitelist_pkgs = []string{"android.annotation"} } } // These libs are required by doclava to parse the framework sources add via // Src_lib and Src_lib_whitelist_* properties just above. // If we don't add them to the classpath, errors messages are generated by doclava, // though they don't break the build. props.Libs = append(props.Libs, "framework") if Bool(module.properties.Metalava_enabled) == true { mctx.CreateModule(android.ModuleFactoryAdaptor(DroidstubsFactory), &props) } else { mctx.CreateModule(android.ModuleFactoryAdaptor(DroiddocFactory), &props, &droiddocProps) } } // Creates the runtime library. This is not directly linkable from other modules. func (module *sdkLibrary) createImplLibrary(mctx android.TopDownMutatorContext) { props := struct { Name *string Srcs []string Libs []string Static_libs []string Soc_specific *bool Device_specific *bool Product_specific *bool Installable *bool Required []string Errorprone struct { Javacflags []string } }{} props.Name = proptools.StringPtr(module.implName()) props.Srcs = module.properties.Srcs props.Libs = module.properties.Libs props.Static_libs = module.properties.Static_libs props.Installable = proptools.BoolPtr(true) // XML file is installed along with the impl lib props.Required = []string{module.xmlFileName()} props.Errorprone.Javacflags = module.properties.Errorprone.Javacflags if module.SocSpecific() { props.Soc_specific = proptools.BoolPtr(true) } else if module.DeviceSpecific() { props.Device_specific = proptools.BoolPtr(true) } else if module.ProductSpecific() { props.Product_specific = proptools.BoolPtr(true) } mctx.CreateModule(android.ModuleFactoryAdaptor(LibraryFactory), &props, &module.deviceProperties) } // Creates the xml file that publicizes the runtime library func (module *sdkLibrary) createXmlFile(mctx android.TopDownMutatorContext) { template := ` ` // genrule to generate the xml file content from the template above // TODO: preserve newlines in the generate xml file. Newlines are being squashed // in the ninja file. Do we need to have an external tool for this? xmlContent := fmt.Sprintf(template, module.BaseModuleName(), module.implPath()) genruleProps := struct { Name *string Cmd *string Out []string }{} genruleProps.Name = proptools.StringPtr(module.xmlFileName() + "-gen") genruleProps.Cmd = proptools.StringPtr("echo '" + xmlContent + "' > $(out)") genruleProps.Out = []string{module.xmlFileName()} mctx.CreateModule(android.ModuleFactoryAdaptor(genrule.GenRuleFactory), &genruleProps) // creates a prebuilt_etc module to actually place the xml file under // /etc/permissions etcProps := struct { Name *string Src *string Sub_dir *string Soc_specific *bool Device_specific *bool Product_specific *bool }{} etcProps.Name = proptools.StringPtr(module.xmlFileName()) etcProps.Src = proptools.StringPtr(":" + module.xmlFileName() + "-gen") etcProps.Sub_dir = proptools.StringPtr("permissions") if module.SocSpecific() { etcProps.Soc_specific = proptools.BoolPtr(true) } else if module.DeviceSpecific() { etcProps.Device_specific = proptools.BoolPtr(true) } else if module.ProductSpecific() { etcProps.Product_specific = proptools.BoolPtr(true) } mctx.CreateModule(android.ModuleFactoryAdaptor(android.PrebuiltEtcFactory), &etcProps) } // to satisfy SdkLibraryDependency interface func (module *sdkLibrary) HeaderJars(linkType linkType) android.Paths { // This module is just a wrapper for the stubs. if linkType == javaSystem { return module.systemApiStubsPath } else if linkType == javaPlatform { return module.implLibPath } else { return module.publicApiStubsPath } } // to satisfy SdkLibraryDependency interface func (module *sdkLibrary) ImplementationJars(linkType linkType) android.Paths { // This module is just a wrapper for the stubs. if linkType == javaSystem { return module.systemApiStubsImplPath } else if linkType == javaPlatform { return module.implLibImplPath } else { return module.publicApiStubsImplPath } } func javaSdkLibraries(config android.Config) *[]string { return config.Once("javaSdkLibraries", func() interface{} { return &[]string{} }).(*[]string) } // For a java_sdk_library module, create internal modules for stubs, docs, // runtime libs and xml file. If requested, the stubs and docs are created twice // once for public API level and once for system API level func sdkLibraryMutator(mctx android.TopDownMutatorContext) { if module, ok := mctx.Module().(*sdkLibrary); ok { if module.properties.Srcs == nil { mctx.PropertyErrorf("srcs", "java_sdk_library must specify srcs") } if module.properties.Api_packages == nil { mctx.PropertyErrorf("api_packages", "java_sdk_library must specify api_packages") } // for public API stubs module.createStubsLibrary(mctx, apiScopePublic) module.createDocs(mctx, apiScopePublic) // for system API stubs module.createStubsLibrary(mctx, apiScopeSystem) module.createDocs(mctx, apiScopeSystem) // for test API stubs module.createStubsLibrary(mctx, apiScopeTest) module.createDocs(mctx, apiScopeTest) // for runtime module.createXmlFile(mctx) module.createImplLibrary(mctx) // record java_sdk_library modules so that they are exported to make javaSdkLibraries := javaSdkLibraries(mctx.Config()) javaSdkLibrariesLock.Lock() defer javaSdkLibrariesLock.Unlock() *javaSdkLibraries = append(*javaSdkLibraries, module.BaseModuleName()) } } func sdkLibraryFactory() android.Module { module := &sdkLibrary{} module.AddProperties(&module.properties) module.AddProperties(&module.deviceProperties) InitJavaModule(module, android.DeviceSupported) return module }