// Copyright 2019 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 ( "github.com/google/blueprint" "android/soong/android" ) var hiddenAPIGenerateCSVRule = pctx.AndroidStaticRule("hiddenAPIGenerateCSV", blueprint.RuleParams{ Command: "${config.Class2NonSdkList} --stub-api-flags ${stubAPIFlags} $in $outFlag $out", CommandDeps: []string{"${config.Class2NonSdkList}"}, }, "outFlag", "stubAPIFlags") type hiddenAPI struct { // The name of the module as it would be used in the boot jars configuration, e.g. without any // prebuilt_ prefix (if it is a prebuilt) and without any ".impl" suffix if it is a // java_sdk_library implementation library. configurationName string // True if the module containing this structure contributes to the hiddenapi information or has // that information encoded within it. active bool // Identifies the active module variant which will be used as the source of hiddenapi information. // // A class may be compiled into a number of different module variants each of which will need the // hiddenapi information encoded into it and so will be marked as active. However, only one of // them must be used as a source of information by hiddenapi otherwise it will end up with // duplicate entries. That module will have primary=true. // // Note, that modules -hiddenapi that provide additional annotation information for module // that is on the bootclasspath are marked as primary=true as they are the primary source of that // annotation information. primary bool // The path to the dex jar that is in the boot class path. If this is nil then the associated // module is not a boot jar, but could be one of the -hiddenapi modules that provide additional // annotations for the boot dex jar but which do not actually provide a boot dex jar // themselves. // // This must be the path to the unencoded dex jar as the encoded dex jar indirectly depends on // this file so using the encoded dex jar here would result in a cycle in the ninja rules. bootDexJarPath android.Path // The path to the CSV file that contains mappings from Java signature to various flags derived // from annotations in the source, e.g. whether it is public or the sdk version above which it // can no longer be used. // // It is created by the Class2NonSdkList tool which processes the .class files in the class // implementation jar looking for UnsupportedAppUsage and CovariantReturnType annotations. The // tool also consumes the hiddenAPISingletonPathsStruct.stubFlags file in order to perform // consistency checks on the information in the annotations and to filter out bridge methods // that are already part of the public API. flagsCSVPath android.Path // The path to the CSV file that contains mappings from Java signature to the value of properties // specified on UnsupportedAppUsage annotations in the source. // // Like the flagsCSVPath file this is also created by the Class2NonSdkList in the same way. // Although the two files could potentially be created in a single invocation of the // Class2NonSdkList at the moment they are created using their own invocation, with the behavior // being determined by the property that is used. metadataCSVPath android.Path // The path to the CSV file that contains mappings from Java signature to source location // information. // // It is created by the merge_csv tool which processes the class implementation jar, extracting // all the files ending in .uau (which are CSV files) and merges them together. The .uau files are // created by the unsupported app usage annotation processor during compilation of the class // implementation jar. indexCSVPath android.Path } func (h *hiddenAPI) flagsCSV() android.Path { return h.flagsCSVPath } func (h *hiddenAPI) metadataCSV() android.Path { return h.metadataCSVPath } func (h *hiddenAPI) bootDexJar() android.Path { return h.bootDexJarPath } func (h *hiddenAPI) indexCSV() android.Path { return h.indexCSVPath } type hiddenAPIIntf interface { bootDexJar() android.Path flagsCSV() android.Path indexCSV() android.Path metadataCSV() android.Path } var _ hiddenAPIIntf = (*hiddenAPI)(nil) // Initialize the hiddenapi structure func (h *hiddenAPI) initHiddenAPI(ctx android.BaseModuleContext, configurationName string) { // If hiddenapi processing is disabled treat this as inactive. if ctx.Config().IsEnvTrue("UNSAFE_DISABLE_HIDDENAPI_FLAGS") { return } h.configurationName = configurationName // It is important that hiddenapi information is only gathered for/from modules that are actually // on the boot jars list because the runtime only enforces access to the hidden API for the // bootclassloader. If information is gathered for modules not on the list then that will cause // failures in the CtsHiddenApiBlocklist... tests. module := ctx.Module() h.active = isModuleInBootClassPath(ctx, module) if !h.active { // The rest of the properties will be ignored if active is false. return } // Determine whether this module is the primary module or not. primary := true // A prebuilt module is only primary if it is preferred and conversely a source module is only // primary if it has not been replaced by a prebuilt module. if pi, ok := module.(android.PrebuiltInterface); ok { if p := pi.Prebuilt(); p != nil { primary = p.UsePrebuilt() } } else { // The only module that will pass a different configurationName to its module name to this // method is the implementation library of a java_sdk_library. It has a configuration name of // the same as its parent java_sdk_library but a module name of .impl. It is not the // primary module, the java_sdk_library with the name of is. primary = configurationName == ctx.ModuleName() // A source module that has been replaced by a prebuilt can never be the primary module. if module.IsReplacedByPrebuilt() { ctx.VisitDirectDepsWithTag(android.PrebuiltDepTag, func(prebuilt android.Module) { if h, ok := prebuilt.(hiddenAPIIntf); ok && h.bootDexJar() != nil { primary = false } else { ctx.ModuleErrorf( "hiddenapi has determined that the source module %q should be ignored as it has been"+ " replaced by the prebuilt module %q but unfortunately it does not provide a"+ " suitable boot dex jar", ctx.ModuleName(), ctx.OtherModuleName(prebuilt)) } }) } } h.primary = primary } func isModuleInBootClassPath(ctx android.BaseModuleContext, module android.Module) bool { // Get the configured non-updatable and updatable boot jars. nonUpdatableBootJars := ctx.Config().NonUpdatableBootJars() updatableBootJars := ctx.Config().UpdatableBootJars() active := isModuleInConfiguredList(ctx, module, nonUpdatableBootJars) || isModuleInConfiguredList(ctx, module, updatableBootJars) return active } // hiddenAPIExtractAndEncode is called by any module that could contribute to the hiddenapi // processing. // // It ignores any module that has not had initHiddenApi() called on it and which is not in the boot // jar list. // // Otherwise, it generates ninja rules to do the following: // 1. Extract information needed for hiddenapi processing from the module and output it into CSV // files. // 2. Conditionally adds the supplied dex file to the list of files used to generate the // hiddenAPISingletonPathsStruct.stubsFlag file. // 3. Conditionally creates a copy of the supplied dex file into which it has encoded the hiddenapi // flags and returns this instead of the supplied dex jar, otherwise simply returns the supplied // dex jar. func (h *hiddenAPI) hiddenAPIExtractAndEncode(ctx android.ModuleContext, dexJar android.OutputPath, implementationJar android.Path, uncompressDex bool) android.OutputPath { if !h.active { return dexJar } h.hiddenAPIExtractInformation(ctx, dexJar, implementationJar) hiddenAPIJar := android.PathForModuleOut(ctx, "hiddenapi", h.configurationName+".jar").OutputPath // Create a copy of the dex jar which has been encoded with hiddenapi flags. hiddenAPIEncodeDex(ctx, hiddenAPIJar, dexJar, uncompressDex) // Use the encoded dex jar from here onwards. dexJar = hiddenAPIJar return dexJar } // hiddenAPIExtractInformation generates ninja rules to extract the information from the classes // jar, and outputs it to the appropriate module specific CSV file. // // It also makes the dex jar available for use when generating the // hiddenAPISingletonPathsStruct.stubFlags. func (h *hiddenAPI) hiddenAPIExtractInformation(ctx android.ModuleContext, dexJar, classesJar android.Path) { if !h.active { return } // More than one library with the same classes may need to be encoded but only one should be // used as a source of information for hidden API processing otherwise it will result in // duplicate entries in the files. if !h.primary { return } classesJars := android.Paths{classesJar} ctx.VisitDirectDepsWithTag(hiddenApiAnnotationsTag, func(dep android.Module) { javaInfo := ctx.OtherModuleProvider(dep, JavaInfoProvider).(JavaInfo) classesJars = append(classesJars, javaInfo.ImplementationJars...) }) stubFlagsCSV := hiddenAPISingletonPaths(ctx).stubFlags flagsCSV := android.PathForModuleOut(ctx, "hiddenapi", "flags.csv") ctx.Build(pctx, android.BuildParams{ Rule: hiddenAPIGenerateCSVRule, Description: "hiddenapi flags", Inputs: classesJars, Output: flagsCSV, Implicit: stubFlagsCSV, Args: map[string]string{ "outFlag": "--write-flags-csv", "stubAPIFlags": stubFlagsCSV.String(), }, }) h.flagsCSVPath = flagsCSV metadataCSV := android.PathForModuleOut(ctx, "hiddenapi", "metadata.csv") ctx.Build(pctx, android.BuildParams{ Rule: hiddenAPIGenerateCSVRule, Description: "hiddenapi metadata", Inputs: classesJars, Output: metadataCSV, Implicit: stubFlagsCSV, Args: map[string]string{ "outFlag": "--write-metadata-csv", "stubAPIFlags": stubFlagsCSV.String(), }, }) h.metadataCSVPath = metadataCSV indexCSV := android.PathForModuleOut(ctx, "hiddenapi", "index.csv") rule := android.NewRuleBuilder(pctx, ctx) rule.Command(). BuiltTool("merge_csv"). Flag("--zip_input"). Flag("--key_field signature"). FlagWithOutput("--output=", indexCSV). Inputs(classesJars) rule.Build("merged-hiddenapi-index", "Merged Hidden API index") h.indexCSVPath = indexCSV // Save the unencoded dex jar so it can be used when generating the // hiddenAPISingletonPathsStruct.stubFlags file. h.bootDexJarPath = dexJar } var hiddenAPIEncodeDexRule = pctx.AndroidStaticRule("hiddenAPIEncodeDex", blueprint.RuleParams{ Command: `rm -rf $tmpDir && mkdir -p $tmpDir && mkdir $tmpDir/dex-input && mkdir $tmpDir/dex-output && unzip -qoDD $in 'classes*.dex' -d $tmpDir/dex-input && for INPUT_DEX in $$(find $tmpDir/dex-input -maxdepth 1 -name 'classes*.dex' | sort); do echo "--input-dex=$${INPUT_DEX}"; echo "--output-dex=$tmpDir/dex-output/$$(basename $${INPUT_DEX})"; done | xargs ${config.HiddenAPI} encode --api-flags=$flagsCsv $hiddenapiFlags && ${config.SoongZipCmd} $soongZipFlags -o $tmpDir/dex.jar -C $tmpDir/dex-output -f "$tmpDir/dex-output/classes*.dex" && ${config.MergeZipsCmd} -D -zipToNotStrip $tmpDir/dex.jar -stripFile "classes*.dex" -stripFile "**/*.uau" $out $tmpDir/dex.jar $in`, CommandDeps: []string{ "${config.HiddenAPI}", "${config.SoongZipCmd}", "${config.MergeZipsCmd}", }, }, "flagsCsv", "hiddenapiFlags", "tmpDir", "soongZipFlags") func hiddenAPIEncodeDex(ctx android.ModuleContext, output android.WritablePath, dexInput android.Path, uncompressDex bool) { flagsCSV := hiddenAPISingletonPaths(ctx).flags // The encode dex rule requires unzipping and rezipping the classes.dex files, ensure that if it was uncompressed // in the input it stays uncompressed in the output. soongZipFlags := "" hiddenapiFlags := "" tmpOutput := output tmpDir := android.PathForModuleOut(ctx, "hiddenapi", "dex") if uncompressDex { soongZipFlags = "-L 0" tmpOutput = android.PathForModuleOut(ctx, "hiddenapi", "unaligned", "unaligned.jar") tmpDir = android.PathForModuleOut(ctx, "hiddenapi", "unaligned") } enforceHiddenApiFlagsToAllMembers := true // If frameworks/base doesn't exist we must be building with the 'master-art' manifest. // Disable assertion that all methods/fields have hidden API flags assigned. if !ctx.Config().FrameworksBaseDirExists(ctx) { enforceHiddenApiFlagsToAllMembers = false } // b/149353192: when a module is instrumented, jacoco adds synthetic members // $jacocoData and $jacocoInit. Since they don't exist when building the hidden API flags, // don't complain when we don't find hidden API flags for the synthetic members. if j, ok := ctx.Module().(interface { shouldInstrument(android.BaseModuleContext) bool }); ok && j.shouldInstrument(ctx) { enforceHiddenApiFlagsToAllMembers = false } if !enforceHiddenApiFlagsToAllMembers { hiddenapiFlags = "--no-force-assign-all" } ctx.Build(pctx, android.BuildParams{ Rule: hiddenAPIEncodeDexRule, Description: "hiddenapi encode dex", Input: dexInput, Output: tmpOutput, Implicit: flagsCSV, Args: map[string]string{ "flagsCsv": flagsCSV.String(), "tmpDir": tmpDir.String(), "soongZipFlags": soongZipFlags, "hiddenapiFlags": hiddenapiFlags, }, }) if uncompressDex { TransformZipAlign(ctx, output, tmpOutput) } } type hiddenApiAnnotationsDependencyTag struct { blueprint.BaseDependencyTag } // Tag used to mark dependencies on java_library instances that contains Java source files whose // sole purpose is to provide additional hiddenapi annotations. var hiddenApiAnnotationsTag hiddenApiAnnotationsDependencyTag // Mark this tag so dependencies that use it are excluded from APEX contents. func (t hiddenApiAnnotationsDependencyTag) ExcludeFromApexContents() {} var _ android.ExcludeFromApexContentsTag = hiddenApiAnnotationsTag