diff --git a/Android.bp b/Android.bp index b4a9d30d3..97f786edc 100644 --- a/Android.bp +++ b/Android.bp @@ -243,6 +243,7 @@ bootstrap_go_package { "java/droiddoc.go", "java/gen.go", "java/genrule.go", + "java/hiddenapi.go", "java/jacoco.go", "java/java.go", "java/jdeps.go", diff --git a/android/config.go b/android/config.go index 09d9cbc4e..cf4d6e550 100644 --- a/android/config.go +++ b/android/config.go @@ -983,6 +983,18 @@ func (c *config) EnforceSystemCertificateWhitelist() []string { return c.productVariables.EnforceSystemCertificateWhitelist } +func (c *config) HiddenAPIPublicList() string { + return String(c.productVariables.HiddenAPIPublicList) +} + +func (c *config) HiddenAPIFlags() string { + return String(c.productVariables.HiddenAPIFlags) +} + +func (c *config) HiddenAPIExtraAppUsageJars() []string { + return c.productVariables.HiddenAPIExtraAppUsageJars +} + func stringSlice(s *[]string) []string { if s != nil { return *s diff --git a/android/variable.go b/android/variable.go index e19d8581d..46b115502 100644 --- a/android/variable.go +++ b/android/variable.go @@ -271,6 +271,11 @@ type productVariables struct { EnforceSystemCertificate *bool `json:",omitempty"` EnforceSystemCertificateWhitelist []string `json:",omitempty"` + + // TODO(ccross): move these to a Singleton in Soong + HiddenAPIPublicList *string `json:",omitempty"` + HiddenAPIFlags *string `json:",omitempty"` + HiddenAPIExtraAppUsageJars []string `json:",omitempty"` } func boolPtr(v bool) *bool { diff --git a/java/config/config.go b/java/config/config.go index 92cfb848a..5c838a555 100644 --- a/java/config/config.go +++ b/java/config/config.go @@ -154,4 +154,7 @@ func init() { pctx.SourcePathsVariable("ManifestMergerClasspath", ":", ManifestMergerClasspath...) pctx.HostBinToolVariable("ZipAlign", "zipalign") + + pctx.HostBinToolVariable("Class2Greylist", "class2greylist") + pctx.HostBinToolVariable("HiddenAPI", "hiddenapi") } diff --git a/java/config/makevars.go b/java/config/makevars.go index 01adaa7d1..156ee2638 100644 --- a/java/config/makevars.go +++ b/java/config/makevars.go @@ -78,4 +78,7 @@ func makeVarsProvider(ctx android.MakeVarsContext) { ctx.Strict("ANDROID_MANIFEST_MERGER_DEPS", "${ManifestMergerJars}") ctx.Strict("ANDROID_MANIFEST_MERGER", "${JavaCmd} -classpath ${ManifestMergerClasspath} com.android.manifmerger.Merger") + + ctx.Strict("CLASS2GREYLIST", "${Class2Greylist}") + ctx.Strict("HIDDENAPI", "${HiddenAPI}") } diff --git a/java/hiddenapi.go b/java/hiddenapi.go new file mode 100644 index 000000000..7378b6401 --- /dev/null +++ b/java/hiddenapi.go @@ -0,0 +1,150 @@ +// 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 ( + "sort" + "strings" + "sync" + + "github.com/google/blueprint" + + "android/soong/android" +) + +var hiddenAPIGenerateCSVRule = pctx.AndroidStaticRule("hiddenAPIGenerateCSV", blueprint.RuleParams{ + Command: "${config.Class2Greylist} --public-api-list ${publicAPIList} $in $outFlag $out", + CommandDeps: []string{"${config.Class2Greylist}"}, +}, "outFlag", "publicAPIList") + +func hiddenAPIGenerateCSV(ctx android.ModuleContext, classesJar android.Path) { + flagsCSV := android.PathForModuleOut(ctx, "hiddenapi", "flags.csv") + metadataCSV := android.PathForModuleOut(ctx, "hiddenapi", "metadata.csv") + publicList := &bootImagePath{ctx.Config().HiddenAPIPublicList()} + + ctx.Build(pctx, android.BuildParams{ + Rule: hiddenAPIGenerateCSVRule, + Description: "hiddenapi flags", + Input: classesJar, + Output: flagsCSV, + Implicit: publicList, + Args: map[string]string{ + "outFlag": "--write-flags-csv", + "publicAPIList": publicList.String(), + }, + }) + + ctx.Build(pctx, android.BuildParams{ + Rule: hiddenAPIGenerateCSVRule, + Description: "hiddenapi metadata", + Input: classesJar, + Output: metadataCSV, + Implicit: publicList, + Args: map[string]string{ + "outFlag": "--write-metadata-csv", + "publicAPIList": publicList.String(), + }, + }) + + hiddenAPISaveCSVOutputs(ctx, flagsCSV, metadataCSV) +} + +var hiddenAPIEncodeDexRule = pctx.AndroidStaticRule("hiddenAPIEncodeDex", blueprint.RuleParams{ + Command: `rm -rf $tmpDir && mkdir -p $tmpDir && mkdir $tmpDir/dex-input && mkdir $tmpDir/dex-output && ` + + `unzip -o -q $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=$flags && ` + + `${config.SoongZipCmd} -o $tmpDir/dex.jar -C $tmpDir/dex-output -f "$tmpDir/dex-output/classes*.dex" && ` + + `${config.MergeZipsCmd} -D -zipToNotStrip $tmpDir/dex.jar -stripFile "classes*.dex" $out $tmpDir/dex.jar $in`, + CommandDeps: []string{ + "${config.HiddenAPI}", + "${config.SoongZipCmd}", + "${config.MergeZipsCmd}", + }, +}, "flags", "tmpDir") + +func hiddenAPIEncodeDex(ctx android.ModuleContext, output android.WritablePath, dexInput android.WritablePath) { + if ctx.Config().IsEnvTrue("UNSAFE_DISABLE_HIDDENAPI_FLAGS") { + output = dexInput + return + } + + flags := &bootImagePath{ctx.Config().HiddenAPIFlags()} + + ctx.Build(pctx, android.BuildParams{ + Rule: hiddenAPIEncodeDexRule, + Description: "hiddenapi encode dex", + Input: dexInput, + Output: output, + Implicit: flags, + Args: map[string]string{ + "flags": flags.String(), + "tmpDir": android.PathForModuleOut(ctx, "hiddenapi", "dex").String(), + }, + }) + + hiddenAPISaveDexInputs(ctx, dexInput) +} + +const hiddenAPIOutputsKey = "hiddenAPIOutputsKey" + +var hiddenAPIOutputsLock sync.Mutex + +func hiddenAPIGetOutputs(config android.Config) (*android.Paths, *android.Paths, *android.Paths) { + type threePathsPtrs [3]*android.Paths + s := config.Once(hiddenAPIOutputsKey, func() interface{} { + return threePathsPtrs{new(android.Paths), new(android.Paths), new(android.Paths)} + }).(threePathsPtrs) + return s[0], s[1], s[2] +} + +func hiddenAPISaveCSVOutputs(ctx android.ModuleContext, flagsCSV, metadataCSV android.Path) { + flagsCSVList, metadataCSVList, _ := hiddenAPIGetOutputs(ctx.Config()) + + hiddenAPIOutputsLock.Lock() + defer hiddenAPIOutputsLock.Unlock() + + *flagsCSVList = append(*flagsCSVList, flagsCSV) + *metadataCSVList = append(*metadataCSVList, metadataCSV) +} + +func hiddenAPISaveDexInputs(ctx android.ModuleContext, dexInput android.Path) { + _, _, dexInputList := hiddenAPIGetOutputs(ctx.Config()) + + hiddenAPIOutputsLock.Lock() + defer hiddenAPIOutputsLock.Unlock() + + *dexInputList = append(*dexInputList, dexInput) +} + +func init() { + android.RegisterMakeVarsProvider(pctx, hiddenAPIMakeVars) +} + +func hiddenAPIMakeVars(ctx android.MakeVarsContext) { + flagsCSVList, metadataCSVList, dexInputList := hiddenAPIGetOutputs(ctx.Config()) + + export := func(name string, paths *android.Paths) { + s := paths.Strings() + sort.Strings(s) + ctx.Strict(name, strings.Join(s, " ")) + } + + export("SOONG_HIDDENAPI_FLAGS", flagsCSVList) + export("SOONG_HIDDENAPI_GREYLIST_METADATA", metadataCSVList) + export("SOONG_HIDDENAPI_DEX_INPUTS", dexInputList) +} diff --git a/java/java.go b/java/java.go index 2ac5a5b46..49095ca36 100644 --- a/java/java.go +++ b/java/java.go @@ -1172,12 +1172,25 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path j.implementationAndResourcesJar = implementationAndResourcesJar if ctx.Device() && (Bool(j.properties.Installable) || Bool(j.deviceProperties.Compile_dex)) { + // Dex compilation var dexOutputFile android.ModuleOutPath dexOutputFile = j.compileDex(ctx, flags, outputFile, jarName) if ctx.Failed() { return } + // Hidden API CSV generation and dex encoding + isBootJar := inList(ctx.ModuleName(), ctx.Config().BootJars()) + if isBootJar || inList(ctx.ModuleName(), ctx.Config().HiddenAPIExtraAppUsageJars()) { + // Derive the greylist from classes jar. + hiddenAPIGenerateCSV(ctx, j.implementationJarFile) + } + if isBootJar { + hiddenAPIJar := android.PathForModuleOut(ctx, "hiddenapi", jarName) + hiddenAPIEncodeDex(ctx, hiddenAPIJar, dexOutputFile) + dexOutputFile = hiddenAPIJar + } + // merge dex jar with resources if necessary if j.resourceJar != nil { jars := android.Paths{dexOutputFile, j.resourceJar} @@ -1189,6 +1202,7 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path j.dexJarFile = dexOutputFile + // Dexpreopting j.dexpreopter.isInstallable = Bool(j.properties.Installable) j.dexpreopter.uncompressedDex = j.deviceProperties.UncompressDex dexOutputFile = j.dexpreopt(ctx, dexOutputFile)