From eef4c1c5630269b255672827aeadde6e5ba88a69 Mon Sep 17 00:00:00 2001 From: Bob Badour Date: Mon, 16 May 2022 12:20:04 -0700 Subject: [PATCH] Add gen_notice module. Refactor notices to support notices for multiple modules. Enforce visibility and handle missing dependencies. Bug: 213388645 Change-Id: Id6a81987f087419ad37d0cce57a71e8a7c4cd6e0 --- android/Android.bp | 1 + android/gen_notice.go | 207 +++++++++++++++++++++++++++++++++++ android/licenses.go | 1 + android/notices.go | 93 ++++++++++++---- android/singleton.go | 30 +++++ android/visibility.go | 8 +- android_sdk/sdk_repo_host.go | 4 +- apex/builder.go | 2 +- java/app.go | 24 ++-- 9 files changed, 331 insertions(+), 39 deletions(-) create mode 100644 android/gen_notice.go diff --git a/android/Android.bp b/android/Android.bp index d58370391..8eb55d278 100644 --- a/android/Android.bp +++ b/android/Android.bp @@ -49,6 +49,7 @@ bootstrap_go_package { "expand.go", "filegroup.go", "fixture.go", + "gen_notice.go", "hooks.go", "image.go", "license.go", diff --git a/android/gen_notice.go b/android/gen_notice.go new file mode 100644 index 000000000..f5149756e --- /dev/null +++ b/android/gen_notice.go @@ -0,0 +1,207 @@ +// Copyright 2020 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 ( + "fmt" + "strings" + + "github.com/google/blueprint/proptools" +) + +func init() { + RegisterGenNoticeBuildComponents(InitRegistrationContext) +} + +// Register the gen_notice module type. +func RegisterGenNoticeBuildComponents(ctx RegistrationContext) { + ctx.RegisterSingletonType("gen_notice_build_rules", GenNoticeBuildRulesFactory) + ctx.RegisterModuleType("gen_notice", GenNoticeFactory) +} + +type genNoticeBuildRules struct{} + +func (s *genNoticeBuildRules) GenerateBuildActions(ctx SingletonContext) { + ctx.VisitAllModules(func(m Module) { + gm, ok := m.(*genNoticeModule) + if !ok { + return + } + if len(gm.missing) > 0 { + missingReferencesRule(ctx, gm) + return + } + out := BuildNoticeTextOutputFromLicenseMetadata + if proptools.Bool(gm.properties.Xml) { + out = BuildNoticeXmlOutputFromLicenseMetadata + } else if proptools.Bool(gm.properties.Html) { + out = BuildNoticeHtmlOutputFromLicenseMetadata + } + defaultName := "" + if len(gm.properties.For) > 0 { + defaultName = gm.properties.For[0] + } + + modules := make([]Module, 0) + for _, name := range gm.properties.For { + mods := ctx.ModuleVariantsFromName(gm, name) + for _, mod := range mods { + if mod == nil { + continue + } + modules = append(modules, mod) + } + } + if ctx.Failed() { + return + } + out(ctx, gm.output, proptools.StringDefault(gm.properties.ArtifactName, defaultName), "", modules...) + }) +} + +func GenNoticeBuildRulesFactory() Singleton { + return &genNoticeBuildRules{} +} + +type genNoticeProperties struct { + // For specifies the modules for which to generate a notice file. + For []string + // ArtifactName specifies the internal name to use for the notice file. + // It appears in the "used by:" list for targets whose entire name is stripped by --strip_prefix. + ArtifactName *string + // Stem specifies the base name of the output file. + Stem *string `android:"arch_variant"` + // Html indicates an html-format file is needed. The default is text. Can be Html or Xml but not both. + Html *bool + // Xml indicates an xml-format file is needed. The default is text. Can be Html or Xml but not both. + Xml *bool + // Gzipped indicates the output file must be compressed with gzip. Will append .gz to suffix if not there. + Gzipped *bool + // Suffix specifies the file extension to use. Defaults to .html for html, .xml for xml, or no extension for text. + Suffix *string + // Visibility specifies where this license can be used + Visibility []string +} + +type genNoticeModule struct { + ModuleBase + DefaultableModuleBase + + properties genNoticeProperties + + output OutputPath + missing []string +} + +func (m *genNoticeModule) DepsMutator(ctx BottomUpMutatorContext) { + if proptools.Bool(m.properties.Html) && proptools.Bool(m.properties.Xml) { + ctx.ModuleErrorf("can be html or xml but not both") + } + if !ctx.Config().AllowMissingDependencies() { + var missing []string + // Verify the modules for which to generate notices exist. + for _, otherMod := range m.properties.For { + if !ctx.OtherModuleExists(otherMod) { + missing = append(missing, otherMod) + } + } + if len(missing) == 1 { + ctx.PropertyErrorf("for", "no %q module exists", missing[0]) + } else if len(missing) > 1 { + ctx.PropertyErrorf("for", "modules \"%s\" do not exist", strings.Join(missing, "\", \"")) + } + } +} + +func (m *genNoticeModule) getStem() string { + stem := m.base().BaseModuleName() + if m.properties.Stem != nil { + stem = proptools.String(m.properties.Stem) + } + return stem +} + +func (m *genNoticeModule) getSuffix() string { + suffix := "" + if m.properties.Suffix == nil { + if proptools.Bool(m.properties.Html) { + suffix = ".html" + } else if proptools.Bool(m.properties.Xml) { + suffix = ".xml" + } + } else { + suffix = proptools.String(m.properties.Suffix) + } + if proptools.Bool(m.properties.Gzipped) && !strings.HasSuffix(suffix, ".gz") { + suffix += ".gz" + } + return suffix +} + +func (m *genNoticeModule) GenerateAndroidBuildActions(ctx ModuleContext) { + if ctx.Config().AllowMissingDependencies() { + // Verify the modules for which to generate notices exist. + for _, otherMod := range m.properties.For { + if !ctx.OtherModuleExists(otherMod) { + m.missing = append(m.missing, otherMod) + } + } + m.missing = append(m.missing, ctx.GetMissingDependencies()...) + m.missing = FirstUniqueStrings(m.missing) + } + out := m.getStem() + m.getSuffix() + m.output = PathForModuleOut(ctx, out).OutputPath +} + +func GenNoticeFactory() Module { + module := &genNoticeModule{} + + base := module.base() + module.AddProperties(&base.nameProperties, &module.properties) + + // The visibility property needs to be checked and parsed by the visibility module. + setPrimaryVisibilityProperty(module, "visibility", &module.properties.Visibility) + + initAndroidModuleBase(module) + InitDefaultableModule(module) + + return module +} + +var _ OutputFileProducer = (*genNoticeModule)(nil) + +// Implements OutputFileProducer +func (m *genNoticeModule) OutputFiles(tag string) (Paths, error) { + if tag == "" { + return Paths{m.output}, nil + } + return nil, fmt.Errorf("unrecognized tag %q", tag) +} + +// missingReferencesRule emits an ErrorRule for missing module references. +func missingReferencesRule(ctx BuilderContext, m *genNoticeModule) { + if len(m.missing) < 1 { + panic(fmt.Errorf("missing references rule requested with no missing references")) + } + + ctx.Build(pctx, BuildParams{ + Rule: ErrorRule, + Output: m.output, + Description: "notice for " + proptools.StringDefault(m.properties.ArtifactName, "container"), + Args: map[string]string{ + "error": m.Name() + " references missing module(s): " + strings.Join(m.missing, ", "), + }, + }) +} diff --git a/android/licenses.go b/android/licenses.go index bd14b26ca..81c557e72 100644 --- a/android/licenses.go +++ b/android/licenses.go @@ -303,6 +303,7 @@ func exemptFromRequiredApplicableLicensesProperty(module Module) bool { switch reflect.TypeOf(module).String() { case "*android.licenseModule": // is a license, doesn't need one case "*android.licenseKindModule": // is a license, doesn't need one + case "*android.genNoticeModule": // contains license texts as data case "*android.NamespaceModule": // just partitions things, doesn't add anything case "*android.soongConfigModuleTypeModule": // creates aliases for modules with licenses case "*android.soongConfigModuleTypeImport": // creates aliases for modules with licenses diff --git a/android/notices.go b/android/notices.go index 2a4c17cd8..b16dc58bd 100644 --- a/android/notices.go +++ b/android/notices.go @@ -15,31 +15,86 @@ package android import ( + "fmt" + "path/filepath" "strings" ) -// BuildNoticeTextOutputFromLicenseMetadata writes out a notice text file based on the module's -// generated license metadata file. -func BuildNoticeTextOutputFromLicenseMetadata(ctx ModuleContext, outputFile WritablePath) { - depsFile := outputFile.ReplaceExtension(ctx, strings.TrimPrefix(outputFile.Ext()+".d", ".")) - rule := NewRuleBuilder(pctx, ctx) - rule.Command(). - BuiltTool("textnotice"). - FlagWithOutput("-o ", outputFile). - FlagWithDepFile("-d ", depsFile). - Input(ctx.Module().base().licenseMetadataFile) - rule.Build("text_notice", "container notice file") +func modulesOutputDirs(ctx BuilderContext, modules ...Module) []string { + dirs := make([]string, 0, len(modules)) + for _, module := range modules { + paths, err := outputFilesForModule(ctx, module, "") + if err != nil { + continue + } + for _, path := range paths { + if path != nil { + dirs = append(dirs, filepath.Dir(path.String())) + } + } + } + return SortedUniqueStrings(dirs) } -// BuildNoticeHtmlOutputFromLicenseMetadata writes out a notice text file based on the module's -// generated license metadata file. -func BuildNoticeHtmlOutputFromLicenseMetadata(ctx ModuleContext, outputFile WritablePath) { +func modulesLicenseMetadata(ctx BuilderContext, modules ...Module) Paths { + result := make(Paths, 0, len(modules)) + for _, module := range modules { + if mf := module.base().licenseMetadataFile; mf != nil { + result = append(result, mf) + } + } + return result +} + +// buildNoticeOutputFromLicenseMetadata writes out a notice file. +func buildNoticeOutputFromLicenseMetadata(ctx BuilderContext, tool, name string, outputFile WritablePath, libraryName, stripPrefix string, modules ...Module) { depsFile := outputFile.ReplaceExtension(ctx, strings.TrimPrefix(outputFile.Ext()+".d", ".")) rule := NewRuleBuilder(pctx, ctx) - rule.Command(). - BuiltTool("htmlnotice"). + if len(modules) == 0 { + if mctx, ok := ctx.(ModuleContext); ok { + modules = []Module{mctx.Module()} + } else { + panic(fmt.Errorf("%s %q needs a module to generate the notice for", name, libraryName)) + } + } + if libraryName == "" { + libraryName = modules[0].Name() + } + cmd := rule.Command(). + BuiltTool(tool). FlagWithOutput("-o ", outputFile). - FlagWithDepFile("-d ", depsFile). - Input(ctx.Module().base().licenseMetadataFile) - rule.Build("html_notice", "container notice file") + FlagWithDepFile("-d ", depsFile) + if stripPrefix != "" { + cmd = cmd.FlagWithArg("--strip_prefix ", stripPrefix) + } + outputs := modulesOutputDirs(ctx, modules...) + if len(outputs) > 0 { + cmd = cmd.FlagForEachArg("--strip_prefix ", outputs) + } + if libraryName != "" { + cmd = cmd.FlagWithArg("--product ", libraryName) + } + cmd = cmd.Inputs(modulesLicenseMetadata(ctx, modules...)) + rule.Build(name, "container notice file") +} + +// BuildNoticeTextOutputFromLicenseMetadata writes out a notice text file based +// on the license metadata files for the input `modules` defaulting to the +// current context module if none given. +func BuildNoticeTextOutputFromLicenseMetadata(ctx BuilderContext, outputFile WritablePath, libraryName, stripPrefix string, modules ...Module) { + buildNoticeOutputFromLicenseMetadata(ctx, "textnotice", "text_notice", outputFile, libraryName, stripPrefix, modules...) +} + +// BuildNoticeHtmlOutputFromLicenseMetadata writes out a notice text file based +// on the license metadata files for the input `modules` defaulting to the +// current context module if none given. +func BuildNoticeHtmlOutputFromLicenseMetadata(ctx BuilderContext, outputFile WritablePath, libraryName, stripPrefix string, modules ...Module) { + buildNoticeOutputFromLicenseMetadata(ctx, "htmlnotice", "html_notice", outputFile, libraryName, stripPrefix, modules...) +} + +// BuildNoticeXmlOutputFromLicenseMetadata writes out a notice text file based +// on the license metadata files for the input `modules` defaulting to the +// current context module if none given. +func BuildNoticeXmlOutputFromLicenseMetadata(ctx BuilderContext, outputFile WritablePath, libraryName, stripPrefix string, modules ...Module) { + buildNoticeOutputFromLicenseMetadata(ctx, "xmlnotice", "xml_notice", outputFile, libraryName, stripPrefix, modules...) } diff --git a/android/singleton.go b/android/singleton.go index 7ff96c9d5..ec7f63eca 100644 --- a/android/singleton.go +++ b/android/singleton.go @@ -29,6 +29,10 @@ type SingletonContext interface { ModuleType(module blueprint.Module) string BlueprintFile(module blueprint.Module) string + // ModuleVariantsFromName returns the list of module variants named `name` in the same namespace as `referer` enforcing visibility rules. + // Allows generating build actions for `referer` based on the metadata for `name` deferred until the singleton context. + ModuleVariantsFromName(referer Module, name string) []Module + // ModuleProvider returns the value, if any, for the provider for a module. If the value for the // provider was not set it returns the zero value of the type of the provider, which means the // return value can always be type-asserted to the type of the provider. The return value should @@ -251,3 +255,29 @@ func (s *singletonContextAdaptor) PrimaryModule(module Module) Module { func (s *singletonContextAdaptor) FinalModule(module Module) Module { return s.SingletonContext.FinalModule(module).(Module) } + +func (s *singletonContextAdaptor) ModuleVariantsFromName(referer Module, name string) []Module { + // get qualified module name for visibility enforcement + qualified := createQualifiedModuleName(s.ModuleName(referer), s.ModuleDir(referer)) + + modules := s.SingletonContext.ModuleVariantsFromName(referer, name) + result := make([]Module, 0, len(modules)) + for _, m := range modules { + if module, ok := m.(Module); ok { + // enforce visibility + depName := s.ModuleName(module) + depDir := s.ModuleDir(module) + depQualified := qualifiedModuleName{depDir, depName} + // Targets are always visible to other targets in their own package. + if depQualified.pkg != qualified.pkg { + rule := effectiveVisibilityRules(s.Config(), depQualified) + if !rule.matches(qualified) { + s.ModuleErrorf(referer, "references %s which is not visible to this module\nYou may need to add %q to its visibility", depQualified, "//"+s.ModuleDir(m)) + continue + } + } + result = append(result, module) + } + } + return result +} diff --git a/android/visibility.go b/android/visibility.go index 5d1be6b47..b20959944 100644 --- a/android/visibility.go +++ b/android/visibility.go @@ -234,7 +234,7 @@ func RegisterVisibilityRuleEnforcer(ctx RegisterMutatorsContext) { // Checks the per-module visibility rule lists before defaults expansion. func visibilityRuleChecker(ctx BottomUpMutatorContext) { - qualified := createQualifiedModuleName(ctx) + qualified := createQualifiedModuleName(ctx.ModuleName(), ctx.ModuleDir()) if m, ok := ctx.Module().(Module); ok { visibilityProperties := m.visibilityProperties() for _, p := range visibilityProperties { @@ -435,7 +435,7 @@ func visibilityRuleEnforcer(ctx TopDownMutatorContext) { return } - qualified := createQualifiedModuleName(ctx) + qualified := createQualifiedModuleName(ctx.ModuleName(), ctx.ModuleDir()) // Visit all the dependencies making sure that this module has access to them all. ctx.VisitDirectDeps(func(dep Module) { @@ -486,9 +486,7 @@ func effectiveVisibilityRules(config Config, qualified qualifiedModuleName) comp return rule } -func createQualifiedModuleName(ctx BaseModuleContext) qualifiedModuleName { - moduleName := ctx.ModuleName() - dir := ctx.ModuleDir() +func createQualifiedModuleName(moduleName, dir string) qualifiedModuleName { qualified := qualifiedModuleName{dir, moduleName} return qualified } diff --git a/android_sdk/sdk_repo_host.go b/android_sdk/sdk_repo_host.go index f050a2e87..c61afcbca 100644 --- a/android_sdk/sdk_repo_host.go +++ b/android_sdk/sdk_repo_host.go @@ -107,6 +107,7 @@ func (s *sdkRepoHost) DepsMutator(ctx android.BottomUpMutatorContext) { func (s *sdkRepoHost) GenerateAndroidBuildActions(ctx android.ModuleContext) { dir := android.PathForModuleOut(ctx, "zip") + outputZipFile := dir.Join(ctx, "output.zip") builder := android.NewRuleBuilder(pctx, ctx). Sbox(dir, android.PathForModuleOut(ctx, "out.sbox.textproto")). SandboxInputs() @@ -123,7 +124,7 @@ func (s *sdkRepoHost) GenerateAndroidBuildActions(ctx android.ModuleContext) { s.CopySpecsToDir(ctx, builder, packageSpecs, dir) noticeFile := android.PathForModuleOut(ctx, "NOTICES.txt") - android.BuildNoticeTextOutputFromLicenseMetadata(ctx, noticeFile) + android.BuildNoticeTextOutputFromLicenseMetadata(ctx, noticeFile, "", outputZipFile.String()) builder.Command().Text("cp"). Input(noticeFile). Text(filepath.Join(dir.String(), "NOTICE.txt")) @@ -209,7 +210,6 @@ func (s *sdkRepoHost) GenerateAndroidBuildActions(ctx android.ModuleContext) { } // Zip up our temporary directory as the sdk-repo - outputZipFile := dir.Join(ctx, "output.zip") builder.Command(). BuiltTool("soong_zip"). FlagWithOutput("-o ", outputZipFile). diff --git a/apex/builder.go b/apex/builder.go index abbf8ad25..f959b7a69 100644 --- a/apex/builder.go +++ b/apex/builder.go @@ -618,7 +618,7 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { // Create a NOTICE file, and embed it as an asset file in the APEX. a.htmlGzNotice = android.PathForModuleOut(ctx, "NOTICE.html.gz") - android.BuildNoticeHtmlOutputFromLicenseMetadata(ctx, a.htmlGzNotice) + android.BuildNoticeHtmlOutputFromLicenseMetadata(ctx, a.htmlGzNotice, "", unsignedOutputFile.String()) noticeAssetPath := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz") builder := android.NewRuleBuilder(pctx, ctx) builder.Command().Text("cp"). diff --git a/java/app.go b/java/app.go index 5f14ceffb..cf57dc2ba 100755 --- a/java/app.go +++ b/java/app.go @@ -582,18 +582,6 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { } a.onDeviceDir = android.InstallPathToOnDevicePath(ctx, a.installDir) - if Bool(a.appProperties.Embed_notices) || ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") { - noticeFile := android.PathForModuleOut(ctx, "NOTICE.html.gz") - android.BuildNoticeHtmlOutputFromLicenseMetadata(ctx, noticeFile) - noticeAssetPath := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz") - builder := android.NewRuleBuilder(pctx, ctx) - builder.Command().Text("cp"). - Input(noticeFile). - Output(noticeAssetPath) - builder.Build("notice_dir", "Building notice dir") - a.aapt.noticeFile = android.OptionalPathForPath(noticeAssetPath) - } - a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx) // Process all building blocks, from AAPT to certificates. @@ -667,6 +655,18 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { a.extraOutputFiles = append(a.extraOutputFiles, v4SignatureFile) } + if Bool(a.appProperties.Embed_notices) || ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") { + noticeFile := android.PathForModuleOut(ctx, "NOTICE.html.gz") + android.BuildNoticeHtmlOutputFromLicenseMetadata(ctx, noticeFile, "", a.outputFile.String()) + noticeAssetPath := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz") + builder := android.NewRuleBuilder(pctx, ctx) + builder.Command().Text("cp"). + Input(noticeFile). + Output(noticeAssetPath) + builder.Build("notice_dir", "Building notice dir") + a.aapt.noticeFile = android.OptionalPathForPath(noticeAssetPath) + } + for _, split := range a.aapt.splits { // Sign the split APKs packageFile := android.PathForModuleOut(ctx, a.installApkName+"_"+split.suffix+".apk")