Add gen_notice module.
Refactor notices to support notices for multiple modules.
Enforce visibility and handle missing dependencies.
Bug: 213388645
Change-Id: Id6a81987f087419ad37d0cce57a71e8a7c4cd6e0
diff --git a/android/Android.bp b/android/Android.bp
index d583703..8eb55d2 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -49,6 +49,7 @@
"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 0000000..f514975
--- /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 bd14b26..81c557e 100644
--- a/android/licenses.go
+++ b/android/licenses.go
@@ -303,6 +303,7 @@
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 2a4c17c..b16dc58 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 7ff96c9..ec7f63e 100644
--- a/android/singleton.go
+++ b/android/singleton.go
@@ -29,6 +29,10 @@
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) 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 5d1be6b..b209599 100644
--- a/android/visibility.go
+++ b/android/visibility.go
@@ -234,7 +234,7 @@
// 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 @@
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 @@
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 f050a2e..c61afcb 100644
--- a/android_sdk/sdk_repo_host.go
+++ b/android_sdk/sdk_repo_host.go
@@ -107,6 +107,7 @@
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 @@
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 @@
}
// 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 abbf8ad..f959b7a 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -618,7 +618,7 @@
// 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 5f14cef..cf57dc2 100755
--- a/java/app.go
+++ b/java/app.go
@@ -582,18 +582,6 @@
}
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 @@
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")