diff --git a/Android.bp b/Android.bp index fff17ef64..fe3a39097 100644 --- a/Android.bp +++ b/Android.bp @@ -55,6 +55,7 @@ bootstrap_go_package { "android/mutator.go", "android/namespace.go", "android/neverallow.go", + "android/notices.go", "android/onceper.go", "android/override_module.go", "android/package_ctx.go", diff --git a/android/module.go b/android/module.go index 87e2ca7d9..66822f080 100644 --- a/android/module.go +++ b/android/module.go @@ -901,14 +901,6 @@ func (m *ModuleBase) GenerateBuildActions(blueprintCtx blueprint.ModuleContext) } if m.Enabled() { - m.module.GenerateAndroidBuildActions(ctx) - if ctx.Failed() { - return - } - - m.installFiles = append(m.installFiles, ctx.installFiles...) - m.checkbuildFiles = append(m.checkbuildFiles, ctx.checkbuildFiles...) - notice := proptools.StringDefault(m.commonProperties.Notice, "NOTICE") if module := SrcIsModule(notice); module != "" { m.noticeFile = ctx.ExpandOptionalSource(¬ice, "notice") @@ -916,6 +908,14 @@ func (m *ModuleBase) GenerateBuildActions(blueprintCtx blueprint.ModuleContext) noticePath := filepath.Join(ctx.ModuleDir(), notice) m.noticeFile = ExistentPathForSource(ctx, noticePath) } + + m.module.GenerateAndroidBuildActions(ctx) + if ctx.Failed() { + return + } + + m.installFiles = append(m.installFiles, ctx.installFiles...) + m.checkbuildFiles = append(m.checkbuildFiles, ctx.checkbuildFiles...) } else if ctx.Config().AllowMissingDependencies() { // If the module is not enabled it will not create any build rules, nothing will call // ctx.GetMissingDependencies(), and blueprint will consider the missing dependencies to be unhandled diff --git a/android/notices.go b/android/notices.go new file mode 100644 index 000000000..dbb88fc1a --- /dev/null +++ b/android/notices.go @@ -0,0 +1,87 @@ +// 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 android + +import ( + "path/filepath" + + "github.com/google/blueprint" +) + +func init() { + pctx.SourcePathVariable("merge_notices", "build/soong/scripts/mergenotice.py") + pctx.SourcePathVariable("generate_notice", "build/make/tools/generate-notice-files.py") + + pctx.HostBinToolVariable("minigzip", "minigzip") +} + +var ( + mergeNoticesRule = pctx.AndroidStaticRule("mergeNoticesRule", blueprint.RuleParams{ + Command: `${merge_notices} --output $out $in`, + CommandDeps: []string{"${merge_notices}"}, + Description: "merge notice files into $out", + }) + + generateNoticeRule = pctx.AndroidStaticRule("generateNoticeRule", blueprint.RuleParams{ + Command: `rm -rf $tmpDir $$(dirname $out) && ` + + `mkdir -p $tmpDir $$(dirname $out) && ` + + `${generate_notice} --text-output $tmpDir/NOTICE.txt --html-output $tmpDir/NOTICE.html -t "$title" -s $inputDir && ` + + `${minigzip} -c $tmpDir/NOTICE.html > $out`, + CommandDeps: []string{"${generate_notice}", "${minigzip}"}, + Description: "produce notice file $out", + }, "tmpDir", "title", "inputDir") +) + +func MergeNotices(ctx ModuleContext, mergedNotice WritablePath, noticePaths []Path) { + ctx.Build(pctx, BuildParams{ + Rule: mergeNoticesRule, + Description: "merge notices", + Inputs: noticePaths, + Output: mergedNotice, + }) +} + +func BuildNoticeOutput(ctx ModuleContext, installPath OutputPath, installFilename string, + noticePaths []Path) ModuleOutPath { + // Merge all NOTICE files into one. + // TODO(jungjw): We should just produce a well-formatted NOTICE.html file in a single pass. + // + // generate-notice-files.py, which processes the merged NOTICE file, has somewhat strict rules + // about input NOTICE file paths. + // 1. Their relative paths to the src root become their NOTICE index titles. We want to use + // on-device paths as titles, and so output the merged NOTICE file the corresponding location. + // 2. They must end with .txt extension. Otherwise, they're ignored. + noticeRelPath := InstallPathToOnDevicePath(ctx, installPath.Join(ctx, installFilename+".txt")) + mergedNotice := PathForModuleOut(ctx, filepath.Join("NOTICE_FILES/src", noticeRelPath)) + MergeNotices(ctx, mergedNotice, noticePaths) + + // Transform the merged NOTICE file into a gzipped HTML file. + noticeOutput := PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz") + tmpDir := PathForModuleOut(ctx, "NOTICE_tmp") + title := "Notices for " + ctx.ModuleName() + ctx.Build(pctx, BuildParams{ + Rule: generateNoticeRule, + Description: "generate notice output", + Input: mergedNotice, + Output: noticeOutput, + Args: map[string]string{ + "tmpDir": tmpDir.String(), + "title": title, + "inputDir": PathForModuleOut(ctx, "NOTICE_FILES/src").String(), + }, + }) + + return noticeOutput +} diff --git a/apex/apex.go b/apex/apex.go index ac0363b82..70a0bc5cf 100644 --- a/apex/apex.go +++ b/apex/apex.go @@ -90,12 +90,6 @@ var ( CommandDeps: []string{"${zip2zip}"}, Description: "app bundle", }, "abi") - - apexMergeNoticeRule = pctx.StaticRule("apexMergeNoticeRule", blueprint.RuleParams{ - Command: `${mergenotice} --output $out $inputs`, - CommandDeps: []string{"${mergenotice}"}, - Description: "merge notice files into $out", - }, "inputs") ) var imageApexSuffix = ".apex" @@ -144,8 +138,6 @@ func init() { pctx.HostBinToolVariable("zip2zip", "zip2zip") pctx.HostBinToolVariable("zipalign", "zipalign") - pctx.SourcePathVariable("mergenotice", "build/soong/scripts/mergenotice.py") - android.RegisterModuleType("apex", apexBundleFactory) android.RegisterModuleType("apex_test", testApexBundleFactory) android.RegisterModuleType("apex_defaults", defaultsFactory) @@ -853,32 +845,22 @@ func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) { func (a *apexBundle) buildNoticeFile(ctx android.ModuleContext) { noticeFiles := []android.Path{} - noticeFilesString := []string{} for _, f := range a.filesInfo { if f.module != nil { notice := f.module.NoticeFile() if notice.Valid() { noticeFiles = append(noticeFiles, notice.Path()) - noticeFilesString = append(noticeFilesString, notice.Path().String()) } } } // append the notice file specified in the apex module itself if a.NoticeFile().Valid() { noticeFiles = append(noticeFiles, a.NoticeFile().Path()) - noticeFilesString = append(noticeFilesString, a.NoticeFile().Path().String()) } if len(noticeFiles) > 0 { a.mergedNoticeFile = android.PathForModuleOut(ctx, "NOTICE") - ctx.Build(pctx, android.BuildParams{ - Rule: apexMergeNoticeRule, - Inputs: noticeFiles, - Output: a.mergedNoticeFile, - Args: map[string]string{ - "inputs": strings.Join(noticeFilesString, " "), - }, - }) + android.MergeNotices(ctx, a.mergedNoticeFile, noticeFiles) } } diff --git a/apex/apex_test.go b/apex/apex_test.go index f71abd77a..f73be20a6 100644 --- a/apex/apex_test.go +++ b/apex/apex_test.go @@ -347,10 +347,10 @@ func TestBasicApex(t *testing.T) { t.Errorf("Could not find all expected symlinks! foo: %t, foo_link_64: %t. Command was %s", found_foo, found_foo_link_64, copyCmds) } - apexMergeNoticeRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexMergeNoticeRule") - noticeInputs := strings.Split(apexMergeNoticeRule.Args["inputs"], " ") - if len(noticeInputs) != 3 { - t.Errorf("number of input notice files: expected = 3, actual = %d", len(noticeInputs)) + mergeNoticesRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("mergeNoticesRule") + noticeInputs := mergeNoticesRule.Inputs.Strings() + if len(noticeInputs) != 4 { + t.Errorf("number of input notice files: expected = 4, actual = %q", len(noticeInputs)) } ensureListContains(t, noticeInputs, "NOTICE") ensureListContains(t, noticeInputs, "custom_notice") diff --git a/java/aar.go b/java/aar.go index 460fbfa63..c9306de4d 100644 --- a/java/aar.go +++ b/java/aar.go @@ -17,6 +17,7 @@ package java import ( "android/soong/android" "fmt" + "path/filepath" "strings" "github.com/google/blueprint" @@ -80,6 +81,7 @@ type aapt struct { rTxt android.Path extraAaptPackagesFile android.Path mergedManifestFile android.Path + noticeFile android.OptionalPath isLibrary bool useEmbeddedNativeLibs bool useEmbeddedDex bool @@ -151,10 +153,16 @@ func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkContext sdkContext, assetFiles = append(assetFiles, androidResourceGlob(ctx, dir)...) } + assetDirStrings := assetDirs.Strings() + if a.noticeFile.Valid() { + assetDirStrings = append(assetDirStrings, filepath.Dir(a.noticeFile.Path().String())) + assetFiles = append(assetFiles, a.noticeFile.Path()) + } + linkFlags = append(linkFlags, "--manifest "+manifestPath.String()) linkDeps = append(linkDeps, manifestPath) - linkFlags = append(linkFlags, android.JoinWithPrefix(assetDirs.Strings(), "-A ")) + linkFlags = append(linkFlags, android.JoinWithPrefix(assetDirStrings, "-A ")) linkDeps = append(linkDeps, assetFiles...) // SDK version flags diff --git a/java/app.go b/java/app.go index cab97de45..42f8bada3 100644 --- a/java/app.go +++ b/java/app.go @@ -19,6 +19,7 @@ package java import ( "path/filepath" "reflect" + "sort" "strings" "github.com/google/blueprint" @@ -103,6 +104,10 @@ type appProperties struct { // Use_embedded_native_libs still selects whether they are stored uncompressed and aligned or compressed. // True for android_test* modules. AlwaysPackageNativeLibs bool `blueprint:"mutated"` + + // If set, find and merge all NOTICE files that this module and its dependencies have and store + // it in the APK as an asset. + Embed_notices *bool } // android_app properties that can be overridden by override_android_app @@ -351,6 +356,54 @@ func (a *AndroidApp) jniBuildActions(jniLibs []jniLib, ctx android.ModuleContext return jniJarFile } +func (a *AndroidApp) noticeBuildActions(ctx android.ModuleContext, installDir android.OutputPath) android.OptionalPath { + if !Bool(a.appProperties.Embed_notices) && !ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") { + return android.OptionalPath{} + } + + // Collect NOTICE files from all dependencies. + seenModules := make(map[android.Module]bool) + noticePathSet := make(map[android.Path]bool) + + ctx.WalkDeps(func(child android.Module, parent android.Module) bool { + // Have we already seen this? + if _, ok := seenModules[child]; ok { + return false + } + seenModules[child] = true + + // Skip host modules. + if child.Target().Os.Class == android.Host || child.Target().Os.Class == android.HostCross { + return false + } + + path := child.(android.Module).NoticeFile() + if path.Valid() { + noticePathSet[path.Path()] = true + } + return true + }) + + // If the app has one, add it too. + if a.NoticeFile().Valid() { + noticePathSet[a.NoticeFile().Path()] = true + } + + if len(noticePathSet) == 0 { + return android.OptionalPath{} + } + var noticePaths []android.Path + for path := range noticePathSet { + noticePaths = append(noticePaths, path) + } + sort.Slice(noticePaths, func(i, j int) bool { + return noticePaths[i].String() < noticePaths[j].String() + }) + noticeFile := android.BuildNoticeOutput(ctx, installDir, a.installApkName+".apk", noticePaths) + + return android.OptionalPathForPath(noticeFile) +} + // Reads and prepends a main cert from the default cert dir if it hasn't been set already, i.e. it // isn't a cert module reference. Also checks and enforces system cert restriction if applicable. func processMainCert(m android.ModuleBase, certPropValue string, certificates []Certificate, ctx android.ModuleContext) []Certificate { @@ -391,6 +444,18 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { // Check if the install APK name needs to be overridden. a.installApkName = ctx.DeviceConfig().OverridePackageNameFor(a.Name()) + var installDir android.OutputPath + if ctx.ModuleName() == "framework-res" { + // framework-res.apk is installed as system/framework/framework-res.apk + installDir = android.PathForModuleInstall(ctx, "framework") + } else if Bool(a.appProperties.Privileged) { + installDir = android.PathForModuleInstall(ctx, "priv-app", a.installApkName) + } else { + installDir = android.PathForModuleInstall(ctx, "app", a.installApkName) + } + + a.aapt.noticeFile = a.noticeBuildActions(ctx, installDir) + // Process all building blocks, from AAPT to certificates. a.aaptBuildActions(ctx) @@ -432,16 +497,6 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { a.bundleFile = bundleFile // Install the app package. - var installDir android.OutputPath - if ctx.ModuleName() == "framework-res" { - // framework-res.apk is installed as system/framework/framework-res.apk - installDir = android.PathForModuleInstall(ctx, "framework") - } else if Bool(a.appProperties.Privileged) { - installDir = android.PathForModuleInstall(ctx, "priv-app", a.installApkName) - } else { - installDir = android.PathForModuleInstall(ctx, "app", a.installApkName) - } - ctx.InstallFile(installDir, a.installApkName+".apk", a.outputFile) for _, split := range a.aapt.splits { ctx.InstallFile(installDir, a.installApkName+"_"+split.suffix+".apk", split.path) diff --git a/java/app_test.go b/java/app_test.go index 459f3770b..2e6d29a57 100644 --- a/java/app_test.go +++ b/java/app_test.go @@ -1396,3 +1396,98 @@ func TestCodelessApp(t *testing.T) { }) } } + +func TestEmbedNotice(t *testing.T) { + ctx := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+` + android_app { + name: "foo", + srcs: ["a.java"], + static_libs: ["javalib"], + jni_libs: ["libjni"], + notice: "APP_NOTICE", + embed_notices: true, + } + + // No embed_notice flag + android_app { + name: "bar", + srcs: ["a.java"], + jni_libs: ["libjni"], + notice: "APP_NOTICE", + } + + // No NOTICE files + android_app { + name: "baz", + srcs: ["a.java"], + embed_notices: true, + } + + cc_library { + name: "libjni", + system_shared_libs: [], + stl: "none", + notice: "LIB_NOTICE", + } + + java_library { + name: "javalib", + srcs: [ + ":gen", + ], + } + + genrule { + name: "gen", + tools: ["gentool"], + out: ["gen.java"], + notice: "GENRULE_NOTICE", + } + + java_binary_host { + name: "gentool", + srcs: ["b.java"], + notice: "TOOL_NOTICE", + } + `) + + // foo has NOTICE files to process, and embed_notices is true. + foo := ctx.ModuleForTests("foo", "android_common") + // verify merge notices rule. + mergeNotices := foo.Rule("mergeNoticesRule") + noticeInputs := mergeNotices.Inputs.Strings() + // TOOL_NOTICE should be excluded as it's a host module. + if len(mergeNotices.Inputs) != 3 { + t.Errorf("number of input notice files: expected = 3, actual = %q", noticeInputs) + } + if !inList("APP_NOTICE", noticeInputs) { + t.Errorf("APP_NOTICE is missing from notice files, %q", noticeInputs) + } + if !inList("LIB_NOTICE", noticeInputs) { + t.Errorf("LIB_NOTICE is missing from notice files, %q", noticeInputs) + } + if !inList("GENRULE_NOTICE", noticeInputs) { + t.Errorf("GENRULE_NOTICE is missing from notice files, %q", noticeInputs) + } + // aapt2 flags should include -A so that its contents are put in the APK's /assets. + res := foo.Output("package-res.apk") + aapt2Flags := res.Args["flags"] + e := "-A " + buildDir + "/.intermediates/foo/android_common/NOTICE" + if !strings.Contains(aapt2Flags, e) { + t.Errorf("asset dir flag for NOTICE, %q is missing in aapt2 link flags, %q", e, aapt2Flags) + } + + // bar has NOTICE files to process, but embed_notices is not set. + bar := ctx.ModuleForTests("bar", "android_common") + mergeNotices = bar.MaybeRule("mergeNoticesRule") + if mergeNotices.Rule != nil { + t.Errorf("mergeNotices shouldn't have run for bar") + } + + // baz's embed_notice is true, but it doesn't have any NOTICE files. + baz := ctx.ModuleForTests("baz", "android_common") + mergeNotices = baz.MaybeRule("mergeNoticesRule") + if mergeNotices.Rule != nil { + t.Errorf("mergeNotices shouldn't have run for baz") + } +} diff --git a/java/java_test.go b/java/java_test.go index e56a48983..a4b20ceaf 100644 --- a/java/java_test.go +++ b/java/java_test.go @@ -121,6 +121,10 @@ func testContext(config android.Config, bp string, "b.kt": nil, "a.jar": nil, "b.jar": nil, + "APP_NOTICE": nil, + "GENRULE_NOTICE": nil, + "LIB_NOTICE": nil, + "TOOL_NOTICE": nil, "java-res/a/a": nil, "java-res/b/b": nil, "java-res2/a": nil,