Merge "Revert "Use kapt stubs for kotlin header jar for javac""
diff --git a/Android.bp b/Android.bp
index 380a388..42a8e5c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -61,6 +61,9 @@
linux_bionic: {
enabled: true,
},
+ linux_musl: {
+ enabled: false,
+ },
linux_glibc: {
enabled: false,
},
@@ -82,6 +85,9 @@
linux_bionic: {
enabled: true,
},
+ linux_musl: {
+ enabled: false,
+ },
linux_glibc: {
enabled: false,
},
diff --git a/android/bazel.go b/android/bazel.go
index fafb68b..af5de12 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -570,12 +570,11 @@
"art-script", // depends on unconverted modules: dalvikvm, dex2oat
"dex2oat-script", // depends on unconverted modules: dex2oat
- "prebuilt_car-ui-androidx-core-common", // b/224773339, genrule dependency creates an .aar, not a .jar
- "prebuilt_art-module-host-exports_okhttp-norepackage@current", // aosp/1999250, needs Jars (arch variant)
- "prebuilt_conscrypt-unbundled", // aosp/1999250, needs Jars (arch variant)
- "prebuilt_conscrypt-module-host-exports_conscrypt-unbundled@current", // aosp/1999250, needs Jars (arch variant)
- "prebuilt_platform-robolectric-4.4-prebuilt", // aosp/1999250, needs .aar support in Jars
- "prebuilt_platform-robolectric-4.5.1-prebuilt", // aosp/1999250, needs .aar support in Jars
+ "prebuilt_car-ui-androidx-core-common", // b/224773339, genrule dependency creates an .aar, not a .jar
+ "prebuilt_platform-robolectric-4.4-prebuilt", // aosp/1999250, needs .aar support in Jars
+ "prebuilt_platform-robolectric-4.5.1-prebuilt", // aosp/1999250, needs .aar support in Jars
+
+ "libtombstoned_client_rust_bridge_code", "libtombstoned_client_wrapper", // rust conversions are not supported
}
// Per-module denylist of cc_library modules to only generate the static
diff --git a/android/neverallow.go b/android/neverallow.go
index 6f9ae58..aa47bca 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -57,6 +57,7 @@
AddNeverAllowRules(createUncompressDexRules()...)
AddNeverAllowRules(createMakefileGoalRules()...)
AddNeverAllowRules(createInitFirstStageRules()...)
+ AddNeverAllowRules(createProhibitFrameworkAccessRules()...)
}
// Add a NeverAllow rule to the set of rules to apply.
@@ -228,6 +229,15 @@
}
}
+func createProhibitFrameworkAccessRules() []Rule {
+ return []Rule{
+ NeverAllow().
+ With("libs", "framework").
+ WithoutMatcher("sdk_version", Regexp("(core_.*|^$)")).
+ Because("framework can't be used when building against SDK"),
+ }
+}
+
func neverallowMutator(ctx BottomUpMutatorContext) {
m, ok := ctx.Module().(Module)
if !ok {
@@ -249,7 +259,7 @@
continue
}
- if !n.appliesToProperties(ctx, properties) {
+ if !n.appliesToProperties(properties) {
continue
}
@@ -261,20 +271,12 @@
continue
}
- if !n.appliesToBootclasspathJar(ctx) {
- continue
- }
-
ctx.ModuleErrorf("violates " + n.String())
}
}
-type ValueMatcherContext interface {
- Config() Config
-}
-
type ValueMatcher interface {
- Test(ValueMatcherContext, string) bool
+ Test(string) bool
String() string
}
@@ -282,7 +284,7 @@
expected string
}
-func (m *equalMatcher) Test(ctx ValueMatcherContext, value string) bool {
+func (m *equalMatcher) Test(value string) bool {
return m.expected == value
}
@@ -293,7 +295,7 @@
type anyMatcher struct {
}
-func (m *anyMatcher) Test(ctx ValueMatcherContext, value string) bool {
+func (m *anyMatcher) Test(value string) bool {
return true
}
@@ -307,7 +309,7 @@
prefix string
}
-func (m *startsWithMatcher) Test(ctx ValueMatcherContext, value string) bool {
+func (m *startsWithMatcher) Test(value string) bool {
return strings.HasPrefix(value, m.prefix)
}
@@ -319,7 +321,7 @@
re *regexp.Regexp
}
-func (m *regexMatcher) Test(ctx ValueMatcherContext, value string) bool {
+func (m *regexMatcher) Test(value string) bool {
return m.re.MatchString(value)
}
@@ -331,7 +333,7 @@
allowed []string
}
-func (m *notInListMatcher) Test(ctx ValueMatcherContext, value string) bool {
+func (m *notInListMatcher) Test(value string) bool {
return !InList(value, m.allowed)
}
@@ -341,7 +343,7 @@
type isSetMatcher struct{}
-func (m *isSetMatcher) Test(ctx ValueMatcherContext, value string) bool {
+func (m *isSetMatcher) Test(value string) bool {
return value != ""
}
@@ -351,19 +353,6 @@
var isSetMatcherInstance = &isSetMatcher{}
-type sdkVersionMatcher struct {
- condition func(ctx ValueMatcherContext, spec SdkSpec) bool
- description string
-}
-
-func (m *sdkVersionMatcher) Test(ctx ValueMatcherContext, value string) bool {
- return m.condition(ctx, SdkSpecFromWithConfig(ctx.Config(), value))
-}
-
-func (m *sdkVersionMatcher) String() string {
- return ".sdk-version(" + m.description + ")"
-}
-
type ruleProperty struct {
fields []string // e.x.: Vndk.Enabled
matcher ValueMatcher
@@ -397,8 +386,6 @@
NotModuleType(types ...string) Rule
- BootclasspathJar() Rule
-
With(properties, value string) Rule
WithMatcher(properties string, matcher ValueMatcher) Rule
@@ -514,12 +501,6 @@
return r
}
-// BootclasspathJar whether this rule only applies to Jars in the Bootclasspath
-func (r *rule) BootclasspathJar() Rule {
- r.onlyBootclasspathJar = true
- return r
-}
-
func (r *rule) String() string {
s := []string{"neverallow requirements. Not allowed:"}
if len(r.paths) > 0 {
@@ -537,9 +518,6 @@
if len(r.osClasses) > 0 {
s = append(s, fmt.Sprintf("os class(es): %q", r.osClasses))
}
- if r.onlyBootclasspathJar {
- s = append(s, "in bootclasspath jar")
- }
if len(r.unlessPaths) > 0 {
s = append(s, fmt.Sprintf("EXCEPT in dirs: %q", r.unlessPaths))
}
@@ -580,14 +558,6 @@
return matches
}
-func (r *rule) appliesToBootclasspathJar(ctx BottomUpMutatorContext) bool {
- if !r.onlyBootclasspathJar {
- return true
- }
-
- return InList(ctx.ModuleName(), ctx.Config().BootJars())
-}
-
func (r *rule) appliesToOsClass(osClass OsClass) bool {
if len(r.osClasses) == 0 {
return true
@@ -606,10 +576,9 @@
return (len(r.moduleTypes) == 0 || InList(moduleType, r.moduleTypes)) && !InList(moduleType, r.unlessModuleTypes)
}
-func (r *rule) appliesToProperties(ctx ValueMatcherContext,
- properties []interface{}) bool {
- includeProps := hasAllProperties(ctx, properties, r.props)
- excludeProps := hasAnyProperty(ctx, properties, r.unlessProps)
+func (r *rule) appliesToProperties(properties []interface{}) bool {
+ includeProps := hasAllProperties(properties, r.props)
+ excludeProps := hasAnyProperty(properties, r.unlessProps)
return includeProps && !excludeProps
}
@@ -629,16 +598,6 @@
return ¬InListMatcher{allowed}
}
-func LessThanSdkVersion(sdk string) ValueMatcher {
- return &sdkVersionMatcher{
- condition: func(ctx ValueMatcherContext, spec SdkSpec) bool {
- return spec.ApiLevel.LessThan(
- SdkSpecFromWithConfig(ctx.Config(), sdk).ApiLevel)
- },
- description: "lessThan=" + sdk,
- }
-}
-
// assorted utils
func cleanPaths(paths []string) []string {
@@ -657,28 +616,25 @@
return names
}
-func hasAnyProperty(ctx ValueMatcherContext, properties []interface{},
- props []ruleProperty) bool {
+func hasAnyProperty(properties []interface{}, props []ruleProperty) bool {
for _, v := range props {
- if hasProperty(ctx, properties, v) {
+ if hasProperty(properties, v) {
return true
}
}
return false
}
-func hasAllProperties(ctx ValueMatcherContext, properties []interface{},
- props []ruleProperty) bool {
+func hasAllProperties(properties []interface{}, props []ruleProperty) bool {
for _, v := range props {
- if !hasProperty(ctx, properties, v) {
+ if !hasProperty(properties, v) {
return false
}
}
return true
}
-func hasProperty(ctx ValueMatcherContext, properties []interface{},
- prop ruleProperty) bool {
+func hasProperty(properties []interface{}, prop ruleProperty) bool {
for _, propertyStruct := range properties {
propertiesValue := reflect.ValueOf(propertyStruct).Elem()
for _, v := range prop.fields {
@@ -692,7 +648,7 @@
}
check := func(value string) bool {
- return prop.matcher.Test(ctx, value)
+ return prop.matcher.Test(value)
}
if matchValue(propertiesValue, check) {
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index 59016d4..86f1a37 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -327,46 +327,19 @@
"Only boot images may be imported as a makefile goal.",
},
},
+ // Tests for the rule prohibiting the use of framework
{
- name: "min_sdk too low",
+ name: "prohibit framework",
fs: map[string][]byte{
"Android.bp": []byte(`
java_library {
- name: "min_sdk_too_low",
- min_sdk_version: "30",
+ name: "foo",
+ libs: ["framework"],
+ sdk_version: "current",
}`),
},
- rules: []Rule{
- NeverAllow().WithMatcher("min_sdk_version", LessThanSdkVersion("31")),
- },
expectedErrors: []string{
- "module \"min_sdk_too_low\": violates neverallow",
- },
- },
- {
- name: "min_sdk high enough",
- fs: map[string][]byte{
- "Android.bp": []byte(`
- java_library {
- name: "min_sdk_high_enough",
- min_sdk_version: "31",
- }`),
- },
- rules: []Rule{
- NeverAllow().WithMatcher("min_sdk_version", LessThanSdkVersion("31")),
- },
- },
- {
- name: "current min_sdk high enough",
- fs: map[string][]byte{
- "Android.bp": []byte(`
- java_library {
- name: "current_min_sdk_high_enough",
- min_sdk_version: "current",
- }`),
- },
- rules: []Rule{
- NeverAllow().WithMatcher("min_sdk_version", LessThanSdkVersion("31")),
+ "framework can't be used when building against SDK",
},
},
}
@@ -452,10 +425,9 @@
}
type mockJavaLibraryProperties struct {
- Libs []string
- Min_sdk_version *string
- Sdk_version *string
- Uncompress_dex *bool
+ Libs []string
+ Sdk_version *string
+ Uncompress_dex *bool
}
type mockJavaLibraryModule struct {
diff --git a/android/notices.go b/android/notices.go
index 194a734..2a4c17c 100644
--- a/android/notices.go
+++ b/android/notices.go
@@ -15,93 +15,9 @@
package android
import (
- "path/filepath"
"strings"
-
- "github.com/google/blueprint"
)
-func init() {
- pctx.SourcePathVariable("merge_notices", "build/soong/scripts/mergenotice.py")
- pctx.SourcePathVariable("generate_notice", "build/soong/scripts/generate-notice-files.py")
-
- pctx.HostBinToolVariable("minigzip", "minigzip")
-}
-
-type NoticeOutputs struct {
- Merged OptionalPath
- TxtOutput OptionalPath
- HtmlOutput OptionalPath
- HtmlGzOutput OptionalPath
-}
-
-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 $$(dirname $txtOut) $$(dirname $htmlOut) $$(dirname $out) && ` +
- `mkdir -p $$(dirname $txtOut) $$(dirname $htmlOut) $$(dirname $out) && ` +
- `${generate_notice} --text-output $txtOut --html-output $htmlOut -t "$title" -s $inputDir && ` +
- `${minigzip} -c $htmlOut > $out`,
- CommandDeps: []string{"${generate_notice}", "${minigzip}"},
- Description: "produce notice file $out",
- }, "txtOut", "htmlOut", "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 InstallPath, installFilename string,
- noticePaths []Path) NoticeOutputs {
- // 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.
- txtOuptut := PathForModuleOut(ctx, "NOTICE_txt", "NOTICE.txt")
- htmlOutput := PathForModuleOut(ctx, "NOTICE_html", "NOTICE.html")
- htmlGzOutput := PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
- title := "Notices for " + ctx.ModuleName()
- ctx.Build(pctx, BuildParams{
- Rule: generateNoticeRule,
- Description: "generate notice output",
- Input: mergedNotice,
- Output: htmlGzOutput,
- ImplicitOutputs: WritablePaths{txtOuptut, htmlOutput},
- Args: map[string]string{
- "txtOut": txtOuptut.String(),
- "htmlOut": htmlOutput.String(),
- "title": title,
- "inputDir": PathForModuleOut(ctx, "NOTICE_FILES/src").String(),
- },
- })
-
- return NoticeOutputs{
- Merged: OptionalPathForPath(mergedNotice),
- TxtOutput: OptionalPathForPath(txtOuptut),
- HtmlOutput: OptionalPathForPath(htmlOutput),
- HtmlGzOutput: OptionalPathForPath(htmlGzOutput),
- }
-}
-
// BuildNoticeTextOutputFromLicenseMetadata writes out a notice text file based on the module's
// generated license metadata file.
func BuildNoticeTextOutputFromLicenseMetadata(ctx ModuleContext, outputFile WritablePath) {
@@ -112,5 +28,18 @@
FlagWithOutput("-o ", outputFile).
FlagWithDepFile("-d ", depsFile).
Input(ctx.Module().base().licenseMetadataFile)
- rule.Build("container_notice", "container notice file")
+ rule.Build("text_notice", "container notice file")
+}
+
+// BuildNoticeHtmlOutputFromLicenseMetadata writes out a notice text file based on the module's
+// generated license metadata file.
+func BuildNoticeHtmlOutputFromLicenseMetadata(ctx ModuleContext, outputFile WritablePath) {
+ depsFile := outputFile.ReplaceExtension(ctx, strings.TrimPrefix(outputFile.Ext()+".d", "."))
+ rule := NewRuleBuilder(pctx, ctx)
+ rule.Command().
+ BuiltTool("htmlnotice").
+ FlagWithOutput("-o ", outputFile).
+ FlagWithDepFile("-d ", depsFile).
+ Input(ctx.Module().base().licenseMetadataFile)
+ rule.Build("html_notice", "container notice file")
}
diff --git a/apex/Android.bp b/apex/Android.bp
index b9b5428..41224ec 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -14,6 +14,7 @@
"soong-cc",
"soong-filesystem",
"soong-java",
+ "soong-provenance",
"soong-python",
"soong-rust",
"soong-sh",
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 059b4d7..e094a12 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -396,10 +396,6 @@
}
a.writeRequiredModules(w, moduleNames)
- if a.mergedNotices.Merged.Valid() {
- fmt.Fprintln(w, "LOCAL_NOTICE_FILE :=", a.mergedNotices.Merged.Path().String())
- }
-
fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
if apexType == imageApex {
diff --git a/apex/apex.go b/apex/apex.go
index 6d8a67a..2fe17da 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -414,8 +414,8 @@
// Processed file_contexts files
fileContexts android.WritablePath
- // Struct holding the merged notice file paths in different formats
- mergedNotices android.NoticeOutputs
+ // Path to notice file in html.gz format.
+ htmlGzNotice android.WritablePath
// The built APEX file. This is the main product.
// Could be .apex or .capex
@@ -487,11 +487,10 @@
// for each of the files in case when the APEX is flattened.
type apexFile struct {
// buildFile is put in the installDir inside the APEX.
- builtFile android.Path
- noticeFiles android.Paths
- installDir string
- customStem string
- symlinks []string // additional symlinks
+ builtFile android.Path
+ installDir string
+ customStem string
+ symlinks []string // additional symlinks
// Info for Android.mk Module name of `module` in AndroidMk. Note the generated AndroidMk
// module for apexFile is named something like <AndroidMk module name>.<apex name>[<apex
@@ -528,7 +527,6 @@
module: module,
}
if module != nil {
- ret.noticeFiles = module.NoticeFiles()
ret.moduleDir = ctx.OtherModuleDir(module)
ret.requiredModuleNames = module.RequiredModuleNames()
ret.targetRequiredModuleNames = module.TargetRequiredModuleNames()
@@ -3280,21 +3278,19 @@
}
func init() {
- android.AddNeverAllowRules(createApexPermittedPackagesRules(qModulesPackages())...)
- android.AddNeverAllowRules(createApexPermittedPackagesRules(rModulesPackages())...)
+ android.AddNeverAllowRules(createBcpPermittedPackagesRules(qBcpPackages())...)
+ android.AddNeverAllowRules(createBcpPermittedPackagesRules(rBcpPackages())...)
}
-func createApexPermittedPackagesRules(modules_packages map[string][]string) []android.Rule {
- rules := make([]android.Rule, 0, len(modules_packages))
- for module_name, module_packages := range modules_packages {
+func createBcpPermittedPackagesRules(bcpPermittedPackages map[string][]string) []android.Rule {
+ rules := make([]android.Rule, 0, len(bcpPermittedPackages))
+ for jar, permittedPackages := range bcpPermittedPackages {
permittedPackagesRule := android.NeverAllow().
- BootclasspathJar().
- With("apex_available", module_name).
- WithMatcher("permitted_packages", android.NotInList(module_packages)).
- WithMatcher("min_sdk_version", android.LessThanSdkVersion("Tiramisu")).
- Because("jars that are part of the " + module_name +
- " module may only use these package prefixes: " + strings.Join(module_packages, ",") +
- " with min_sdk < T. Please consider the following alternatives:\n" +
+ With("name", jar).
+ WithMatcher("permitted_packages", android.NotInList(permittedPackages)).
+ Because(jar +
+ " bootjar may only use these package prefixes: " + strings.Join(permittedPackages, ",") +
+ ". Please consider the following alternatives:\n" +
" 1. If the offending code is from a statically linked library, consider " +
"removing that dependency and using an alternative already in the " +
"bootclasspath, or perhaps a shared library." +
@@ -3302,55 +3298,56 @@
" 3. Jarjar the offending code. Please be mindful of the potential system " +
"health implications of bundling that code, particularly if the offending jar " +
"is part of the bootclasspath.")
+
rules = append(rules, permittedPackagesRule)
}
return rules
}
-// DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART on Q/R/S.
+// DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART.
// Adding code to the bootclasspath in new packages will cause issues on module update.
-func qModulesPackages() map[string][]string {
+func qBcpPackages() map[string][]string {
return map[string][]string{
- "com.android.conscrypt": []string{
+ "conscrypt": []string{
"android.net.ssl",
"com.android.org.conscrypt",
},
- "com.android.media": []string{
+ "updatable-media": []string{
"android.media",
},
}
}
-// DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART on R/S.
+// DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART.
// Adding code to the bootclasspath in new packages will cause issues on module update.
-func rModulesPackages() map[string][]string {
+func rBcpPackages() map[string][]string {
return map[string][]string{
- "com.android.mediaprovider": []string{
+ "framework-mediaprovider": []string{
"android.provider",
},
- "com.android.permission": []string{
+ "framework-permission": []string{
"android.permission",
"android.app.role",
"com.android.permission",
"com.android.role",
},
- "com.android.sdkext": []string{
+ "framework-sdkextensions": []string{
"android.os.ext",
},
- "com.android.os.statsd": []string{
+ "framework-statsd": []string{
"android.app",
"android.os",
"android.util",
"com.android.internal.statsd",
"com.android.server.stats",
},
- "com.android.wifi": []string{
+ "framework-wifi": []string{
"com.android.server.wifi",
"com.android.wifi.x",
"android.hardware.wifi",
"android.net.wifi",
},
- "com.android.tethering": []string{
+ "framework-tethering": []string{
"android.net",
},
}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 85bd595..3e01f26 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -591,15 +591,6 @@
t.Errorf("Could not find all expected symlinks! foo: %t, foo_link_64: %t. Command was %s", found_foo, found_foo_link_64, copyCmds)
}
- mergeNoticesRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("mergeNoticesRule")
- noticeInputs := mergeNoticesRule.Inputs.Strings()
- if len(noticeInputs) != 3 {
- t.Errorf("number of input notice files: expected = 3, actual = %q", len(noticeInputs))
- }
- ensureListContains(t, noticeInputs, "NOTICE")
- ensureListContains(t, noticeInputs, "custom_notice")
- ensureListContains(t, noticeInputs, "custom_notice_for_static_lib")
-
fullDepsInfo := strings.Split(ctx.ModuleForTests("myapex", "android_common_myapex_image").Output("depsinfo/fulllist.txt").Args["content"], "\\n")
ensureListContains(t, fullDepsInfo, " myjar(minSdkVersion:(no version)) <- myapex")
ensureListContains(t, fullDepsInfo, " mylib2(minSdkVersion:(no version)) <- mylib")
@@ -3898,7 +3889,7 @@
}),
withBinder32bit,
withTargets(map[android.OsType][]android.Target{
- android.Android: []android.Target{
+ android.Android: {
{Os: android.Android, Arch: android.Arch{ArchType: android.Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}},
NativeBridge: android.NativeBridgeDisabled, NativeBridgeHostArchName: "", NativeBridgeRelativePath: ""},
},
@@ -4579,12 +4570,20 @@
}
`)
- prebuilt := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*Prebuilt)
+ testingModule := ctx.ModuleForTests("myapex", "android_common_myapex")
+ prebuilt := testingModule.Module().(*Prebuilt)
expectedInput := "myapex-arm64.apex"
if prebuilt.inputApex.String() != expectedInput {
t.Errorf("inputApex invalid. expected: %q, actual: %q", expectedInput, prebuilt.inputApex.String())
}
+ android.AssertStringDoesContain(t, "Invalid provenance metadata file",
+ prebuilt.ProvenanceMetaDataFile().String(), "soong/.intermediates/provenance_metadata/myapex/provenance_metadata.textproto")
+ rule := testingModule.Rule("genProvenanceMetaData")
+ android.AssertStringEquals(t, "Invalid input", "myapex-arm64.apex", rule.Inputs[0].String())
+ android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/myapex/provenance_metadata.textproto", rule.Output.String())
+ android.AssertStringEquals(t, "Invalid args", "myapex", rule.Args["module_name"])
+ android.AssertStringEquals(t, "Invalid args", "/system/apex/myapex.apex", rule.Args["install_path"])
}
func TestPrebuiltMissingSrc(t *testing.T) {
@@ -4604,12 +4603,18 @@
}
`)
- p := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*Prebuilt)
+ testingModule := ctx.ModuleForTests("myapex", "android_common_myapex")
+ p := testingModule.Module().(*Prebuilt)
expected := "notmyapex.apex"
if p.installFilename != expected {
t.Errorf("installFilename invalid. expected: %q, actual: %q", expected, p.installFilename)
}
+ rule := testingModule.Rule("genProvenanceMetaData")
+ android.AssertStringEquals(t, "Invalid input", "myapex-arm.apex", rule.Inputs[0].String())
+ android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/myapex/provenance_metadata.textproto", rule.Output.String())
+ android.AssertStringEquals(t, "Invalid args", "myapex", rule.Args["module_name"])
+ android.AssertStringEquals(t, "Invalid args", "/system/apex/notmyapex.apex", rule.Args["install_path"])
}
func TestApexSetFilenameOverride(t *testing.T) {
@@ -4652,13 +4657,19 @@
}
`)
- p := ctx.ModuleForTests("myapex.prebuilt", "android_common_myapex.prebuilt").Module().(*Prebuilt)
+ testingModule := ctx.ModuleForTests("myapex.prebuilt", "android_common_myapex.prebuilt")
+ p := testingModule.Module().(*Prebuilt)
expected := []string{"myapex"}
actual := android.AndroidMkEntriesForTest(t, ctx, p)[0].EntryMap["LOCAL_OVERRIDES_MODULES"]
if !reflect.DeepEqual(actual, expected) {
t.Errorf("Incorrect LOCAL_OVERRIDES_MODULES value '%s', expected '%s'", actual, expected)
}
+ rule := testingModule.Rule("genProvenanceMetaData")
+ android.AssertStringEquals(t, "Invalid input", "myapex-arm.apex", rule.Inputs[0].String())
+ android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/myapex.prebuilt/provenance_metadata.textproto", rule.Output.String())
+ android.AssertStringEquals(t, "Invalid args", "myapex.prebuilt", rule.Args["module_name"])
+ android.AssertStringEquals(t, "Invalid args", "/system/apex/myapex.prebuilt.apex", rule.Args["install_path"])
}
func TestPrebuiltApexName(t *testing.T) {
@@ -7589,7 +7600,7 @@
})
}
-func testApexPermittedPackagesRules(t *testing.T, errmsg, bp string, bootJars []string, rules []android.Rule) {
+func testBootJarPermittedPackagesRules(t *testing.T, errmsg, bp string, bootJars []string, rules []android.Rule) {
t.Helper()
bp += `
apex_key {
@@ -7628,11 +7639,11 @@
func TestApexPermittedPackagesRules(t *testing.T) {
testcases := []struct {
- name string
- expectedError string
- bp string
- bootJars []string
- modulesPackages map[string][]string
+ name string
+ expectedError string
+ bp string
+ bootJars []string
+ bcpPermittedPackages map[string][]string
}{
{
@@ -7646,7 +7657,6 @@
apex_available: ["myapex"],
sdk_version: "none",
system_modules: "none",
- min_sdk_version: "30",
}
java_library {
name: "nonbcp_lib2",
@@ -7655,25 +7665,23 @@
permitted_packages: ["a.b"],
sdk_version: "none",
system_modules: "none",
- min_sdk_version: "30",
}
apex {
name: "myapex",
- min_sdk_version: "30",
key: "myapex.key",
java_libs: ["bcp_lib1", "nonbcp_lib2"],
updatable: false,
}`,
bootJars: []string{"bcp_lib1"},
- modulesPackages: map[string][]string{
- "myapex": []string{
+ bcpPermittedPackages: map[string][]string{
+ "bcp_lib1": []string{
"foo.bar",
},
},
},
{
- name: "Bootclasspath apex jar not satisfying allowed module packages on Q.",
- expectedError: `(?s)module "bcp_lib2" .* which is restricted because jars that are part of the myapex module may only use these package prefixes: foo.bar with min_sdk < T. Please consider the following alternatives:\n 1. If the offending code is from a statically linked library, consider removing that dependency and using an alternative already in the bootclasspath, or perhaps a shared library. 2. Move the offending code into an allowed package.\n 3. Jarjar the offending code. Please be mindful of the potential system health implications of bundling that code, particularly if the offending jar is part of the bootclasspath.`,
+ name: "Bootclasspath apex jar not satisfying allowed module packages.",
+ expectedError: `(?s)module "bcp_lib2" .* which is restricted because bcp_lib2 bootjar may only use these package prefixes: foo.bar. Please consider the following alternatives:\n 1. If the offending code is from a statically linked library, consider removing that dependency and using an alternative already in the bootclasspath, or perhaps a shared library. 2. Move the offending code into an allowed package.\n 3. Jarjar the offending code. Please be mindful of the potential system health implications of bundling that code, particularly if the offending jar is part of the bootclasspath.`,
bp: `
java_library {
name: "bcp_lib1",
@@ -7682,7 +7690,6 @@
permitted_packages: ["foo.bar"],
sdk_version: "none",
system_modules: "none",
- min_sdk_version: "29",
}
java_library {
name: "bcp_lib2",
@@ -7691,102 +7698,67 @@
permitted_packages: ["foo.bar", "bar.baz"],
sdk_version: "none",
system_modules: "none",
- min_sdk_version: "29",
}
apex {
name: "myapex",
- min_sdk_version: "29",
key: "myapex.key",
java_libs: ["bcp_lib1", "bcp_lib2"],
updatable: false,
}
`,
bootJars: []string{"bcp_lib1", "bcp_lib2"},
- modulesPackages: map[string][]string{
- "myapex": []string{
+ bcpPermittedPackages: map[string][]string{
+ "bcp_lib1": []string{
+ "foo.bar",
+ },
+ "bcp_lib2": []string{
"foo.bar",
},
},
},
{
- name: "Bootclasspath apex jar not satisfying allowed module packages on R.",
- expectedError: `(?s)module "bcp_lib2" .* which is restricted because jars that are part of the myapex module may only use these package prefixes: foo.bar with min_sdk < T. Please consider the following alternatives:\n 1. If the offending code is from a statically linked library, consider removing that dependency and using an alternative already in the bootclasspath, or perhaps a shared library. 2. Move the offending code into an allowed package.\n 3. Jarjar the offending code. Please be mindful of the potential system health implications of bundling that code, particularly if the offending jar is part of the bootclasspath.`,
- bp: `
- java_library {
- name: "bcp_lib1",
- srcs: ["lib1/src/*.java"],
- apex_available: ["myapex"],
- permitted_packages: ["foo.bar"],
- sdk_version: "none",
- system_modules: "none",
- min_sdk_version: "30",
- }
- java_library {
- name: "bcp_lib2",
- srcs: ["lib2/src/*.java"],
- apex_available: ["myapex"],
- permitted_packages: ["foo.bar", "bar.baz"],
- sdk_version: "none",
- system_modules: "none",
- min_sdk_version: "30",
- }
- apex {
- name: "myapex",
- min_sdk_version: "30",
- key: "myapex.key",
- java_libs: ["bcp_lib1", "bcp_lib2"],
- updatable: false,
- }
- `,
- bootJars: []string{"bcp_lib1", "bcp_lib2"},
- modulesPackages: map[string][]string{
- "myapex": []string{
- "foo.bar",
- },
- },
- },
- {
- name: "Bootclasspath apex jar >= T not satisfying Q/R/S allowed module packages.",
+ name: "Updateable Bootclasspath apex jar not satisfying allowed module packages.",
expectedError: "",
bp: `
java_library {
- name: "bcp_lib1",
+ name: "bcp_lib_restricted",
srcs: ["lib1/src/*.java"],
apex_available: ["myapex"],
permitted_packages: ["foo.bar"],
sdk_version: "none",
+ min_sdk_version: "29",
system_modules: "none",
- min_sdk_version: "current",
}
java_library {
- name: "bcp_lib2",
+ name: "bcp_lib_unrestricted",
srcs: ["lib2/src/*.java"],
apex_available: ["myapex"],
permitted_packages: ["foo.bar", "bar.baz"],
sdk_version: "none",
+ min_sdk_version: "29",
system_modules: "none",
- min_sdk_version: "current",
}
apex {
name: "myapex",
- min_sdk_version: "current",
key: "myapex.key",
- java_libs: ["bcp_lib1", "bcp_lib2"],
- updatable: false,
+ java_libs: ["bcp_lib_restricted", "bcp_lib_unrestricted"],
+ updatable: true,
+ min_sdk_version: "29",
}
`,
bootJars: []string{"bcp_lib1", "bcp_lib2"},
- modulesPackages: map[string][]string{
- "myapex": []string{
+ bcpPermittedPackages: map[string][]string{
+ "bcp_lib1_non_updateable": []string{
"foo.bar",
},
+ // bcp_lib2_updateable has no entry here since updateable bcp can contain new packages - tracking via an allowlist is not necessary
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
- rules := createApexPermittedPackagesRules(tc.modulesPackages)
- testApexPermittedPackagesRules(t, tc.expectedError, tc.bp, tc.bootJars, rules)
+ rules := createBcpPermittedPackagesRules(tc.bcpPermittedPackages)
+ testBootJarPermittedPackagesRules(t, tc.expectedError, tc.bp, tc.bootJars, rules)
})
}
}
diff --git a/apex/builder.go b/apex/builder.go
index 8c5f99b..ea61e1a 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -305,32 +305,6 @@
return output.OutputPath
}
-// buildNoticeFiles creates a buile rule for aggregating notice files from the modules that
-// contributes to this APEX. The notice files are merged into a big notice file.
-func (a *apexBundle) buildNoticeFiles(ctx android.ModuleContext, apexFileName string) android.NoticeOutputs {
- var noticeFiles android.Paths
-
- a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
- if externalDep {
- // As soon as the dependency graph crosses the APEX boundary, don't go further.
- return false
- }
- noticeFiles = append(noticeFiles, to.NoticeFiles()...)
- return true
- })
-
- // TODO(jiyong): why do we need this? WalkPayloadDeps should have already covered this.
- for _, fi := range a.filesInfo {
- noticeFiles = append(noticeFiles, fi.noticeFiles...)
- }
-
- if len(noticeFiles) == 0 {
- return android.NoticeOutputs{}
- }
-
- return android.BuildNoticeOutput(ctx, a.installDir, apexFileName, android.SortedUniquePaths(noticeFiles))
-}
-
// buildInstalledFilesFile creates a build rule for the installed-files.txt file where the list of
// files included in this APEX is shown. The text file is dist'ed so that people can see what's
// included in the APEX without actually downloading and extracting it.
@@ -642,12 +616,17 @@
optFlags = append(optFlags, "--logging_parent ", a.overridableProperties.Logging_parent)
}
- a.mergedNotices = a.buildNoticeFiles(ctx, a.Name()+suffix)
- if a.mergedNotices.HtmlGzOutput.Valid() {
- // If there's a NOTICE file, embed it as an asset file in the APEX.
- implicitInputs = append(implicitInputs, a.mergedNotices.HtmlGzOutput.Path())
- optFlags = append(optFlags, "--assets_dir "+filepath.Dir(a.mergedNotices.HtmlGzOutput.String()))
- }
+ // 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)
+ noticeAssetPath := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
+ builder := android.NewRuleBuilder(pctx, ctx)
+ builder.Command().Text("cp").
+ Input(a.htmlGzNotice).
+ Output(noticeAssetPath)
+ builder.Build("notice_dir", "Building notice dir")
+ implicitInputs = append(implicitInputs, noticeAssetPath)
+ optFlags = append(optFlags, "--assets_dir "+filepath.Dir(noticeAssetPath.String()))
if (moduleMinSdkVersion.GreaterThan(android.SdkVersion_Android10) && !a.shouldGenerateHashtree()) && !compressionEnabled {
// Apexes which are supposed to be installed in builtin dirs(/system, etc)
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index 158c804..187e0df 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -23,7 +23,7 @@
"android/soong/android"
"android/soong/java"
-
+ "android/soong/provenance"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
)
@@ -482,6 +482,8 @@
properties PrebuiltProperties
inputApex android.Path
+
+ provenanceMetaDataFile android.OutputPath
}
type ApexFileProperties struct {
@@ -778,9 +780,14 @@
if p.installable() {
p.installedFile = ctx.InstallFile(p.installDir, p.installFilename, p.inputApex, p.compatSymlinks.Paths()...)
+ p.provenanceMetaDataFile = provenance.GenerateArtifactProvenanceMetaData(ctx, p.inputApex, p.installedFile)
}
}
+func (p *Prebuilt) ProvenanceMetaDataFile() android.OutputPath {
+ return p.provenanceMetaDataFile
+}
+
// prebuiltApexExtractorModule is a private module type that is only created by the prebuilt_apex
// module. It extracts the correct apex to use and makes it available for use by apex_set.
type prebuiltApexExtractorModule struct {
diff --git a/bp2build/java_import_conversion_test.go b/bp2build/java_import_conversion_test.go
index 2f7211c..0b3191c 100644
--- a/bp2build/java_import_conversion_test.go
+++ b/bp2build/java_import_conversion_test.go
@@ -29,7 +29,7 @@
func registerJavaImportModuleTypes(ctx android.RegistrationContext) {
}
-func TestMinimalJavaImport(t *testing.T) {
+func TestJavaImportMinimal(t *testing.T) {
runJavaImportTestCase(t, bp2buildTestCase{
description: "Java import - simple example",
moduleTypeUnderTest: "java_import",
@@ -50,3 +50,36 @@
}),
}})
}
+
+func TestJavaImportArchVariant(t *testing.T) {
+ runJavaImportTestCase(t, bp2buildTestCase{
+ description: "Java import - simple example",
+ moduleTypeUnderTest: "java_import",
+ moduleTypeUnderTestFactory: java.ImportFactory,
+ filesystem: map[string]string{
+ "import.jar": "",
+ },
+ blueprint: `
+java_import {
+ name: "example_import",
+ target: {
+ android: {
+ jars: ["android.jar"],
+ },
+ linux_glibc: {
+ jars: ["linux.jar"],
+ },
+ },
+ bazel_module: { bp2build_available: true },
+}
+`,
+ expectedBazelTargets: []string{
+ makeBazelTarget("java_import", "example_import", attrNameToString{
+ "jars": `select({
+ "//build/bazel/platforms/os:android": ["android.jar"],
+ "//build/bazel/platforms/os:linux": ["linux.jar"],
+ "//conditions:default": [],
+ })`,
+ }),
+ }})
+}
diff --git a/bp2build/java_library_conversion_test.go b/bp2build/java_library_conversion_test.go
index 2f6bce2..4b75e3b 100644
--- a/bp2build/java_library_conversion_test.go
+++ b/bp2build/java_library_conversion_test.go
@@ -30,6 +30,7 @@
}
func runJavaLibraryTestCase(t *testing.T, tc bp2buildTestCase) {
+ t.Helper()
runJavaLibraryTestCaseWithRegistrationCtxFunc(t, tc, func(ctx android.RegistrationContext) {})
}
@@ -156,3 +157,65 @@
ctx.RegisterModuleType("java_plugin", java.PluginFactory)
})
}
+
+func TestJavaLibraryErrorproneJavacflagsEnabledManually(t *testing.T) {
+ runJavaLibraryTestCase(t, bp2buildTestCase{
+ blueprint: `java_library {
+ name: "java-lib-1",
+ srcs: ["a.java"],
+ javacflags: ["-Xsuper-fast"],
+ errorprone: {
+ enabled: true,
+ javacflags: ["-Xep:SpeedLimit:OFF"],
+ },
+}`,
+ expectedBazelTargets: []string{
+ makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+ "javacopts": `[
+ "-Xsuper-fast",
+ "-Xep:SpeedLimit:OFF",
+ ]`,
+ "srcs": `["a.java"]`,
+ }),
+ },
+ })
+}
+
+func TestJavaLibraryErrorproneJavacflagsErrorproneDisabledByDefault(t *testing.T) {
+ runJavaLibraryTestCase(t, bp2buildTestCase{
+ blueprint: `java_library {
+ name: "java-lib-1",
+ srcs: ["a.java"],
+ javacflags: ["-Xsuper-fast"],
+ errorprone: {
+ javacflags: ["-Xep:SpeedLimit:OFF"],
+ },
+}`,
+ expectedBazelTargets: []string{
+ makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+ "javacopts": `["-Xsuper-fast"]`,
+ "srcs": `["a.java"]`,
+ }),
+ },
+ })
+}
+
+func TestJavaLibraryErrorproneJavacflagsErrorproneDisabledManually(t *testing.T) {
+ runJavaLibraryTestCase(t, bp2buildTestCase{
+ blueprint: `java_library {
+ name: "java-lib-1",
+ srcs: ["a.java"],
+ javacflags: ["-Xsuper-fast"],
+ errorprone: {
+ enabled: false,
+ javacflags: ["-Xep:SpeedLimit:OFF"],
+ },
+}`,
+ expectedBazelTargets: []string{
+ makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+ "javacopts": `["-Xsuper-fast"]`,
+ "srcs": `["a.java"]`,
+ }),
+ },
+ })
+}
diff --git a/cc/androidmk.go b/cc/androidmk.go
index 318cd7c..ff5ba45 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -331,6 +331,14 @@
})
}
+func (test *testDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+ entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+ if len(test.InstallerProperties.Test_suites) > 0 {
+ entries.AddCompatibilityTestSuites(test.InstallerProperties.Test_suites...)
+ }
+ })
+}
+
func (binary *binaryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
ctx.subAndroidMk(entries, binary.baseInstaller)
@@ -379,14 +387,13 @@
func (test *testBinary) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
ctx.subAndroidMk(entries, test.binaryDecorator)
+ ctx.subAndroidMk(entries, test.testDecorator)
+
entries.Class = "NATIVE_TESTS"
if Bool(test.Properties.Test_per_src) {
entries.SubName = "_" + String(test.binaryDecorator.Properties.Stem)
}
entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
- if len(test.Properties.Test_suites) > 0 {
- entries.AddCompatibilityTestSuites(test.Properties.Test_suites...)
- }
if test.testConfig != nil {
entries.SetString("LOCAL_FULL_TEST_CONFIG", test.testConfig.String())
}
@@ -445,6 +452,7 @@
func (test *testLibrary) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
ctx.subAndroidMk(entries, test.libraryDecorator)
+ ctx.subAndroidMk(entries, test.testDecorator)
}
func (installer *baseInstaller) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
diff --git a/cc/cc.go b/cc/cc.go
index 58ab28c..ac6da05 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -3595,7 +3595,8 @@
&SharedProperties{},
&FlagExporterProperties{},
&BinaryLinkerProperties{},
- &TestProperties{},
+ &TestLinkerProperties{},
+ &TestInstallerProperties{},
&TestBinaryProperties{},
&BenchmarkProperties{},
&fuzz.FuzzProperties{},
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 278efa1..09cc352 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -779,6 +779,68 @@
}
}
+func TestTestBinaryTestSuites(t *testing.T) {
+ bp := `
+ cc_test {
+ name: "main_test",
+ srcs: ["main_test.cpp"],
+ test_suites: [
+ "suite_1",
+ "suite_2",
+ ],
+ gtest: false,
+ }
+ `
+
+ ctx := prepareForCcTest.RunTestWithBp(t, bp).TestContext
+ module := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon").Module()
+
+ entries := android.AndroidMkEntriesForTest(t, ctx, module)[0]
+ compatEntries := entries.EntryMap["LOCAL_COMPATIBILITY_SUITE"]
+ if len(compatEntries) != 2 {
+ t.Errorf("expected two elements in LOCAL_COMPATIBILITY_SUITE. got %d", len(compatEntries))
+ }
+ if compatEntries[0] != "suite_1" {
+ t.Errorf("expected LOCAL_COMPATIBILITY_SUITE to be`suite_1`,"+
+ " but was '%s'", compatEntries[0])
+ }
+ if compatEntries[1] != "suite_2" {
+ t.Errorf("expected LOCAL_COMPATIBILITY_SUITE to be`suite_2`,"+
+ " but was '%s'", compatEntries[1])
+ }
+}
+
+func TestTestLibraryTestSuites(t *testing.T) {
+ bp := `
+ cc_test_library {
+ name: "main_test_lib",
+ srcs: ["main_test_lib.cpp"],
+ test_suites: [
+ "suite_1",
+ "suite_2",
+ ],
+ gtest: false,
+ }
+ `
+
+ ctx := prepareForCcTest.RunTestWithBp(t, bp).TestContext
+ module := ctx.ModuleForTests("main_test_lib", "android_arm_armv7-a-neon_shared").Module()
+
+ entries := android.AndroidMkEntriesForTest(t, ctx, module)[0]
+ compatEntries := entries.EntryMap["LOCAL_COMPATIBILITY_SUITE"]
+ if len(compatEntries) != 2 {
+ t.Errorf("expected two elements in LOCAL_COMPATIBILITY_SUITE. got %d", len(compatEntries))
+ }
+ if compatEntries[0] != "suite_1" {
+ t.Errorf("expected LOCAL_COMPATIBILITY_SUITE to be`suite_1`,"+
+ " but was '%s'", compatEntries[0])
+ }
+ if compatEntries[1] != "suite_2" {
+ t.Errorf("expected LOCAL_COMPATIBILITY_SUITE to be`suite_2`,"+
+ " but was '%s'", compatEntries[1])
+ }
+}
+
func TestVndkWhenVndkVersionIsNotSet(t *testing.T) {
ctx := testCcNoVndk(t, `
cc_library {
diff --git a/cc/config/global.go b/cc/config/global.go
index 8dda537..0f31931 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -214,7 +214,6 @@
// http://b/145211066
"-Wno-implicit-int-float-conversion",
// New warnings to be fixed after clang-r377782.
- "-Wno-int-in-bool-context", // http://b/148287349
"-Wno-sizeof-array-div", // http://b/148815709
"-Wno-tautological-overlap-compare", // http://b/148815696
// New warnings to be fixed after clang-r383902.
@@ -226,7 +225,6 @@
"-Wno-deprecated-enum-enum-conversion", // http://b/153746563
"-Wno-string-compare", // http://b/153764102
"-Wno-enum-enum-conversion", // http://b/154138986
- "-Wno-enum-float-conversion", // http://b/154255917
"-Wno-pessimizing-move", // http://b/154270751
// New warnings to be fixed after clang-r399163
"-Wno-non-c-typedef-for-linkage", // http://b/161304145
@@ -287,8 +285,8 @@
// prebuilts/clang default settings.
ClangDefaultBase = "prebuilts/clang/host"
- ClangDefaultVersion = "clang-r450784"
- ClangDefaultShortVersion = "14.0.3"
+ ClangDefaultVersion = "clang-r450784b"
+ ClangDefaultShortVersion = "14.0.4"
// Directories with warnings from Android.bp files.
WarningAllowedProjects = []string{
diff --git a/cc/test.go b/cc/test.go
index d8b7833..ead7877 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -25,7 +25,8 @@
"android/soong/tradefed"
)
-type TestProperties struct {
+// TestLinkerProperties properties to be registered via the linker
+type TestLinkerProperties struct {
// if set, build against the gtest library. Defaults to true.
Gtest *bool
@@ -33,6 +34,12 @@
Isolated *bool
}
+// TestInstallerProperties properties to be registered via the installer
+type TestInstallerProperties struct {
+ // list of compatibility suites (for example "cts", "vts") that the module should be installed into.
+ Test_suites []string `android:"arch_variant"`
+}
+
// Test option struct.
type TestOptions struct {
// The UID that you want to run the test as on a device.
@@ -83,10 +90,6 @@
// list of binary modules that should be installed alongside the test
Data_bins []string `android:"arch_variant"`
- // list of compatibility suites (for example "cts", "vts") that the module should be
- // installed into.
- Test_suites []string `android:"arch_variant"`
-
// the name of the test configuration (for example "AndroidTest.xml") that should be
// installed with the module.
Test_config *string `android:"path,arch_variant"`
@@ -243,12 +246,14 @@
}
type testDecorator struct {
- Properties TestProperties
- linker *baseLinker
+ LinkerProperties TestLinkerProperties
+ InstallerProperties TestInstallerProperties
+ installer *baseInstaller
+ linker *baseLinker
}
func (test *testDecorator) gtest() bool {
- return BoolDefault(test.Properties.Gtest, true)
+ return BoolDefault(test.LinkerProperties.Gtest, true)
}
func (test *testDecorator) testBinary() bool {
@@ -283,7 +288,7 @@
if test.gtest() {
if ctx.useSdk() && ctx.Device() {
deps.StaticLibs = append(deps.StaticLibs, "libgtest_main_ndk_c++", "libgtest_ndk_c++")
- } else if BoolDefault(test.Properties.Isolated, false) {
+ } else if BoolDefault(test.LinkerProperties.Isolated, false) {
deps.StaticLibs = append(deps.StaticLibs, "libgtest_isolated_main")
// The isolated library requires liblog, but adding it
// as a static library means unit tests cannot override
@@ -316,7 +321,11 @@
}
func (test *testDecorator) linkerProps() []interface{} {
- return []interface{}{&test.Properties}
+ return []interface{}{&test.LinkerProperties}
+}
+
+func (test *testDecorator) installerProps() []interface{} {
+ return []interface{}{&test.InstallerProperties}
}
func NewTestInstaller() *baseInstaller {
@@ -324,7 +333,7 @@
}
type testBinary struct {
- testDecorator
+ *testDecorator
*binaryDecorator
*baseCompiler
Properties TestBinaryProperties
@@ -358,6 +367,10 @@
return flags
}
+func (test *testBinary) installerProps() []interface{} {
+ return append(test.baseInstaller.installerProps(), test.testDecorator.installerProps()...)
+}
+
func (test *testBinary) install(ctx ModuleContext, file android.Path) {
// TODO: (b/167308193) Switch to /data/local/tests/unrestricted as the default install base.
testInstallBase := "/data/local/tmp"
@@ -411,7 +424,7 @@
var options []tradefed.Option
configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.StopServicesSetup", options})
}
- if Bool(test.testDecorator.Properties.Isolated) {
+ if Bool(test.testDecorator.LinkerProperties.Isolated) {
configs = append(configs, tradefed.Option{Name: "not-shardable", Value: "true"})
}
if test.Properties.Test_options.Run_test_as != nil {
@@ -441,7 +454,7 @@
}
test.testConfig = tradefed.AutoGenNativeTestConfig(ctx, test.Properties.Test_config,
- test.Properties.Test_config_template, test.Properties.Test_suites, configs, test.Properties.Auto_gen_config, testInstallBase)
+ test.Properties.Test_config_template, test.testDecorator.InstallerProperties.Test_suites, configs, test.Properties.Auto_gen_config, testInstallBase)
test.extraTestConfigs = android.PathsForModuleSrc(ctx, test.Properties.Test_options.Extra_test_configs)
@@ -466,8 +479,9 @@
binary.baseInstaller = NewTestInstaller()
test := &testBinary{
- testDecorator: testDecorator{
- linker: binary.baseLinker,
+ testDecorator: &testDecorator{
+ linker: binary.baseLinker,
+ installer: binary.baseInstaller,
},
binaryDecorator: binary,
baseCompiler: NewBaseCompiler(),
@@ -479,12 +493,14 @@
}
type testLibrary struct {
- testDecorator
+ *testDecorator
*libraryDecorator
}
func (test *testLibrary) linkerProps() []interface{} {
- return append(test.testDecorator.linkerProps(), test.libraryDecorator.linkerProps()...)
+ var props []interface{}
+ props = append(props, test.testDecorator.linkerProps()...)
+ return append(props, test.libraryDecorator.linkerProps()...)
}
func (test *testLibrary) linkerInit(ctx BaseModuleContext) {
@@ -504,16 +520,22 @@
return flags
}
+func (test *testLibrary) installerProps() []interface{} {
+ return append(test.baseInstaller.installerProps(), test.testDecorator.installerProps()...)
+}
+
func NewTestLibrary(hod android.HostOrDeviceSupported) *Module {
module, library := NewLibrary(android.HostAndDeviceSupported)
library.baseInstaller = NewTestInstaller()
test := &testLibrary{
- testDecorator: testDecorator{
- linker: library.baseLinker,
+ testDecorator: &testDecorator{
+ linker: library.baseLinker,
+ installer: library.baseInstaller,
},
libraryDecorator: library,
}
module.linker = test
+ module.installer = test
return module
}
diff --git a/cmd/soong_build/Android.bp b/cmd/soong_build/Android.bp
index e85163e..72af3e0 100644
--- a/cmd/soong_build/Android.bp
+++ b/cmd/soong_build/Android.bp
@@ -25,6 +25,7 @@
"golang-protobuf-android",
"soong",
"soong-android",
+ "soong-provenance",
"soong-bp2build",
"soong-ui-metrics_proto",
],
diff --git a/cmd/symbols_map/Android.bp b/cmd/symbols_map/Android.bp
new file mode 100644
index 0000000..0ba3b07
--- /dev/null
+++ b/cmd/symbols_map/Android.bp
@@ -0,0 +1,34 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+ name: "symbols_map",
+ srcs: [
+ "elf.go",
+ "r8.go",
+ "symbols_map.go",
+ ],
+ testSrcs: [
+ "elf_test.go",
+ "r8_test.go",
+ ],
+ deps: [
+ "blueprint-pathtools",
+ "golang-protobuf-encoding-prototext",
+ "soong-response",
+ "symbols_map_proto",
+ ],
+}
+
+bootstrap_go_package {
+ name: "symbols_map_proto",
+ pkgPath: "android/soong/cmd/symbols_map/symbols_map_proto",
+ deps: [
+ "golang-protobuf-reflect-protoreflect",
+ "golang-protobuf-runtime-protoimpl",
+ ],
+ srcs: [
+ "symbols_map_proto/symbols_map.pb.go",
+ ],
+}
diff --git a/cmd/symbols_map/elf.go b/cmd/symbols_map/elf.go
new file mode 100644
index 0000000..b38896a
--- /dev/null
+++ b/cmd/symbols_map/elf.go
@@ -0,0 +1,95 @@
+// Copyright 2022 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 main
+
+import (
+ "debug/elf"
+ "encoding/binary"
+ "encoding/hex"
+ "fmt"
+ "io"
+)
+
+const gnuBuildID = "GNU\x00"
+
+// elfIdentifier extracts the elf build ID from an elf file. If allowMissing is true it returns
+// an empty identifier if the file exists but the build ID note does not.
+func elfIdentifier(filename string, allowMissing bool) (string, error) {
+ f, err := elf.Open(filename)
+ if err != nil {
+ return "", fmt.Errorf("failed to open %s: %w", filename, err)
+ }
+ defer f.Close()
+
+ buildIDNote := f.Section(".note.gnu.build-id")
+ if buildIDNote == nil {
+ if allowMissing {
+ return "", nil
+ }
+ return "", fmt.Errorf("failed to find .note.gnu.build-id in %s", filename)
+ }
+
+ buildIDs, err := readNote(buildIDNote.Open(), f.ByteOrder)
+ if err != nil {
+ return "", fmt.Errorf("failed to read .note.gnu.build-id: %w", err)
+ }
+
+ for name, desc := range buildIDs {
+ if name == gnuBuildID {
+ return hex.EncodeToString(desc), nil
+ }
+ }
+
+ return "", nil
+}
+
+// readNote reads the contents of a note section, returning it as a map from name to descriptor.
+func readNote(note io.Reader, byteOrder binary.ByteOrder) (map[string][]byte, error) {
+ var noteHeader struct {
+ Namesz uint32
+ Descsz uint32
+ Type uint32
+ }
+
+ notes := make(map[string][]byte)
+ for {
+ err := binary.Read(note, byteOrder, ¬eHeader)
+ if err != nil {
+ if err == io.EOF {
+ return notes, nil
+ }
+ return nil, fmt.Errorf("failed to read note header: %w", err)
+ }
+
+ nameBuf := make([]byte, align4(noteHeader.Namesz))
+ err = binary.Read(note, byteOrder, &nameBuf)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read note name: %w", err)
+ }
+ name := string(nameBuf[:noteHeader.Namesz])
+
+ descBuf := make([]byte, align4(noteHeader.Descsz))
+ err = binary.Read(note, byteOrder, &descBuf)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read note desc: %w", err)
+ }
+ notes[name] = descBuf[:noteHeader.Descsz]
+ }
+}
+
+// align4 rounds the input up to the next multiple of 4.
+func align4(i uint32) uint32 {
+ return (i + 3) &^ 3
+}
diff --git a/cmd/symbols_map/elf_test.go b/cmd/symbols_map/elf_test.go
new file mode 100644
index 0000000..e616228
--- /dev/null
+++ b/cmd/symbols_map/elf_test.go
@@ -0,0 +1,45 @@
+// Copyright 2022 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 main
+
+import (
+ "bytes"
+ "encoding/binary"
+ "reflect"
+ "testing"
+)
+
+func Test_readNote(t *testing.T) {
+ note := []byte{
+ 0x04, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00,
+ 0x47, 0x4e, 0x55, 0x00,
+ 0xca, 0xaf, 0x44, 0xd2, 0x82, 0x78, 0x68, 0xfe, 0xc0, 0x90, 0xa3, 0x43, 0x85, 0x36, 0x6c, 0xc7,
+ }
+
+ descs, err := readNote(bytes.NewBuffer(note), binary.LittleEndian)
+ if err != nil {
+ t.Fatalf("unexpected error in readNote: %s", err)
+ }
+
+ expectedDescs := map[string][]byte{
+ "GNU\x00": []byte{0xca, 0xaf, 0x44, 0xd2, 0x82, 0x78, 0x68, 0xfe, 0xc0, 0x90, 0xa3, 0x43, 0x85, 0x36, 0x6c, 0xc7},
+ }
+
+ if !reflect.DeepEqual(descs, expectedDescs) {
+ t.Errorf("incorrect return, want %#v got %#v", expectedDescs, descs)
+ }
+}
diff --git a/cmd/symbols_map/r8.go b/cmd/symbols_map/r8.go
new file mode 100644
index 0000000..6f73e09
--- /dev/null
+++ b/cmd/symbols_map/r8.go
@@ -0,0 +1,56 @@
+// Copyright 2022 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 main
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+)
+
+const hashPrefix = "# pg_map_hash: "
+const hashTypePrefix = "SHA-256 "
+const commentPrefix = "#"
+
+// r8Identifier extracts the hash from the comments of a dictionary produced by R8. It returns
+// an empty identifier if no matching comment was found before the first non-comment line.
+func r8Identifier(filename string) (string, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return "", fmt.Errorf("failed to open %s: %w", filename, err)
+ }
+ defer f.Close()
+
+ return extractR8CompilerHash(f)
+}
+
+func extractR8CompilerHash(r io.Reader) (string, error) {
+ s := bufio.NewScanner(r)
+ for s.Scan() {
+ line := s.Text()
+ if strings.HasPrefix(line, hashPrefix) {
+ hash := strings.TrimPrefix(line, hashPrefix)
+ if !strings.HasPrefix(hash, hashTypePrefix) {
+ return "", fmt.Errorf("invalid hash type found in %q", line)
+ }
+ return strings.TrimPrefix(hash, hashTypePrefix), nil
+ } else if !strings.HasPrefix(line, commentPrefix) {
+ break
+ }
+ }
+ return "", nil
+}
diff --git a/cmd/symbols_map/r8_test.go b/cmd/symbols_map/r8_test.go
new file mode 100644
index 0000000..5712da9
--- /dev/null
+++ b/cmd/symbols_map/r8_test.go
@@ -0,0 +1,91 @@
+// Copyright 2022 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 main
+
+import (
+ "bytes"
+ "strings"
+ "testing"
+)
+
+func Test_extractR8CompilerHash(t *testing.T) {
+ testCases := []struct {
+ name string
+ data string
+
+ hash string
+ err string
+ }{
+ {
+ name: "simple",
+ data: `# compiler: R8
+# compiler_version: 3.3.18-dev
+# min_api: 10000
+# compiler_hash: bab44c1a04a2201b55fe10394f477994205c34e0
+# common_typos_disable
+# {"id":"com.android.tools.r8.mapping","version":"2.0"}
+# pg_map_id: 7fe8b95
+# pg_map_hash: SHA-256 7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da
+android.car.userlib.UserHelper -> android.car.userlib.UserHelper:
+`,
+ hash: "7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da",
+ },
+ {
+ name: "empty",
+ data: ``,
+ hash: "",
+ },
+ {
+ name: "non comment line",
+ data: `# compiler: R8
+# compiler_version: 3.3.18-dev
+# min_api: 10000
+# compiler_hash: bab44c1a04a2201b55fe10394f477994205c34e0
+# common_typos_disable
+# {"id":"com.android.tools.r8.mapping","version":"2.0"}
+# pg_map_id: 7fe8b95
+android.car.userlib.UserHelper -> android.car.userlib.UserHelper:
+# pg_map_hash: SHA-256 7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da
+`,
+ hash: "",
+ },
+ {
+ name: "invalid hash",
+ data: `# pg_map_hash: foobar 7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da`,
+ err: "invalid hash type",
+ },
+ }
+
+ for _, tt := range testCases {
+ t.Run(tt.name, func(t *testing.T) {
+ hash, err := extractR8CompilerHash(bytes.NewBufferString(tt.data))
+ if err != nil {
+ if tt.err != "" {
+ if !strings.Contains(err.Error(), tt.err) {
+ t.Fatalf("incorrect error in extractR8CompilerHash, want %s got %s", tt.err, err)
+ }
+ } else {
+ t.Fatalf("unexpected error in extractR8CompilerHash: %s", err)
+ }
+ } else if tt.err != "" {
+ t.Fatalf("missing error in extractR8CompilerHash, want %s", tt.err)
+ }
+
+ if g, w := hash, tt.hash; g != w {
+ t.Errorf("incorrect hash, want %q got %q", w, g)
+ }
+ })
+ }
+}
diff --git a/cmd/symbols_map/symbols_map.go b/cmd/symbols_map/symbols_map.go
new file mode 100644
index 0000000..938446d
--- /dev/null
+++ b/cmd/symbols_map/symbols_map.go
@@ -0,0 +1,202 @@
+// Copyright 2022 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 main
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "strings"
+
+ "android/soong/cmd/symbols_map/symbols_map_proto"
+ "android/soong/response"
+
+ "github.com/google/blueprint/pathtools"
+ "google.golang.org/protobuf/encoding/prototext"
+ "google.golang.org/protobuf/proto"
+)
+
+// This tool is used to extract a hash from an elf file or an r8 dictionary and store it as a
+// textproto, or to merge multiple textprotos together.
+
+func main() {
+ var expandedArgs []string
+ for _, arg := range os.Args[1:] {
+ if strings.HasPrefix(arg, "@") {
+ f, err := os.Open(strings.TrimPrefix(arg, "@"))
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err.Error())
+ os.Exit(1)
+ }
+
+ respArgs, err := response.ReadRspFile(f)
+ f.Close()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err.Error())
+ os.Exit(1)
+ }
+ expandedArgs = append(expandedArgs, respArgs...)
+ } else {
+ expandedArgs = append(expandedArgs, arg)
+ }
+ }
+
+ flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+ // Hide the flag package to prevent accidental references to flag instead of flags.
+ flag := struct{}{}
+ _ = flag
+
+ flags.Usage = func() {
+ fmt.Fprintf(flags.Output(), "Usage of %s:\n", os.Args[0])
+ fmt.Fprintf(flags.Output(), " %s -elf|-r8 <input file> [-write_if_changed] <output file>\n", os.Args[0])
+ fmt.Fprintf(flags.Output(), " %s -merge <output file> [-write_if_changed] [-ignore_missing_files] [-strip_prefix <prefix>] [<input file>...]\n", os.Args[0])
+ fmt.Fprintln(flags.Output())
+
+ flags.PrintDefaults()
+ }
+
+ elfFile := flags.String("elf", "", "extract identifier from an elf file")
+ r8File := flags.String("r8", "", "extract identifier from an r8 dictionary")
+ merge := flags.String("merge", "", "merge multiple identifier protos")
+
+ writeIfChanged := flags.Bool("write_if_changed", false, "only write output file if it is modified")
+ ignoreMissingFiles := flags.Bool("ignore_missing_files", false, "ignore missing input files in merge mode")
+ stripPrefix := flags.String("strip_prefix", "", "prefix to strip off of the location field in merge mode")
+
+ flags.Parse(expandedArgs)
+
+ if *merge != "" {
+ // If merge mode was requested perform the merge and exit early.
+ err := mergeProtos(*merge, flags.Args(), *stripPrefix, *writeIfChanged, *ignoreMissingFiles)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "failed to merge protos: %s", err)
+ os.Exit(1)
+ }
+ os.Exit(0)
+ }
+
+ if *elfFile == "" && *r8File == "" {
+ fmt.Fprintf(os.Stderr, "-elf or -r8 argument is required\n")
+ flags.Usage()
+ os.Exit(1)
+ }
+
+ if *elfFile != "" && *r8File != "" {
+ fmt.Fprintf(os.Stderr, "only one of -elf or -r8 argument is allowed\n")
+ flags.Usage()
+ os.Exit(1)
+ }
+
+ if flags.NArg() != 1 {
+ flags.Usage()
+ os.Exit(1)
+ }
+
+ output := flags.Arg(0)
+
+ var identifier string
+ var location string
+ var typ symbols_map_proto.Mapping_Type
+ var err error
+
+ if *elfFile != "" {
+ typ = symbols_map_proto.Mapping_ELF
+ location = *elfFile
+ identifier, err = elfIdentifier(*elfFile, true)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error reading elf identifier: %s\n", err)
+ os.Exit(1)
+ }
+ } else if *r8File != "" {
+ typ = symbols_map_proto.Mapping_R8
+ identifier, err = r8Identifier(*r8File)
+ location = *r8File
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error reading r8 identifier: %s\n", err)
+ os.Exit(1)
+ }
+ } else {
+ panic("shouldn't get here")
+ }
+
+ mapping := symbols_map_proto.Mapping{
+ Identifier: proto.String(identifier),
+ Location: proto.String(location),
+ Type: typ.Enum(),
+ }
+
+ err = writeTextProto(output, &mapping, *writeIfChanged)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error writing output: %s\n", err)
+ os.Exit(1)
+ }
+}
+
+// writeTextProto writes a proto to an output file as a textproto, optionally leaving the file
+// unmodified if it was already up to date.
+func writeTextProto(output string, message proto.Message, writeIfChanged bool) error {
+ marshaller := prototext.MarshalOptions{Multiline: true}
+ data, err := marshaller.Marshal(message)
+ if err != nil {
+ return fmt.Errorf("error marshalling textproto: %w", err)
+ }
+
+ if writeIfChanged {
+ err = pathtools.WriteFileIfChanged(output, data, 0666)
+ } else {
+ err = ioutil.WriteFile(output, data, 0666)
+ }
+
+ if err != nil {
+ return fmt.Errorf("error writing to %s: %w\n", output, err)
+ }
+
+ return nil
+}
+
+// mergeProtos merges a list of textproto files containing Mapping messages into a single textproto
+// containing a Mappings message.
+func mergeProtos(output string, inputs []string, stripPrefix string, writeIfChanged bool, ignoreMissingFiles bool) error {
+ mappings := symbols_map_proto.Mappings{}
+ for _, input := range inputs {
+ mapping := symbols_map_proto.Mapping{}
+ data, err := ioutil.ReadFile(input)
+ if err != nil {
+ if ignoreMissingFiles && os.IsNotExist(err) {
+ // Merge mode is used on a list of files in the packaging directory. If multiple
+ // goals are included on the build command line, for example `dist` and `tests`,
+ // then the symbols packaging rule for `dist` can run while a dependency of `tests`
+ // is modifying the symbols packaging directory. That can result in a file that
+ // existed when the file list was generated being deleted as part of updating it,
+ // resulting in sporadic ENOENT errors. Ignore them if -ignore_missing_files
+ // was passed on the command line.
+ continue
+ }
+ return fmt.Errorf("failed to read %s: %w", input, err)
+ }
+ err = prototext.Unmarshal(data, &mapping)
+ if err != nil {
+ return fmt.Errorf("failed to parse textproto %s: %w", input, err)
+ }
+ if stripPrefix != "" && mapping.Location != nil {
+ mapping.Location = proto.String(strings.TrimPrefix(*mapping.Location, stripPrefix))
+ }
+ mappings.Mappings = append(mappings.Mappings, &mapping)
+ }
+
+ return writeTextProto(output, &mappings, writeIfChanged)
+}
diff --git a/cmd/symbols_map/symbols_map_proto/symbols_map.pb.go b/cmd/symbols_map/symbols_map_proto/symbols_map.pb.go
new file mode 100644
index 0000000..f9c0ce5
--- /dev/null
+++ b/cmd/symbols_map/symbols_map_proto/symbols_map.pb.go
@@ -0,0 +1,315 @@
+// Copyright 2022 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.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.9.1
+// source: symbols_map.proto
+
+package symbols_map_proto
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// Type is the valid types of a mapping.
+type Mapping_Type int32
+
+const (
+ // ELF denotes a mapping from an elf build ID to an unstripped elf file.
+ Mapping_ELF Mapping_Type = 0
+ // R8 denotes a mapping from an R8 dictionary hash to an R8 dictionary.
+ Mapping_R8 Mapping_Type = 1
+)
+
+// Enum value maps for Mapping_Type.
+var (
+ Mapping_Type_name = map[int32]string{
+ 0: "ELF",
+ 1: "R8",
+ }
+ Mapping_Type_value = map[string]int32{
+ "ELF": 0,
+ "R8": 1,
+ }
+)
+
+func (x Mapping_Type) Enum() *Mapping_Type {
+ p := new(Mapping_Type)
+ *p = x
+ return p
+}
+
+func (x Mapping_Type) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Mapping_Type) Descriptor() protoreflect.EnumDescriptor {
+ return file_symbols_map_proto_enumTypes[0].Descriptor()
+}
+
+func (Mapping_Type) Type() protoreflect.EnumType {
+ return &file_symbols_map_proto_enumTypes[0]
+}
+
+func (x Mapping_Type) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Do not use.
+func (x *Mapping_Type) UnmarshalJSON(b []byte) error {
+ num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
+ if err != nil {
+ return err
+ }
+ *x = Mapping_Type(num)
+ return nil
+}
+
+// Deprecated: Use Mapping_Type.Descriptor instead.
+func (Mapping_Type) EnumDescriptor() ([]byte, []int) {
+ return file_symbols_map_proto_rawDescGZIP(), []int{0, 0}
+}
+
+type Mapping struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // identifier is a unique identifier of a location, generally the hash of the file. For an
+ // elf file it is the elf build ID, for an R8 dictionary it is the hash from the comments in the
+ // top of the file. It may be empty if no hash could be extracted from the file.
+ Identifier *string `protobuf:"bytes,1,opt,name=identifier" json:"identifier,omitempty"`
+ // location is the path to the file with the given identifier. The location should be valid
+ // both on the local disk and in the distributed symbols.zip or proguard_dict.zip files.
+ Location *string `protobuf:"bytes,2,opt,name=location" json:"location,omitempty"`
+ // type is the type of the mapping, either ELF or R8.
+ Type *Mapping_Type `protobuf:"varint,3,opt,name=type,enum=symbols_map.Mapping_Type" json:"type,omitempty"`
+}
+
+func (x *Mapping) Reset() {
+ *x = Mapping{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_symbols_map_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Mapping) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Mapping) ProtoMessage() {}
+
+func (x *Mapping) ProtoReflect() protoreflect.Message {
+ mi := &file_symbols_map_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Mapping.ProtoReflect.Descriptor instead.
+func (*Mapping) Descriptor() ([]byte, []int) {
+ return file_symbols_map_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Mapping) GetIdentifier() string {
+ if x != nil && x.Identifier != nil {
+ return *x.Identifier
+ }
+ return ""
+}
+
+func (x *Mapping) GetLocation() string {
+ if x != nil && x.Location != nil {
+ return *x.Location
+ }
+ return ""
+}
+
+func (x *Mapping) GetType() Mapping_Type {
+ if x != nil && x.Type != nil {
+ return *x.Type
+ }
+ return Mapping_ELF
+}
+
+type Mappings struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Mappings []*Mapping `protobuf:"bytes,4,rep,name=mappings" json:"mappings,omitempty"`
+}
+
+func (x *Mappings) Reset() {
+ *x = Mappings{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_symbols_map_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Mappings) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Mappings) ProtoMessage() {}
+
+func (x *Mappings) ProtoReflect() protoreflect.Message {
+ mi := &file_symbols_map_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Mappings.ProtoReflect.Descriptor instead.
+func (*Mappings) Descriptor() ([]byte, []int) {
+ return file_symbols_map_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *Mappings) GetMappings() []*Mapping {
+ if x != nil {
+ return x.Mappings
+ }
+ return nil
+}
+
+var File_symbols_map_proto protoreflect.FileDescriptor
+
+var file_symbols_map_proto_rawDesc = []byte{
+ 0x0a, 0x11, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x5f, 0x6d, 0x61, 0x70,
+ 0x22, 0x8d, 0x01, 0x0a, 0x07, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x1e, 0x0a, 0x0a,
+ 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08,
+ 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
+ 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
+ 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73,
+ 0x5f, 0x6d, 0x61, 0x70, 0x2e, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x54, 0x79, 0x70,
+ 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x17, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12,
+ 0x07, 0x0a, 0x03, 0x45, 0x4c, 0x46, 0x10, 0x00, 0x12, 0x06, 0x0a, 0x02, 0x52, 0x38, 0x10, 0x01,
+ 0x22, 0x3c, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x30, 0x0a, 0x08,
+ 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14,
+ 0x2e, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x2e, 0x4d, 0x61, 0x70,
+ 0x70, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x42, 0x31,
+ 0x5a, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f,
+ 0x63, 0x6d, 0x64, 0x2f, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x2f,
+ 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f,
+}
+
+var (
+ file_symbols_map_proto_rawDescOnce sync.Once
+ file_symbols_map_proto_rawDescData = file_symbols_map_proto_rawDesc
+)
+
+func file_symbols_map_proto_rawDescGZIP() []byte {
+ file_symbols_map_proto_rawDescOnce.Do(func() {
+ file_symbols_map_proto_rawDescData = protoimpl.X.CompressGZIP(file_symbols_map_proto_rawDescData)
+ })
+ return file_symbols_map_proto_rawDescData
+}
+
+var file_symbols_map_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_symbols_map_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_symbols_map_proto_goTypes = []interface{}{
+ (Mapping_Type)(0), // 0: symbols_map.Mapping.Type
+ (*Mapping)(nil), // 1: symbols_map.Mapping
+ (*Mappings)(nil), // 2: symbols_map.Mappings
+}
+var file_symbols_map_proto_depIdxs = []int32{
+ 0, // 0: symbols_map.Mapping.type:type_name -> symbols_map.Mapping.Type
+ 1, // 1: symbols_map.Mappings.mappings:type_name -> symbols_map.Mapping
+ 2, // [2:2] is the sub-list for method output_type
+ 2, // [2:2] is the sub-list for method input_type
+ 2, // [2:2] is the sub-list for extension type_name
+ 2, // [2:2] is the sub-list for extension extendee
+ 0, // [0:2] is the sub-list for field type_name
+}
+
+func init() { file_symbols_map_proto_init() }
+func file_symbols_map_proto_init() {
+ if File_symbols_map_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_symbols_map_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Mapping); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_symbols_map_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Mappings); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_symbols_map_proto_rawDesc,
+ NumEnums: 1,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_symbols_map_proto_goTypes,
+ DependencyIndexes: file_symbols_map_proto_depIdxs,
+ EnumInfos: file_symbols_map_proto_enumTypes,
+ MessageInfos: file_symbols_map_proto_msgTypes,
+ }.Build()
+ File_symbols_map_proto = out.File
+ file_symbols_map_proto_rawDesc = nil
+ file_symbols_map_proto_goTypes = nil
+ file_symbols_map_proto_depIdxs = nil
+}
diff --git a/cmd/symbols_map/symbols_map_proto/symbols_map.proto b/cmd/symbols_map/symbols_map_proto/symbols_map.proto
new file mode 100644
index 0000000..693fe3e
--- /dev/null
+++ b/cmd/symbols_map/symbols_map_proto/symbols_map.proto
@@ -0,0 +1,44 @@
+// Copyright 2022 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.
+
+syntax = "proto2";
+
+package symbols_map;
+option go_package = "android/soong/cmd/symbols_map/symbols_map_proto";
+
+message Mapping {
+ // identifier is a unique identifier of a location, generally the hash of the file. For an
+ // elf file it is the elf build ID, for an R8 dictionary it is the hash from the comments in the
+ // top of the file. It may be empty if no hash could be extracted from the file.
+ optional string identifier = 1;
+
+ // location is the path to the file with the given identifier. The location should be valid
+ // both on the local disk and in the distributed symbols.zip or proguard_dict.zip files.
+ optional string location = 2;
+
+ // Type is the valid types of a mapping.
+ enum Type {
+ // ELF denotes a mapping from an elf build ID to an unstripped elf file.
+ ELF = 0;
+ // R8 denotes a mapping from an R8 dictionary hash to an R8 dictionary.
+ R8 = 1;
+ }
+
+ // type is the type of the mapping, either ELF or R8.
+ optional Type type = 3;
+}
+
+message Mappings {
+ repeated Mapping mappings = 4;
+}
\ No newline at end of file
diff --git a/cmd/symbols_map/symbols_map_test.go b/cmd/symbols_map/symbols_map_test.go
new file mode 100644
index 0000000..754b7ef
--- /dev/null
+++ b/cmd/symbols_map/symbols_map_test.go
@@ -0,0 +1,217 @@
+// Copyright 2022 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 main
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "android/soong/cmd/symbols_map/symbols_map_proto"
+
+ "google.golang.org/protobuf/encoding/prototext"
+ "google.golang.org/protobuf/proto"
+)
+
+func Test_mergeProtos(t *testing.T) {
+ type testFile struct {
+ filename string
+ contents *symbols_map_proto.Mapping
+ missing bool
+ }
+
+ tests := []struct {
+ name string
+ inputs []testFile
+ stripPrefix string
+ writeIfChanged bool
+ ignoreMissingFiles bool
+
+ error string
+ output *symbols_map_proto.Mappings
+ }{
+ {
+ name: "empty",
+ output: &symbols_map_proto.Mappings{},
+ },
+ {
+ name: "merge",
+ inputs: []testFile{
+ {
+ filename: "foo",
+ contents: &symbols_map_proto.Mapping{
+ Identifier: proto.String("foo"),
+ Location: proto.String("symbols/foo"),
+ Type: symbols_map_proto.Mapping_ELF.Enum(),
+ },
+ },
+ {
+ filename: "bar",
+ contents: &symbols_map_proto.Mapping{
+ Identifier: proto.String("bar"),
+ Location: proto.String("symbols/bar"),
+ Type: symbols_map_proto.Mapping_R8.Enum(),
+ },
+ },
+ },
+ output: &symbols_map_proto.Mappings{
+ Mappings: []*symbols_map_proto.Mapping{
+ {
+ Identifier: proto.String("foo"),
+ Location: proto.String("symbols/foo"),
+ Type: symbols_map_proto.Mapping_ELF.Enum(),
+ },
+ {
+ Identifier: proto.String("bar"),
+ Location: proto.String("symbols/bar"),
+ Type: symbols_map_proto.Mapping_R8.Enum(),
+ },
+ },
+ },
+ },
+ {
+ name: "strip prefix",
+ inputs: []testFile{
+ {
+ filename: "foo",
+ contents: &symbols_map_proto.Mapping{
+ Identifier: proto.String("foo"),
+ Location: proto.String("symbols/foo"),
+ Type: symbols_map_proto.Mapping_ELF.Enum(),
+ },
+ },
+ {
+ filename: "bar",
+ contents: &symbols_map_proto.Mapping{
+ Identifier: proto.String("bar"),
+ Location: proto.String("symbols/bar"),
+ Type: symbols_map_proto.Mapping_R8.Enum(),
+ },
+ },
+ },
+ stripPrefix: "symbols/",
+ output: &symbols_map_proto.Mappings{
+ Mappings: []*symbols_map_proto.Mapping{
+ {
+ Identifier: proto.String("foo"),
+ Location: proto.String("foo"),
+ Type: symbols_map_proto.Mapping_ELF.Enum(),
+ },
+ {
+ Identifier: proto.String("bar"),
+ Location: proto.String("bar"),
+ Type: symbols_map_proto.Mapping_R8.Enum(),
+ },
+ },
+ },
+ },
+ {
+ name: "missing",
+ inputs: []testFile{
+ {
+ filename: "foo",
+ contents: &symbols_map_proto.Mapping{
+ Identifier: proto.String("foo"),
+ Location: proto.String("symbols/foo"),
+ Type: symbols_map_proto.Mapping_ELF.Enum(),
+ },
+ },
+ {
+ filename: "bar",
+ missing: true,
+ },
+ },
+ error: "no such file or directory",
+ },
+ {
+ name: "ignore missing",
+ inputs: []testFile{
+ {
+ filename: "foo",
+ contents: &symbols_map_proto.Mapping{
+ Identifier: proto.String("foo"),
+ Location: proto.String("symbols/foo"),
+ Type: symbols_map_proto.Mapping_ELF.Enum(),
+ },
+ },
+ {
+ filename: "bar",
+ missing: true,
+ },
+ },
+ ignoreMissingFiles: true,
+ output: &symbols_map_proto.Mappings{
+ Mappings: []*symbols_map_proto.Mapping{
+ {
+ Identifier: proto.String("foo"),
+ Location: proto.String("symbols/foo"),
+ Type: symbols_map_proto.Mapping_ELF.Enum(),
+ },
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ dir, err := os.MkdirTemp("", "test_mergeProtos")
+ if err != nil {
+ t.Fatalf("failed to create temporary directory: %s", err)
+ }
+ defer os.RemoveAll(dir)
+
+ var inputs []string
+ for _, in := range tt.inputs {
+ path := filepath.Join(dir, in.filename)
+ inputs = append(inputs, path)
+ if !in.missing {
+ err := writeTextProto(path, in.contents, false)
+ if err != nil {
+ t.Fatalf("failed to create input file %s: %s", path, err)
+ }
+ }
+ }
+ output := filepath.Join(dir, "out")
+
+ err = mergeProtos(output, inputs, tt.stripPrefix, tt.writeIfChanged, tt.ignoreMissingFiles)
+ if err != nil {
+ if tt.error != "" {
+ if !strings.Contains(err.Error(), tt.error) {
+ t.Fatalf("expected error %q, got %s", tt.error, err.Error())
+ }
+ } else {
+ t.Fatalf("unexpected error %q", err)
+ }
+ } else if tt.error != "" {
+ t.Fatalf("missing error %q", tt.error)
+ } else {
+ data, err := ioutil.ReadFile(output)
+ if err != nil {
+ t.Fatalf("failed to read output file %s: %s", output, err)
+ }
+ var got symbols_map_proto.Mappings
+ err = prototext.Unmarshal(data, &got)
+ if err != nil {
+ t.Fatalf("failed to unmarshal textproto %s: %s", output, err)
+ }
+
+ if !proto.Equal(tt.output, &got) {
+ t.Fatalf("expected output %q, got %q", tt.output.String(), got.String())
+ }
+ }
+ })
+ }
+}
diff --git a/java/Android.bp b/java/Android.bp
index 4bcae4f..df0d1eb 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -15,6 +15,7 @@
"soong-dexpreopt",
"soong-genrule",
"soong-java-config",
+ "soong-provenance",
"soong-python",
"soong-remoteexec",
"soong-tradefed",
diff --git a/java/androidmk.go b/java/androidmk.go
index b930441..80b828d 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -409,22 +409,6 @@
entries.SetOptionalPaths("LOCAL_SOONG_LINT_REPORTS", app.linter.reports)
},
},
- ExtraFooters: []android.AndroidMkExtraFootersFunc{
- func(w io.Writer, name, prefix, moduleDir string) {
- if app.noticeOutputs.Merged.Valid() {
- fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n",
- app.installApkName, app.noticeOutputs.Merged.String(), app.installApkName+"_NOTICE")
- }
- if app.noticeOutputs.TxtOutput.Valid() {
- fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n",
- app.installApkName, app.noticeOutputs.TxtOutput.String(), app.installApkName+"_NOTICE.txt")
- }
- if app.noticeOutputs.HtmlOutput.Valid() {
- fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n",
- app.installApkName, app.noticeOutputs.HtmlOutput.String(), app.installApkName+"_NOTICE.html")
- }
- },
- },
}}
}
diff --git a/java/app.go b/java/app.go
index 8728df6..21ee34e 100755
--- a/java/app.go
+++ b/java/app.go
@@ -19,7 +19,6 @@
import (
"path/filepath"
- "sort"
"strings"
"github.com/google/blueprint"
@@ -164,8 +163,6 @@
additionalAaptFlags []string
- noticeOutputs android.NoticeOutputs
-
overriddenManifestPackageName string
android.ApexBundleDepsInfo
@@ -523,53 +520,6 @@
return jniSymbols
}
-func (a *AndroidApp) noticeBuildActions(ctx android.ModuleContext) {
- // 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 {
- return false
- }
-
- paths := child.(android.Module).NoticeFiles()
- if len(paths) > 0 {
- for _, path := range paths {
- noticePathSet[path] = true
- }
- }
- return true
- })
-
- // If the app has one, add it too.
- if len(a.NoticeFiles()) > 0 {
- for _, path := range a.NoticeFiles() {
- noticePathSet[path] = true
- }
- }
-
- if len(noticePathSet) == 0 {
- return
- }
- 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()
- })
-
- a.noticeOutputs = android.BuildNoticeOutput(ctx, a.installDir, a.installApkName+".apk", noticePaths)
-}
-
// 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 {
@@ -636,9 +586,16 @@
}
a.onDeviceDir = android.InstallPathToOnDevicePath(ctx, a.installDir)
- a.noticeBuildActions(ctx)
if Bool(a.appProperties.Embed_notices) || ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") {
- a.aapt.noticeFile = a.noticeOutputs.HtmlGzOutput
+ 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)
diff --git a/java/app_import.go b/java/app_import.go
index 3e5f972..a1c4d58 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -22,6 +22,7 @@
"github.com/google/blueprint/proptools"
"android/soong/android"
+ "android/soong/provenance"
)
func init() {
@@ -57,6 +58,8 @@
installPath android.InstallPath
hideApexVariantFromMake bool
+
+ provenanceMetaDataFile android.OutputPath
}
type AndroidAppImportProperties struct {
@@ -343,6 +346,8 @@
if apexInfo.IsForPlatform() {
a.installPath = ctx.InstallFile(installDir, apkFilename, a.outputFile)
+ artifactPath := android.PathForModuleSrc(ctx, *a.properties.Apk)
+ a.provenanceMetaDataFile = provenance.GenerateArtifactProvenanceMetaData(ctx, artifactPath, a.installPath)
}
// TODO: androidmk converter jni libs
@@ -368,6 +373,10 @@
return a.certificate
}
+func (a *AndroidAppImport) ProvenanceMetaDataFile() android.OutputPath {
+ return a.provenanceMetaDataFile
+}
+
var dpiVariantGroupType reflect.Type
var archVariantGroupType reflect.Type
var supportedDpis = []string{"ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"}
diff --git a/java/app_import_test.go b/java/app_import_test.go
index efa52c1..8f6c75f 100644
--- a/java/app_import_test.go
+++ b/java/app_import_test.go
@@ -53,6 +53,11 @@
if expected != signingFlag {
t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
}
+ rule := variant.Rule("genProvenanceMetaData")
+ android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String())
+ android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String())
+ android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"])
+ android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"])
}
func TestAndroidAppImport_NoDexPreopt(t *testing.T) {
@@ -74,6 +79,12 @@
variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule != nil {
t.Errorf("dexpreopt shouldn't have run.")
}
+
+ rule := variant.Rule("genProvenanceMetaData")
+ android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String())
+ android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String())
+ android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"])
+ android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"])
}
func TestAndroidAppImport_Presigned(t *testing.T) {
@@ -102,6 +113,12 @@
if variant.MaybeOutput("zip-aligned/foo.apk").Rule == nil {
t.Errorf("can't find aligning rule")
}
+
+ rule := variant.Rule("genProvenanceMetaData")
+ android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String())
+ android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String())
+ android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"])
+ android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"])
}
func TestAndroidAppImport_SigningLineage(t *testing.T) {
@@ -137,6 +154,12 @@
if expected != signingFlag {
t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
}
+
+ rule := variant.Rule("genProvenanceMetaData")
+ android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String())
+ android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String())
+ android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"])
+ android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"])
}
func TestAndroidAppImport_SigningLineageFilegroup(t *testing.T) {
@@ -163,6 +186,12 @@
if expected != signingFlag {
t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
}
+
+ rule := variant.Rule("genProvenanceMetaData")
+ android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String())
+ android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String())
+ android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"])
+ android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"])
}
func TestAndroidAppImport_DefaultDevCert(t *testing.T) {
@@ -192,6 +221,12 @@
if expected != signingFlag {
t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
}
+
+ rule := variant.Rule("genProvenanceMetaData")
+ android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String())
+ android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String())
+ android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"])
+ android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"])
}
func TestAndroidAppImport_DpiVariants(t *testing.T) {
@@ -214,40 +249,46 @@
}
`
testCases := []struct {
- name string
- aaptPreferredConfig *string
- aaptPrebuiltDPI []string
- expected string
+ name string
+ aaptPreferredConfig *string
+ aaptPrebuiltDPI []string
+ expected string
+ expectedProvenanceMetaDataArtifactPath string
}{
{
- name: "no preferred",
- aaptPreferredConfig: nil,
- aaptPrebuiltDPI: []string{},
- expected: "verify_uses_libraries/apk/app.apk",
+ name: "no preferred",
+ aaptPreferredConfig: nil,
+ aaptPrebuiltDPI: []string{},
+ expected: "verify_uses_libraries/apk/app.apk",
+ expectedProvenanceMetaDataArtifactPath: "prebuilts/apk/app.apk",
},
{
- name: "AAPTPreferredConfig matches",
- aaptPreferredConfig: proptools.StringPtr("xhdpi"),
- aaptPrebuiltDPI: []string{"xxhdpi", "ldpi"},
- expected: "verify_uses_libraries/apk/app_xhdpi.apk",
+ name: "AAPTPreferredConfig matches",
+ aaptPreferredConfig: proptools.StringPtr("xhdpi"),
+ aaptPrebuiltDPI: []string{"xxhdpi", "ldpi"},
+ expected: "verify_uses_libraries/apk/app_xhdpi.apk",
+ expectedProvenanceMetaDataArtifactPath: "prebuilts/apk/app_xhdpi.apk",
},
{
- name: "AAPTPrebuiltDPI matches",
- aaptPreferredConfig: proptools.StringPtr("mdpi"),
- aaptPrebuiltDPI: []string{"xxhdpi", "xhdpi"},
- expected: "verify_uses_libraries/apk/app_xxhdpi.apk",
+ name: "AAPTPrebuiltDPI matches",
+ aaptPreferredConfig: proptools.StringPtr("mdpi"),
+ aaptPrebuiltDPI: []string{"xxhdpi", "xhdpi"},
+ expected: "verify_uses_libraries/apk/app_xxhdpi.apk",
+ expectedProvenanceMetaDataArtifactPath: "prebuilts/apk/app_xxhdpi.apk",
},
{
- name: "non-first AAPTPrebuiltDPI matches",
- aaptPreferredConfig: proptools.StringPtr("mdpi"),
- aaptPrebuiltDPI: []string{"ldpi", "xhdpi"},
- expected: "verify_uses_libraries/apk/app_xhdpi.apk",
+ name: "non-first AAPTPrebuiltDPI matches",
+ aaptPreferredConfig: proptools.StringPtr("mdpi"),
+ aaptPrebuiltDPI: []string{"ldpi", "xhdpi"},
+ expected: "verify_uses_libraries/apk/app_xhdpi.apk",
+ expectedProvenanceMetaDataArtifactPath: "prebuilts/apk/app_xhdpi.apk",
},
{
- name: "no matches",
- aaptPreferredConfig: proptools.StringPtr("mdpi"),
- aaptPrebuiltDPI: []string{"ldpi", "xxxhdpi"},
- expected: "verify_uses_libraries/apk/app.apk",
+ name: "no matches",
+ aaptPreferredConfig: proptools.StringPtr("mdpi"),
+ aaptPrebuiltDPI: []string{"ldpi", "xxxhdpi"},
+ expected: "verify_uses_libraries/apk/app.apk",
+ expectedProvenanceMetaDataArtifactPath: "prebuilts/apk/app.apk",
},
}
@@ -270,6 +311,12 @@
if strings.HasSuffix(matches[1], test.expected) {
t.Errorf("wrong src apk, expected: %q got: %q", test.expected, matches[1])
}
+
+ provenanceMetaDataRule := variant.Rule("genProvenanceMetaData")
+ android.AssertStringEquals(t, "Invalid input", test.expectedProvenanceMetaDataArtifactPath, provenanceMetaDataRule.Inputs[0].String())
+ android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", provenanceMetaDataRule.Output.String())
+ android.AssertStringEquals(t, "Invalid args", "foo", provenanceMetaDataRule.Args["module_name"])
+ android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", provenanceMetaDataRule.Args["install_path"])
}
}
@@ -290,16 +337,25 @@
`)
testCases := []struct {
- name string
- expected string
+ name string
+ expected string
+ onDevice string
+ expectedArtifactPath string
+ expectedMetaDataPath string
}{
{
- name: "foo",
- expected: "foo.apk",
+ name: "foo",
+ expected: "foo.apk",
+ onDevice: "/system/app/foo/foo.apk",
+ expectedArtifactPath: "prebuilts/apk/app.apk",
+ expectedMetaDataPath: "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto",
},
{
- name: "bar",
- expected: "bar_sample.apk",
+ name: "bar",
+ expected: "bar_sample.apk",
+ onDevice: "/system/app/bar/bar_sample.apk",
+ expectedArtifactPath: "prebuilts/apk/app.apk",
+ expectedMetaDataPath: "out/soong/.intermediates/provenance_metadata/bar/provenance_metadata.textproto",
},
}
@@ -316,15 +372,23 @@
t.Errorf("Incorrect LOCAL_INSTALLED_MODULE_STEM value '%s', expected '%s'",
actualValues, expectedValues)
}
+ rule := variant.Rule("genProvenanceMetaData")
+ android.AssertStringEquals(t, "Invalid input", test.expectedArtifactPath, rule.Inputs[0].String())
+ android.AssertStringEquals(t, "Invalid output", test.expectedMetaDataPath, rule.Output.String())
+ android.AssertStringEquals(t, "Invalid args", test.name, rule.Args["module_name"])
+ android.AssertStringEquals(t, "Invalid args", test.onDevice, rule.Args["install_path"])
}
}
func TestAndroidAppImport_ArchVariants(t *testing.T) {
// The test config's target arch is ARM64.
testCases := []struct {
- name string
- bp string
- expected string
+ name string
+ bp string
+ expected string
+ artifactPath string
+ metaDataPath string
+ installPath string
}{
{
name: "matching arch",
@@ -343,7 +407,9 @@
},
}
`,
- expected: "verify_uses_libraries/apk/app_arm64.apk",
+ expected: "verify_uses_libraries/apk/app_arm64.apk",
+ artifactPath: "prebuilts/apk/app_arm64.apk",
+ installPath: "/system/app/foo/foo.apk",
},
{
name: "no matching arch",
@@ -362,7 +428,9 @@
},
}
`,
- expected: "verify_uses_libraries/apk/app.apk",
+ expected: "verify_uses_libraries/apk/app.apk",
+ artifactPath: "prebuilts/apk/app.apk",
+ installPath: "/system/app/foo/foo.apk",
},
{
name: "no matching arch without default",
@@ -380,7 +448,9 @@
},
}
`,
- expected: "",
+ expected: "",
+ artifactPath: "prebuilts/apk/app_arm.apk",
+ installPath: "/system/app/foo/foo.apk",
},
}
@@ -393,6 +463,8 @@
if variant.Module().Enabled() {
t.Error("module should have been disabled, but wasn't")
}
+ rule := variant.MaybeRule("genProvenanceMetaData")
+ android.AssertDeepEquals(t, "Provenance metadata is not empty", android.TestingBuildParams{}, rule)
continue
}
jniRuleCommand := variant.Output("jnis-uncompressed/foo.apk").RuleParams.Command
@@ -403,6 +475,11 @@
if strings.HasSuffix(matches[1], test.expected) {
t.Errorf("wrong src apk, expected: %q got: %q", test.expected, matches[1])
}
+ rule := variant.Rule("genProvenanceMetaData")
+ android.AssertStringEquals(t, "Invalid input", test.artifactPath, rule.Inputs[0].String())
+ android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String())
+ android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"])
+ android.AssertStringEquals(t, "Invalid args", test.installPath, rule.Args["install_path"])
}
}
diff --git a/java/app_test.go b/java/app_test.go
index 16bbec1..08baf54 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -27,7 +27,6 @@
"android/soong/android"
"android/soong/cc"
"android/soong/dexpreopt"
- "android/soong/genrule"
)
// testApp runs tests using the prepareForJavaTest
@@ -2722,116 +2721,6 @@
}
}
-func TestEmbedNotice(t *testing.T) {
- result := android.GroupFixturePreparers(
- PrepareForTestWithJavaDefaultModules,
- cc.PrepareForTestWithCcDefaultModules,
- genrule.PrepareForTestWithGenRuleBuildComponents,
- android.MockFS{
- "APP_NOTICE": nil,
- "GENRULE_NOTICE": nil,
- "LIB_NOTICE": nil,
- "TOOL_NOTICE": nil,
- }.AddToFixture(),
- ).RunTestWithBp(t, `
- android_app {
- name: "foo",
- srcs: ["a.java"],
- static_libs: ["javalib"],
- jni_libs: ["libjni"],
- notice: "APP_NOTICE",
- embed_notices: true,
- sdk_version: "current",
- }
-
- // No embed_notice flag
- android_app {
- name: "bar",
- srcs: ["a.java"],
- jni_libs: ["libjni"],
- notice: "APP_NOTICE",
- sdk_version: "current",
- }
-
- // No NOTICE files
- android_app {
- name: "baz",
- srcs: ["a.java"],
- embed_notices: true,
- sdk_version: "current",
- }
-
- cc_library {
- name: "libjni",
- system_shared_libs: [],
- stl: "none",
- notice: "LIB_NOTICE",
- sdk_version: "current",
- }
-
- java_library {
- name: "javalib",
- srcs: [
- ":gen",
- ],
- sdk_version: "current",
- }
-
- 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 := result.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 <NOTICE dir> so that its contents are put in the APK's /assets.
- res := foo.Output("package-res.apk")
- aapt2Flags := res.Args["flags"]
- e := "-A out/soong/.intermediates/foo/android_common/NOTICE"
- android.AssertStringDoesContain(t, "expected.apkPath", aapt2Flags, e)
-
- // bar has NOTICE files to process, but embed_notices is not set.
- bar := result.ModuleForTests("bar", "android_common")
- res = bar.Output("package-res.apk")
- aapt2Flags = res.Args["flags"]
- e = "-A out/soong/.intermediates/bar/android_common/NOTICE"
- android.AssertStringDoesNotContain(t, "bar shouldn't have the asset dir flag for NOTICE", aapt2Flags, e)
-
- // baz's embed_notice is true, but it doesn't have any NOTICE files.
- baz := result.ModuleForTests("baz", "android_common")
- res = baz.Output("package-res.apk")
- aapt2Flags = res.Args["flags"]
- e = "-A out/soong/.intermediates/baz/android_common/NOTICE"
- if strings.Contains(aapt2Flags, e) {
- t.Errorf("baz shouldn't have the asset dir flag for NOTICE: %q", e)
- }
-}
-
func TestUncompressDex(t *testing.T) {
testCases := []struct {
name string
diff --git a/java/config/config.go b/java/config/config.go
index 05dfde6..262c531 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -50,11 +50,6 @@
"core-icu4j",
"core-oj",
"core-libart",
- // TODO: Could this be all updatable bootclasspath jars?
- "updatable-media",
- "framework-mediaprovider",
- "framework-sdkextensions",
- "android.net.ipsec.ike",
}
)
diff --git a/java/droidstubs.go b/java/droidstubs.go
index c5b56f7..3b1f7c0 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -481,7 +481,7 @@
Flag("--format=v2").
FlagWithArg("--repeat-errors-max ", "10").
FlagWithArg("--hide ", "UnresolvedImport").
- FlagWithArg("--hide ", "InvalidNullability").
+ FlagWithArg("--hide ", "InvalidNullabilityOverride").
// b/223382732
FlagWithArg("--hide ", "ChangedDefault")
diff --git a/java/java.go b/java/java.go
index 1b6e559..713fe94 100644
--- a/java/java.go
+++ b/java/java.go
@@ -2060,15 +2060,22 @@
protoSrcPartition: android.ProtoSrcLabelPartition,
})
+ var javacopts []string
+ if m.properties.Javacflags != nil {
+ javacopts = append(javacopts, m.properties.Javacflags...)
+ }
+ epEnabled := m.properties.Errorprone.Enabled
+ //TODO(b/227504307) add configuration that depends on RUN_ERROR_PRONE environment variable
+ if Bool(epEnabled) {
+ javacopts = append(javacopts, m.properties.Errorprone.Javacflags...)
+ }
+
commonAttrs := &javaCommonAttributes{
Srcs: srcPartitions[javaSrcPartition],
Plugins: bazel.MakeLabelListAttribute(
android.BazelLabelForModuleDeps(ctx, m.properties.Plugins),
),
- }
-
- if m.properties.Javacflags != nil {
- commonAttrs.Javacopts = bazel.MakeStringListAttribute(m.properties.Javacflags)
+ Javacopts: bazel.MakeStringListAttribute(javacopts),
}
depLabels := &javaDependencyLabels{}
@@ -2218,8 +2225,16 @@
// java_import bp2Build converter.
func (i *Import) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
- //TODO(b/209577426): Support multiple arch variants
- jars := bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrcExcludes(ctx, i.properties.Jars, []string(nil)))
+ var jars bazel.LabelListAttribute
+ archVariantProps := i.GetArchVariantProperties(ctx, &ImportProperties{})
+ for axis, configToProps := range archVariantProps {
+ for config, _props := range configToProps {
+ if archProps, ok := _props.(*ImportProperties); ok {
+ archJars := android.BazelLabelForModuleSrcExcludes(ctx, archProps.Jars, []string(nil))
+ jars.SetSelectValue(axis, config, archJars)
+ }
+ }
+ }
attrs := &bazelJavaImportAttributes{
Jars: jars,
diff --git a/mk2rbc/expr.go b/mk2rbc/expr.go
index dc16d1d..54bb6d1 100644
--- a/mk2rbc/expr.go
+++ b/mk2rbc/expr.go
@@ -221,11 +221,9 @@
}
func (xi *interpolateExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
- argsCopy := make([]starlarkExpr, len(xi.args))
- for i, arg := range xi.args {
- argsCopy[i] = arg.transform(transformer)
+ for i := range xi.args {
+ xi.args[i] = xi.args[i].transform(transformer)
}
- xi.args = argsCopy
if replacement := transformer(xi); replacement != nil {
return replacement
} else {
@@ -591,11 +589,9 @@
if cx.object != nil {
cx.object = cx.object.transform(transformer)
}
- argsCopy := make([]starlarkExpr, len(cx.args))
- for i, arg := range cx.args {
- argsCopy[i] = arg.transform(transformer)
+ for i := range cx.args {
+ cx.args[i] = cx.args[i].transform(transformer)
}
- cx.args = argsCopy
if replacement := transformer(cx); replacement != nil {
return replacement
} else {
@@ -769,3 +765,35 @@
x, ok := expr.(*stringLiteralExpr)
return ok && x.literal == ""
}
+
+func negateExpr(expr starlarkExpr) starlarkExpr {
+ switch typedExpr := expr.(type) {
+ case *notExpr:
+ return typedExpr.expr
+ case *inExpr:
+ typedExpr.isNot = !typedExpr.isNot
+ return typedExpr
+ case *eqExpr:
+ typedExpr.isEq = !typedExpr.isEq
+ return typedExpr
+ case *binaryOpExpr:
+ switch typedExpr.op {
+ case ">":
+ typedExpr.op = "<="
+ return typedExpr
+ case "<":
+ typedExpr.op = ">="
+ return typedExpr
+ case ">=":
+ typedExpr.op = "<"
+ return typedExpr
+ case "<=":
+ typedExpr.op = ">"
+ return typedExpr
+ default:
+ return ¬Expr{expr: expr}
+ }
+ default:
+ return ¬Expr{expr: expr}
+ }
+}
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
index c881751..8807437 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -77,7 +77,7 @@
"addprefix": &simpleCallParser{name: baseName + ".addprefix", returnType: starlarkTypeList},
"addsuffix": &simpleCallParser{name: baseName + ".addsuffix", returnType: starlarkTypeList},
"copy-files": &simpleCallParser{name: baseName + ".copy_files", returnType: starlarkTypeList},
- "dir": &simpleCallParser{name: baseName + ".dir", returnType: starlarkTypeList},
+ "dir": &simpleCallParser{name: baseName + ".dir", returnType: starlarkTypeString},
"dist-for-goals": &simpleCallParser{name: baseName + ".mkdist_for_goals", returnType: starlarkTypeVoid, addGlobals: true},
"enforce-product-packages-exist": &simpleCallParser{name: baseName + ".enforce_product_packages_exist", returnType: starlarkTypeVoid},
"error": &makeControlFuncParser{name: baseName + ".mkerror"},
@@ -86,7 +86,7 @@
"filter": &simpleCallParser{name: baseName + ".filter", returnType: starlarkTypeList},
"filter-out": &simpleCallParser{name: baseName + ".filter_out", returnType: starlarkTypeList},
"firstword": &firstOrLastwordCallParser{isLastWord: false},
- "foreach": &foreachCallPaser{},
+ "foreach": &foreachCallParser{},
"if": &ifCallParser{},
"info": &makeControlFuncParser{name: baseName + ".mkinfo"},
"is-board-platform": &simpleCallParser{name: baseName + ".board_platform_is", returnType: starlarkTypeBool, addGlobals: true},
@@ -117,6 +117,17 @@
"wildcard": &simpleCallParser{name: baseName + ".expand_wildcard", returnType: starlarkTypeList},
}
+// The same as knownFunctions, but returns a []starlarkNode instead of a starlarkExpr
+var knownNodeFunctions = map[string]interface {
+ parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode
+}{
+ "eval": &evalNodeParser{},
+ "if": &ifCallNodeParser{},
+ "inherit-product": &inheritProductCallParser{loadAlways: true},
+ "inherit-product-if-exists": &inheritProductCallParser{loadAlways: false},
+ "foreach": &foreachCallNodeParser{},
+}
+
// These are functions that we don't implement conversions for, but
// we allow seeing their definitions in the product config files.
var ignoredDefines = map[string]bool{
@@ -846,15 +857,19 @@
return res
}
-func (ctx *parseContext) handleInheritModule(v mkparser.Node, args *mkparser.MakeString, loadAlways bool) []starlarkNode {
+type inheritProductCallParser struct {
+ loadAlways bool
+}
+
+func (p *inheritProductCallParser) parse(ctx *parseContext, v mkparser.Node, args *mkparser.MakeString) []starlarkNode {
args.TrimLeftSpaces()
args.TrimRightSpaces()
pathExpr := ctx.parseMakeString(v, args)
if _, ok := pathExpr.(*badExpr); ok {
return []starlarkNode{ctx.newBadNode(v, "Unable to parse argument to inherit")}
}
- return ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) starlarkNode {
- return &inheritNode{im, loadAlways}
+ return ctx.handleSubConfig(v, pathExpr, p.loadAlways, func(im inheritedModule) starlarkNode {
+ return &inheritNode{im, p.loadAlways}
})
}
@@ -873,19 +888,12 @@
// $(error xxx)
// $(call other-custom-functions,...)
- // inherit-product(-if-exists) gets converted to a series of statements,
- // not just a single expression like parseReference returns. So handle it
- // separately at the beginning here.
- if strings.HasPrefix(v.Name.Dump(), "call inherit-product,") {
- args := v.Name.Clone()
- args.ReplaceLiteral("call inherit-product,", "")
- return ctx.handleInheritModule(v, args, true)
+ if name, args, ok := ctx.maybeParseFunctionCall(v, v.Name); ok {
+ if kf, ok := knownNodeFunctions[name]; ok {
+ return kf.parse(ctx, v, args)
+ }
}
- if strings.HasPrefix(v.Name.Dump(), "call inherit-product-if-exists,") {
- args := v.Name.Clone()
- args.ReplaceLiteral("call inherit-product-if-exists,", "")
- return ctx.handleInheritModule(v, args, false)
- }
+
return []starlarkNode{&exprNode{expr: ctx.parseReference(v, v.Name)}}
}
@@ -1030,49 +1038,19 @@
otherOperand = xLeft
}
- not := func(expr starlarkExpr) starlarkExpr {
- switch typedExpr := expr.(type) {
- case *inExpr:
- typedExpr.isNot = !typedExpr.isNot
- return typedExpr
- case *eqExpr:
- typedExpr.isEq = !typedExpr.isEq
- return typedExpr
- case *binaryOpExpr:
- switch typedExpr.op {
- case ">":
- typedExpr.op = "<="
- return typedExpr
- case "<":
- typedExpr.op = ">="
- return typedExpr
- case ">=":
- typedExpr.op = "<"
- return typedExpr
- case "<=":
- typedExpr.op = ">"
- return typedExpr
- default:
- return ¬Expr{expr: expr}
- }
- default:
- return ¬Expr{expr: expr}
- }
- }
-
// If we've identified one of the operands as being a string literal, check
// for some special cases we can do to simplify the resulting expression.
if otherOperand != nil {
if stringOperand == "" {
if isEq {
- return not(otherOperand)
+ return negateExpr(otherOperand)
} else {
return otherOperand
}
}
if stringOperand == "true" && otherOperand.typ() == starlarkTypeBool {
if !isEq {
- return not(otherOperand)
+ return negateExpr(otherOperand)
} else {
return otherOperand
}
@@ -1228,6 +1206,37 @@
right: xValue, isEq: !negate}
}
+func (ctx *parseContext) maybeParseFunctionCall(node mkparser.Node, ref *mkparser.MakeString) (name string, args *mkparser.MakeString, ok bool) {
+ ref.TrimLeftSpaces()
+ ref.TrimRightSpaces()
+
+ words := ref.SplitN(" ", 2)
+ if !words[0].Const() {
+ return "", nil, false
+ }
+
+ name = words[0].Dump()
+ args = mkparser.SimpleMakeString("", words[0].Pos())
+ if len(words) >= 2 {
+ args = words[1]
+ }
+ args.TrimLeftSpaces()
+ if name == "call" {
+ words = args.SplitN(",", 2)
+ if words[0].Empty() || !words[0].Const() {
+ return "", nil, false
+ }
+ name = words[0].Dump()
+ if len(words) < 2 {
+ args = &mkparser.MakeString{}
+ } else {
+ args = words[1]
+ }
+ }
+ ok = true
+ return
+}
+
// parses $(...), returning an expression
func (ctx *parseContext) parseReference(node mkparser.Node, ref *mkparser.MakeString) starlarkExpr {
ref.TrimLeftSpaces()
@@ -1242,7 +1251,7 @@
// If it is a single word, it can be a simple variable
// reference or a function call
- if len(words) == 1 && !isMakeControlFunc(refDump) && refDump != "shell" {
+ if len(words) == 1 && !isMakeControlFunc(refDump) && refDump != "shell" && refDump != "eval" {
if strings.HasPrefix(refDump, soongNsPrefix) {
// TODO (asmundak): if we find many, maybe handle them.
return ctx.newBadExpr(node, "SOONG_CONFIG_ variables cannot be referenced, use soong_config_get instead: %s", refDump)
@@ -1281,28 +1290,14 @@
return ctx.newBadExpr(node, "unknown variable %s", refDump)
}
- expr := &callExpr{name: words[0].Dump(), returnType: starlarkTypeUnknown}
- args := mkparser.SimpleMakeString("", words[0].Pos())
- if len(words) >= 2 {
- args = words[1]
- }
- args.TrimLeftSpaces()
- if expr.name == "call" {
- words = args.SplitN(",", 2)
- if words[0].Empty() || !words[0].Const() {
- return ctx.newBadExpr(node, "cannot handle %s", refDump)
- }
- expr.name = words[0].Dump()
- if len(words) < 2 {
- args = &mkparser.MakeString{}
+ if name, args, ok := ctx.maybeParseFunctionCall(node, ref); ok {
+ if kf, found := knownFunctions[name]; found {
+ return kf.parse(ctx, node, args)
} else {
- args = words[1]
+ return ctx.newBadExpr(node, "cannot handle invoking %s", name)
}
- }
- if kf, found := knownFunctions[expr.name]; found {
- return kf.parse(ctx, node, args)
} else {
- return ctx.newBadExpr(node, "cannot handle invoking %s", expr.name)
+ return ctx.newBadExpr(node, "cannot handle %s", refDump)
}
}
@@ -1486,9 +1481,46 @@
}
}
-type foreachCallPaser struct{}
+type ifCallNodeParser struct{}
-func (p *foreachCallPaser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+func (p *ifCallNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
+ words := args.Split(",")
+ if len(words) != 2 && len(words) != 3 {
+ return []starlarkNode{ctx.newBadNode(node, "if function should have 2 or 3 arguments, found "+strconv.Itoa(len(words)))}
+ }
+
+ ifn := &ifNode{expr: ctx.parseMakeString(node, words[0])}
+ cases := []*switchCase{
+ {
+ gate: ifn,
+ nodes: ctx.parseNodeMakeString(node, words[1]),
+ },
+ }
+ if len(words) == 3 {
+ cases = append(cases, &switchCase{
+ gate: &elseNode{},
+ nodes: ctx.parseNodeMakeString(node, words[2]),
+ })
+ }
+ if len(cases) == 2 {
+ if len(cases[1].nodes) == 0 {
+ // Remove else branch if it has no contents
+ cases = cases[:1]
+ } else if len(cases[0].nodes) == 0 {
+ // If the if branch has no contents but the else does,
+ // move them to the if and negate its condition
+ ifn.expr = negateExpr(ifn.expr)
+ cases[0].nodes = cases[1].nodes
+ cases = cases[:1]
+ }
+ }
+
+ return []starlarkNode{&switchNode{ssCases: cases}}
+}
+
+type foreachCallParser struct{}
+
+func (p *foreachCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
words := args.Split(",")
if len(words) != 3 {
return ctx.newBadExpr(node, "foreach function should have 3 arguments, found "+strconv.Itoa(len(words)))
@@ -1520,6 +1552,71 @@
}
}
+func transformNode(node starlarkNode, transformer func(expr starlarkExpr) starlarkExpr) {
+ switch a := node.(type) {
+ case *ifNode:
+ a.expr = a.expr.transform(transformer)
+ case *switchCase:
+ transformNode(a.gate, transformer)
+ for _, n := range a.nodes {
+ transformNode(n, transformer)
+ }
+ case *switchNode:
+ for _, n := range a.ssCases {
+ transformNode(n, transformer)
+ }
+ case *exprNode:
+ a.expr = a.expr.transform(transformer)
+ case *assignmentNode:
+ a.value = a.value.transform(transformer)
+ case *foreachNode:
+ a.list = a.list.transform(transformer)
+ for _, n := range a.actions {
+ transformNode(n, transformer)
+ }
+ }
+}
+
+type foreachCallNodeParser struct{}
+
+func (p *foreachCallNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
+ words := args.Split(",")
+ if len(words) != 3 {
+ return []starlarkNode{ctx.newBadNode(node, "foreach function should have 3 arguments, found "+strconv.Itoa(len(words)))}
+ }
+ if !words[0].Const() || words[0].Empty() || !identifierFullMatchRegex.MatchString(words[0].Strings[0]) {
+ return []starlarkNode{ctx.newBadNode(node, "first argument to foreach function must be a simple string identifier")}
+ }
+
+ loopVarName := words[0].Strings[0]
+
+ list := ctx.parseMakeString(node, words[1])
+ if list.typ() != starlarkTypeList {
+ list = &callExpr{
+ name: baseName + ".words",
+ returnType: starlarkTypeList,
+ args: []starlarkExpr{list},
+ }
+ }
+
+ actions := ctx.parseNodeMakeString(node, words[2])
+ // TODO(colefaust): Replace transforming code with something more elegant
+ for _, action := range actions {
+ transformNode(action, func(expr starlarkExpr) starlarkExpr {
+ if varRefExpr, ok := expr.(*variableRefExpr); ok && varRefExpr.ref.name() == loopVarName {
+ return &identifierExpr{loopVarName}
+ }
+ return nil
+ })
+ }
+
+ return []starlarkNode{&foreachNode{
+ varName: loopVarName,
+ list: list,
+ actions: actions,
+ }}
+}
+
type wordCallParser struct{}
func (p *wordCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
@@ -1630,6 +1727,31 @@
}
}
+type evalNodeParser struct{}
+
+func (p *evalNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
+ parser := mkparser.NewParser("Eval expression", strings.NewReader(args.Dump()))
+ nodes, errs := parser.Parse()
+ if errs != nil {
+ return []starlarkNode{ctx.newBadNode(node, "Unable to parse eval statement")}
+ }
+
+ if len(nodes) == 0 {
+ return []starlarkNode{}
+ } else if len(nodes) == 1 {
+ switch n := nodes[0].(type) {
+ case *mkparser.Assignment:
+ if n.Name.Const() {
+ return ctx.handleAssignment(n)
+ }
+ case *mkparser.Comment:
+ return []starlarkNode{&commentNode{strings.TrimSpace("#" + n.Comment)}}
+ }
+ }
+
+ return []starlarkNode{ctx.newBadNode(node, "Eval expression too complex; only assignments and comments are supported")}
+}
+
func (ctx *parseContext) parseMakeString(node mkparser.Node, mk *mkparser.MakeString) starlarkExpr {
if mk.Const() {
return &stringLiteralExpr{mk.Dump()}
@@ -1654,6 +1776,16 @@
return NewInterpolateExpr(parts)
}
+func (ctx *parseContext) parseNodeMakeString(node mkparser.Node, mk *mkparser.MakeString) []starlarkNode {
+ // Discard any constant values in the make string, as they would be top level
+ // string literals and do nothing.
+ result := make([]starlarkNode, 0, len(mk.Variables))
+ for i := range mk.Variables {
+ result = append(result, ctx.handleVariable(&mk.Variables[i])...)
+ }
+ return result
+}
+
// Handles the statements whose treatment is the same in all contexts: comment,
// assignment, variable (which is a macro call in reality) and all constructs that
// do not handle in any context ('define directive and any unrecognized stuff).
@@ -1698,6 +1830,7 @@
if result == nil {
result = []starlarkNode{}
}
+
return result
}
diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go
index 31739fa..3698813 100644
--- a/mk2rbc/mk2rbc_test.go
+++ b/mk2rbc/mk2rbc_test.go
@@ -793,7 +793,7 @@
PRODUCT_COPY_FILES := $(addsuffix .sff, a b c)
PRODUCT_NAME := $(word 1, $(subst ., ,$(TARGET_BOARD_PLATFORM)))
$(info $(patsubst %.pub,$(PRODUCT_NAME)%,$(PRODUCT_ADB_KEYS)))
-$(info $(dir foo/bar))
+$(info $$(dir foo/bar): $(dir foo/bar))
$(info $(firstword $(PRODUCT_COPY_FILES)))
$(info $(lastword $(PRODUCT_COPY_FILES)))
$(info $(dir $(lastword $(MAKEFILE_LIST))))
@@ -816,7 +816,7 @@
cfg["PRODUCT_COPY_FILES"] = rblf.addsuffix(".sff", "a b c")
cfg["PRODUCT_NAME"] = ((g.get("TARGET_BOARD_PLATFORM", "")).replace(".", " ")).split()[0]
rblf.mkinfo("product.mk", rblf.mkpatsubst("%.pub", "%s%%" % cfg["PRODUCT_NAME"], g.get("PRODUCT_ADB_KEYS", "")))
- rblf.mkinfo("product.mk", rblf.dir("foo/bar"))
+ rblf.mkinfo("product.mk", "$(dir foo/bar): %s" % rblf.dir("foo/bar"))
rblf.mkinfo("product.mk", cfg["PRODUCT_COPY_FILES"][0])
rblf.mkinfo("product.mk", cfg["PRODUCT_COPY_FILES"][-1])
rblf.mkinfo("product.mk", rblf.dir("product.mk"))
@@ -1313,6 +1313,11 @@
FOREACH_WITH_IF := $(foreach module,\
$(BOOT_KERNEL_MODULES_LIST),\
$(if $(filter $(module),foo.ko),,$(error module "$(module)" has an error!)))
+
+# Same as above, but not assigning it to a variable allows it to be converted to statements
+$(foreach module,\
+ $(BOOT_KERNEL_MODULES_LIST),\
+ $(if $(filter $(module),foo.ko),,$(error module "$(module)" has an error!)))
`,
expected: `load("//build/make/core:product_config.rbc", "rblf")
@@ -1324,6 +1329,10 @@
g["BOOT_KERNEL_MODULES_LIST"] += ["bar.ko"]
g["BOOT_KERNEL_MODULES_FILTER_2"] = ["%%/%s" % m for m in g["BOOT_KERNEL_MODULES_LIST"]]
g["FOREACH_WITH_IF"] = [("" if rblf.filter(module, "foo.ko") else rblf.mkerror("product.mk", "module \"%s\" has an error!" % module)) for module in g["BOOT_KERNEL_MODULES_LIST"]]
+ # Same as above, but not assigning it to a variable allows it to be converted to statements
+ for module in g["BOOT_KERNEL_MODULES_LIST"]:
+ if not rblf.filter(module, "foo.ko"):
+ rblf.mkerror("product.mk", "module \"%s\" has an error!" % module)
`,
},
{
@@ -1474,6 +1483,34 @@
`,
},
+ {
+ desc: "Evals",
+ mkname: "product.mk",
+ in: `
+$(eval)
+$(eval MY_VAR := foo)
+$(eval # This is a test of eval functions)
+$(eval $(TOO_COMPLICATED) := bar)
+$(foreach x,$(MY_LIST_VAR), \
+ $(eval PRODUCT_COPY_FILES += foo/bar/$(x):$(TARGET_COPY_OUT_VENDOR)/etc/$(x)) \
+ $(if $(MY_OTHER_VAR),$(eval PRODUCT_COPY_FILES += $(MY_OTHER_VAR):foo/bar/$(x))) \
+)
+
+`,
+ expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+ cfg = rblf.cfg(handle)
+ g["MY_VAR"] = "foo"
+ # This is a test of eval functions
+ rblf.mk2rbc_error("product.mk:5", "Eval expression too complex; only assignments and comments are supported")
+ for x in rblf.words(g.get("MY_LIST_VAR", "")):
+ rblf.setdefault(handle, "PRODUCT_COPY_FILES")
+ cfg["PRODUCT_COPY_FILES"] += ("foo/bar/%s:%s/etc/%s" % (x, g.get("TARGET_COPY_OUT_VENDOR", ""), x)).split()
+ if g.get("MY_OTHER_VAR", ""):
+ cfg["PRODUCT_COPY_FILES"] += ("%s:foo/bar/%s" % (g.get("MY_OTHER_VAR", ""), x)).split()
+`,
+ },
}
var known_variables = []struct {
diff --git a/mk2rbc/node.go b/mk2rbc/node.go
index 9d5af91..c0c4c98 100644
--- a/mk2rbc/node.go
+++ b/mk2rbc/node.go
@@ -294,3 +294,28 @@
ssCase.emit(gctx)
}
}
+
+type foreachNode struct {
+ varName string
+ list starlarkExpr
+ actions []starlarkNode
+}
+
+func (f *foreachNode) emit(gctx *generationContext) {
+ gctx.newLine()
+ gctx.writef("for %s in ", f.varName)
+ f.list.emit(gctx)
+ gctx.write(":")
+ gctx.indentLevel++
+ hasStatements := false
+ for _, a := range f.actions {
+ if _, ok := a.(*commentNode); !ok {
+ hasStatements = true
+ }
+ a.emit(gctx)
+ }
+ if !hasStatements {
+ gctx.emitPass()
+ }
+ gctx.indentLevel--
+}
diff --git a/provenance/Android.bp b/provenance/Android.bp
new file mode 100644
index 0000000..6fd67aa
--- /dev/null
+++ b/provenance/Android.bp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+ name: "soong-provenance",
+ pkgPath: "android/soong/provenance",
+ srcs: [
+ "provenance_singleton.go",
+ ],
+ deps: [
+ "soong-android",
+ ],
+ testSrcs: [
+ "provenance_singleton_test.go",
+ ],
+ pluginFor: [
+ "soong_build",
+ ],
+}
diff --git a/provenance/provenance_metadata_proto/Android.bp b/provenance/provenance_metadata_proto/Android.bp
new file mode 100644
index 0000000..7fc47a9
--- /dev/null
+++ b/provenance/provenance_metadata_proto/Android.bp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_library_host {
+ name: "provenance_metadata_proto",
+ version: {
+ py3: {
+ enabled: true,
+ },
+ },
+ srcs: [
+ "provenance_metadata.proto",
+ ],
+ proto: {
+ canonical_path_from_root: false,
+ },
+}
diff --git a/provenance/provenance_metadata_proto/provenance_metadata.proto b/provenance/provenance_metadata_proto/provenance_metadata.proto
new file mode 100644
index 0000000..f42aba7
--- /dev/null
+++ b/provenance/provenance_metadata_proto/provenance_metadata.proto
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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.
+ */
+
+syntax = "proto3";
+
+package provenance_metadata_proto;
+option go_package = "android/soong/provenance/provenance_metadata_proto";
+
+// Provenance metadata of artifacts.
+message ProvenanceMetadata {
+ // Name of the module/target that creates the artifact.
+ // It is either a Soong module name or Bazel target label.
+ string module_name = 1;
+
+ // The path to the prebuilt artifacts, which is relative to the source tree
+ // directory. For example, “prebuilts/runtime/mainline/i18n/apex/com.android.i18n-arm.apex”.
+ string artifact_path = 2;
+
+ // The SHA256 hash of the artifact.
+ string artifact_sha256 = 3;
+
+ // The install path of the artifact in filesystem images.
+ // This is the absolute path of the artifact on the device.
+ string artifact_install_path = 4;
+
+ // Path of the attestation file of a prebuilt artifact, which is relative to
+ // the source tree directory. This is for prebuilt artifacts which have
+ // corresponding attestation files checked in the source tree.
+ string attestation_path = 5;
+}
+
+message ProvenanceMetaDataList {
+ repeated ProvenanceMetadata metadata = 1;
+}
\ No newline at end of file
diff --git a/provenance/provenance_singleton.go b/provenance/provenance_singleton.go
new file mode 100644
index 0000000..ae96e1f
--- /dev/null
+++ b/provenance/provenance_singleton.go
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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 provenance
+
+import (
+ "android/soong/android"
+ "github.com/google/blueprint"
+)
+
+var (
+ pctx = android.NewPackageContext("android/soong/provenance")
+ rule = pctx.HostBinToolVariable("gen_provenance_metadata", "gen_provenance_metadata")
+
+ genProvenanceMetaData = pctx.AndroidStaticRule("genProvenanceMetaData",
+ blueprint.RuleParams{
+ Command: `rm -rf "$out" && ` +
+ `${gen_provenance_metadata} --module_name=${module_name} ` +
+ `--artifact_path=$in --install_path=${install_path} --metadata_path=$out`,
+ CommandDeps: []string{"${gen_provenance_metadata}"},
+ }, "module_name", "install_path")
+
+ mergeProvenanceMetaData = pctx.AndroidStaticRule("mergeProvenanceMetaData",
+ blueprint.RuleParams{
+ Command: `rm -rf $out $out.temp && ` +
+ `echo -e "# proto-file: build/soong/provenance/proto/provenance_metadata.proto\n# proto-message: ProvenanceMetaDataList" > $out && ` +
+ `touch $out.temp && cat $out.temp $in | grep -v "^#.*" >> $out && rm -rf $out.temp`,
+ })
+)
+
+type ProvenanceMetadata interface {
+ ProvenanceMetaDataFile() android.OutputPath
+}
+
+func init() {
+ RegisterProvenanceSingleton(android.InitRegistrationContext)
+}
+
+func RegisterProvenanceSingleton(ctx android.RegistrationContext) {
+ ctx.RegisterSingletonType("provenance_metadata_singleton", provenanceInfoSingletonFactory)
+}
+
+var PrepareForTestWithProvenanceSingleton = android.FixtureRegisterWithContext(RegisterProvenanceSingleton)
+
+func provenanceInfoSingletonFactory() android.Singleton {
+ return &provenanceInfoSingleton{}
+}
+
+type provenanceInfoSingleton struct {
+}
+
+func (b *provenanceInfoSingleton) GenerateBuildActions(context android.SingletonContext) {
+ allMetaDataFiles := make([]android.Path, 0)
+ context.VisitAllModulesIf(moduleFilter, func(module android.Module) {
+ if p, ok := module.(ProvenanceMetadata); ok {
+ allMetaDataFiles = append(allMetaDataFiles, p.ProvenanceMetaDataFile())
+ }
+ })
+ mergedMetaDataFile := android.PathForOutput(context, "provenance_metadata.textproto")
+ context.Build(pctx, android.BuildParams{
+ Rule: mergeProvenanceMetaData,
+ Description: "merge provenance metadata",
+ Inputs: allMetaDataFiles,
+ Output: mergedMetaDataFile,
+ })
+
+ context.Build(pctx, android.BuildParams{
+ Rule: blueprint.Phony,
+ Description: "phony rule of merge provenance metadata",
+ Inputs: []android.Path{mergedMetaDataFile},
+ Output: android.PathForPhony(context, "provenance_metadata"),
+ })
+}
+
+func moduleFilter(module android.Module) bool {
+ if !module.Enabled() || module.IsSkipInstall() {
+ return false
+ }
+ if p, ok := module.(ProvenanceMetadata); ok {
+ return p.ProvenanceMetaDataFile().String() != ""
+ }
+ return false
+}
+
+func GenerateArtifactProvenanceMetaData(ctx android.ModuleContext, artifactPath android.Path, installedFile android.InstallPath) android.OutputPath {
+ onDevicePathOfInstalledFile := android.InstallPathToOnDevicePath(ctx, installedFile)
+ artifactMetaDataFile := android.PathForIntermediates(ctx, "provenance_metadata", ctx.ModuleDir(), ctx.ModuleName(), "provenance_metadata.textproto")
+ ctx.Build(pctx, android.BuildParams{
+ Rule: genProvenanceMetaData,
+ Description: "generate artifact provenance metadata",
+ Inputs: []android.Path{artifactPath},
+ Output: artifactMetaDataFile,
+ Args: map[string]string{
+ "module_name": ctx.ModuleName(),
+ "install_path": onDevicePathOfInstalledFile,
+ }})
+
+ return artifactMetaDataFile
+}
diff --git a/provenance/provenance_singleton_test.go b/provenance/provenance_singleton_test.go
new file mode 100644
index 0000000..0f1eae2
--- /dev/null
+++ b/provenance/provenance_singleton_test.go
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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 provenance
+
+import (
+ "strings"
+ "testing"
+
+ "android/soong/android"
+)
+
+func TestProvenanceSingleton(t *testing.T) {
+ result := android.GroupFixturePreparers(
+ PrepareForTestWithProvenanceSingleton,
+ android.PrepareForTestWithAndroidMk).RunTestWithBp(t, "")
+
+ outputs := result.SingletonForTests("provenance_metadata_singleton").AllOutputs()
+ for _, output := range outputs {
+ testingBuildParam := result.SingletonForTests("provenance_metadata_singleton").Output(output)
+ switch {
+ case strings.Contains(output, "soong/provenance_metadata.textproto"):
+ android.AssertStringEquals(t, "Invalid build rule", "android/soong/provenance.mergeProvenanceMetaData", testingBuildParam.Rule.String())
+ android.AssertIntEquals(t, "Invalid input", len(testingBuildParam.Inputs), 0)
+ android.AssertStringDoesContain(t, "Invalid output path", output, "soong/provenance_metadata.textproto")
+ android.AssertIntEquals(t, "Invalid args", len(testingBuildParam.Args), 0)
+
+ case strings.HasSuffix(output, "provenance_metadata"):
+ android.AssertStringEquals(t, "Invalid build rule", "<builtin>:phony", testingBuildParam.Rule.String())
+ android.AssertStringEquals(t, "Invalid input", testingBuildParam.Inputs[0].String(), "out/soong/provenance_metadata.textproto")
+ android.AssertStringEquals(t, "Invalid output path", output, "provenance_metadata")
+ android.AssertIntEquals(t, "Invalid args", len(testingBuildParam.Args), 0)
+ }
+ }
+}
diff --git a/provenance/tools/Android.bp b/provenance/tools/Android.bp
new file mode 100644
index 0000000..1f959bb
--- /dev/null
+++ b/provenance/tools/Android.bp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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.
+ */
+
+python_binary_host {
+ name: "gen_provenance_metadata",
+ srcs: [
+ "gen_provenance_metadata.py",
+ ],
+ version: {
+ py3: {
+ embedded_launcher: true,
+ },
+ },
+ libs: [
+ "provenance_metadata_proto",
+ "libprotobuf-python",
+ ],
+}
+
+python_test_host {
+ name: "gen_provenance_metadata_test",
+ main: "gen_provenance_metadata_test.py",
+ srcs: [
+ "gen_provenance_metadata_test.py",
+ ],
+ data: [
+ ":gen_provenance_metadata",
+ ],
+ libs: [
+ "provenance_metadata_proto",
+ "libprotobuf-python",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/provenance/tools/gen_provenance_metadata.py b/provenance/tools/gen_provenance_metadata.py
new file mode 100644
index 0000000..b33f911
--- /dev/null
+++ b/provenance/tools/gen_provenance_metadata.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# 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.
+
+import argparse
+import hashlib
+import sys
+
+import google.protobuf.text_format as text_format
+import provenance_metadata_pb2
+
+def Log(*info):
+ if args.verbose:
+ for i in info:
+ print(i)
+
+def ParseArgs(argv):
+ parser = argparse.ArgumentParser(description='Create provenance metadata for a prebuilt artifact')
+ parser.add_argument('-v', '--verbose', action='store_true', help='Print more information in execution')
+ parser.add_argument('--module_name', help='Module name', required=True)
+ parser.add_argument('--artifact_path', help='Relative path of the prebuilt artifact in source tree', required=True)
+ parser.add_argument('--install_path', help='Absolute path of the artifact in the filesystem images', required=True)
+ parser.add_argument('--metadata_path', help='Path of the provenance metadata file created for the artifact', required=True)
+ return parser.parse_args(argv)
+
+def main(argv):
+ global args
+ args = ParseArgs(argv)
+ Log("Args:", vars(args))
+
+ provenance_metadata = provenance_metadata_pb2.ProvenanceMetadata()
+ provenance_metadata.module_name = args.module_name
+ provenance_metadata.artifact_path = args.artifact_path
+ provenance_metadata.artifact_install_path = args.install_path
+
+ Log("Generating SHA256 hash")
+ h = hashlib.sha256()
+ with open(args.artifact_path, "rb") as artifact_file:
+ h.update(artifact_file.read())
+ provenance_metadata.artifact_sha256 = h.hexdigest()
+
+ text_proto = [
+ "# proto-file: build/soong/provenance/proto/provenance_metadata.proto",
+ "# proto-message: ProvenanceMetaData",
+ "",
+ text_format.MessageToString(provenance_metadata)
+ ]
+ with open(args.metadata_path, "wt") as metadata_file:
+ file_content = "\n".join(text_proto)
+ Log("Writing provenance metadata in textproto:", file_content)
+ metadata_file.write(file_content)
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/provenance/tools/gen_provenance_metadata_test.py b/provenance/tools/gen_provenance_metadata_test.py
new file mode 100644
index 0000000..2fc04bf
--- /dev/null
+++ b/provenance/tools/gen_provenance_metadata_test.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# 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.
+
+import hashlib
+import logging
+import os
+import subprocess
+import tempfile
+import unittest
+
+import google.protobuf.text_format as text_format
+import provenance_metadata_pb2
+
+logger = logging.getLogger(__name__)
+
+def run(args, verbose=None, **kwargs):
+ """Creates and returns a subprocess.Popen object.
+
+ Args:
+ args: The command represented as a list of strings.
+ verbose: Whether the commands should be shown. Default to the global
+ verbosity if unspecified.
+ kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
+ stdin, etc. stdout and stderr will default to subprocess.PIPE and
+ subprocess.STDOUT respectively unless caller specifies any of them.
+ universal_newlines will default to True, as most of the users in
+ releasetools expect string output.
+
+ Returns:
+ A subprocess.Popen object.
+ """
+ if 'stdout' not in kwargs and 'stderr' not in kwargs:
+ kwargs['stdout'] = subprocess.PIPE
+ kwargs['stderr'] = subprocess.STDOUT
+ if 'universal_newlines' not in kwargs:
+ kwargs['universal_newlines'] = True
+ if verbose:
+ logger.info(" Running: \"%s\"", " ".join(args))
+ return subprocess.Popen(args, **kwargs)
+
+
+def run_and_check_output(args, verbose=None, **kwargs):
+ """Runs the given command and returns the output.
+
+ Args:
+ args: The command represented as a list of strings.
+ verbose: Whether the commands should be shown. Default to the global
+ verbosity if unspecified.
+ kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
+ stdin, etc. stdout and stderr will default to subprocess.PIPE and
+ subprocess.STDOUT respectively unless caller specifies any of them.
+
+ Returns:
+ The output string.
+
+ Raises:
+ ExternalError: On non-zero exit from the command.
+ """
+ proc = run(args, verbose=verbose, **kwargs)
+ output, _ = proc.communicate()
+ if output is None:
+ output = ""
+ if verbose:
+ logger.info("%s", output.rstrip())
+ if proc.returncode != 0:
+ raise RuntimeError(
+ "Failed to run command '{}' (exit code {}):\n{}".format(
+ args, proc.returncode, output))
+ return output
+
+def run_host_command(args, verbose=None, **kwargs):
+ host_build_top = os.environ.get("ANDROID_BUILD_TOP")
+ if host_build_top:
+ host_command_dir = os.path.join(host_build_top, "out/host/linux-x86/bin")
+ args[0] = os.path.join(host_command_dir, args[0])
+ return run_and_check_output(args, verbose, **kwargs)
+
+def sha256(s):
+ h = hashlib.sha256()
+ h.update(bytearray(s, 'utf-8'))
+ return h.hexdigest()
+
+class ProvenanceMetaDataToolTest(unittest.TestCase):
+
+ def test_gen_provenance_metadata(self):
+ artifact_content = "test artifact"
+ artifact_file = tempfile.mktemp()
+ with open(artifact_file,"wt") as f:
+ f.write(artifact_content)
+ metadata_file = tempfile.mktemp()
+ cmd = ["gen_provenance_metadata"]
+ cmd.extend(["--module_name", "a"])
+ cmd.extend(["--artifact_path", artifact_file])
+ cmd.extend(["--install_path", "b"])
+ cmd.extend(["--metadata_path", metadata_file])
+ output = run_host_command(cmd)
+ self.assertEqual(output, "")
+
+ with open(metadata_file,"rt") as f:
+ data = f.read()
+ provenance_metadata = provenance_metadata_pb2.ProvenanceMetadata()
+ text_format.Parse(data, provenance_metadata)
+ self.assertEqual(provenance_metadata.module_name, "a")
+ self.assertEqual(provenance_metadata.artifact_path, artifact_file)
+ self.assertEqual(provenance_metadata.artifact_install_path, "b")
+ self.assertEqual(provenance_metadata.artifact_sha256, sha256(artifact_content))
+
+ os.remove(artifact_file)
+ os.remove(metadata_file)
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
\ No newline at end of file
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index bc36b20..802e1da 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -26,7 +26,9 @@
"packages/modules/Virtualization",
"platform_testing/tests/codecoverage/native/rust",
"prebuilts/rust",
+ "system/core/debuggerd/rust",
"system/core/libstats/pull_rust",
+ "system/core/trusty/libtrusty-rs",
"system/extras/profcollectd",
"system/extras/simpleperf",
"system/hardware/interfaces/keystore2",
diff --git a/scripts/generate-notice-files.py b/scripts/generate-notice-files.py
deleted file mode 100755
index 1b4acfa..0000000
--- a/scripts/generate-notice-files.py
+++ /dev/null
@@ -1,272 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2012 The Android Open Source Project
-#
-# 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.
-"""
-Usage: generate-notice-files --text-output [plain text output file] \
- --html-output [html output file] \
- --xml-output [xml output file] \
- -t [file title] -s [directory of notices]
-
-Generate the Android notice files, including both text and html files.
-
--h to display this usage message and exit.
-"""
-from collections import defaultdict
-import argparse
-import hashlib
-import itertools
-import os
-import os.path
-import re
-import struct
-import sys
-
-MD5_BLOCKSIZE = 1024 * 1024
-HTML_ESCAPE_TABLE = {
- b"&": b"&",
- b'"': b""",
- b"'": b"'",
- b">": b">",
- b"<": b"<",
- }
-
-def md5sum(filename):
- """Calculate an MD5 of the file given by FILENAME,
- and return hex digest as a string.
- Output should be compatible with md5sum command"""
-
- f = open(filename, "rb")
- sum = hashlib.md5()
- while 1:
- block = f.read(MD5_BLOCKSIZE)
- if not block:
- break
- sum.update(block)
- f.close()
- return sum.hexdigest()
-
-
-def html_escape(text):
- """Produce entities within text."""
- # Using for i in text doesn't work since i will be an int, not a byte.
- # There are multiple ways to solve this, but the most performant way
- # to iterate over a byte array is to use unpack. Using the
- # for i in range(len(text)) and using that to get a byte using array
- # slices is twice as slow as this method.
- return b"".join(HTML_ESCAPE_TABLE.get(i,i) for i in struct.unpack(str(len(text)) + 'c', text))
-
-HTML_OUTPUT_CSS=b"""
-<style type="text/css">
-body { padding: 0; font-family: sans-serif; }
-.same-license { background-color: #eeeeee; border-top: 20px solid white; padding: 10px; }
-.label { font-weight: bold; }
-.file-list { margin-left: 1em; color: blue; }
-</style>
-
-"""
-
-def combine_notice_files_html(file_hash, input_dir, output_filename):
- """Combine notice files in FILE_HASH and output a HTML version to OUTPUT_FILENAME."""
-
- SRC_DIR_STRIP_RE = re.compile(input_dir + "(/.*).txt")
-
- # Set up a filename to row id table (anchors inside tables don't work in
- # most browsers, but href's to table row ids do)
- id_table = {}
- id_count = 0
- for value in file_hash:
- for filename in value:
- id_table[filename] = id_count
- id_count += 1
-
- # Open the output file, and output the header pieces
- output_file = open(output_filename, "wb")
-
- output_file.write(b"<html><head>\n")
- output_file.write(HTML_OUTPUT_CSS)
- output_file.write(b'</head><body topmargin="0" leftmargin="0" rightmargin="0" bottommargin="0">\n')
-
- # Output our table of contents
- output_file.write(b'<div class="toc">\n')
- output_file.write(b"<ul>\n")
-
- # Flatten the list of lists into a single list of filenames
- sorted_filenames = sorted(itertools.chain.from_iterable(file_hash))
-
- # Print out a nice table of contents
- for filename in sorted_filenames:
- stripped_filename = SRC_DIR_STRIP_RE.sub(r"\1", filename)
- output_file.write(('<li><a href="#id%d">%s</a></li>\n' % (id_table.get(filename), stripped_filename)).encode())
-
- output_file.write(b"</ul>\n")
- output_file.write(b"</div><!-- table of contents -->\n")
- # Output the individual notice file lists
- output_file.write(b'<table cellpadding="0" cellspacing="0" border="0">\n')
- for value in file_hash:
- output_file.write(('<tr id="id%d"><td class="same-license">\n' % id_table.get(value[0])).encode())
- output_file.write(b'<div class="label">Notices for file(s):</div>\n')
- output_file.write(b'<div class="file-list">\n')
- for filename in value:
- output_file.write(("%s <br/>\n" % (SRC_DIR_STRIP_RE.sub(r"\1", filename))).encode())
- output_file.write(b"</div><!-- file-list -->\n\n")
- output_file.write(b'<pre class="license-text">\n')
- with open(value[0], "rb") as notice_file:
- output_file.write(html_escape(notice_file.read()))
- output_file.write(b"\n</pre><!-- license-text -->\n")
- output_file.write(b"</td></tr><!-- same-license -->\n\n\n\n")
-
- # Finish off the file output
- output_file.write(b"</table>\n")
- output_file.write(b"</body></html>\n")
- output_file.close()
-
-def combine_notice_files_text(file_hash, input_dir, output_filename, file_title):
- """Combine notice files in FILE_HASH and output a text version to OUTPUT_FILENAME."""
-
- SRC_DIR_STRIP_RE = re.compile(input_dir + "(/.*).txt")
- output_file = open(output_filename, "wb")
- output_file.write(file_title.encode())
- output_file.write(b"\n")
- for value in file_hash:
- output_file.write(b"============================================================\n")
- output_file.write(b"Notices for file(s):\n")
- for filename in value:
- output_file.write(SRC_DIR_STRIP_RE.sub(r"\1", filename).encode())
- output_file.write(b"\n")
- output_file.write(b"------------------------------------------------------------\n")
- with open(value[0], "rb") as notice_file:
- output_file.write(notice_file.read())
- output_file.write(b"\n")
- output_file.close()
-
-def combine_notice_files_xml(files_with_same_hash, input_dir, output_filename):
- """Combine notice files in FILE_HASH and output a XML version to OUTPUT_FILENAME."""
-
- SRC_DIR_STRIP_RE = re.compile(input_dir + "(/.*).txt")
-
- # Set up a filename to row id table (anchors inside tables don't work in
- # most browsers, but href's to table row ids do)
- id_table = {}
- for file_key, files in files_with_same_hash.items():
- for filename in files:
- id_table[filename] = file_key
-
- # Open the output file, and output the header pieces
- output_file = open(output_filename, "wb")
-
- output_file.write(b'<?xml version="1.0" encoding="utf-8"?>\n')
- output_file.write(b"<licenses>\n")
-
- # Flatten the list of lists into a single list of filenames
- sorted_filenames = sorted(list(id_table))
-
- # Print out a nice table of contents
- for filename in sorted_filenames:
- stripped_filename = SRC_DIR_STRIP_RE.sub(r"\1", filename)
- output_file.write(('<file-name contentId="%s">%s</file-name>\n' % (id_table.get(filename), stripped_filename)).encode())
- output_file.write(b"\n\n")
-
- processed_file_keys = []
- # Output the individual notice file lists
- for filename in sorted_filenames:
- file_key = id_table.get(filename)
- if file_key in processed_file_keys:
- continue
- processed_file_keys.append(file_key)
-
- output_file.write(('<file-content contentId="%s"><![CDATA[' % file_key).encode())
- with open(filename, "rb") as notice_file:
- output_file.write(html_escape(notice_file.read()))
- output_file.write(b"]]></file-content>\n\n")
-
- # Finish off the file output
- output_file.write(b"</licenses>\n")
- output_file.close()
-
-def get_args():
- parser = argparse.ArgumentParser()
- parser.add_argument(
- '--text-output', required=True,
- help='The text output file path.')
- parser.add_argument(
- '--html-output',
- help='The html output file path.')
- parser.add_argument(
- '--xml-output',
- help='The xml output file path.')
- parser.add_argument(
- '-t', '--title', required=True,
- help='The file title.')
- parser.add_argument(
- '-s', '--source-dir', required=True,
- help='The directory containing notices.')
- parser.add_argument(
- '-i', '--included-subdirs', action='append',
- help='The sub directories which should be included.')
- parser.add_argument(
- '-e', '--excluded-subdirs', action='append',
- help='The sub directories which should be excluded.')
- return parser.parse_args()
-
-def main(argv):
- args = get_args()
-
- txt_output_file = args.text_output
- html_output_file = args.html_output
- xml_output_file = args.xml_output
- file_title = args.title
- included_subdirs = []
- excluded_subdirs = []
- if args.included_subdirs is not None:
- included_subdirs = args.included_subdirs
- if args.excluded_subdirs is not None:
- excluded_subdirs = args.excluded_subdirs
-
- # Find all the notice files and md5 them
- input_dir = os.path.normpath(args.source_dir)
- files_with_same_hash = defaultdict(list)
- for root, dir, files in os.walk(input_dir):
- for file in files:
- matched = True
- if len(included_subdirs) > 0:
- matched = False
- for subdir in included_subdirs:
- if (root == (input_dir + '/' + subdir) or
- root.startswith(input_dir + '/' + subdir + '/')):
- matched = True
- break
- elif len(excluded_subdirs) > 0:
- for subdir in excluded_subdirs:
- if (root == (input_dir + '/' + subdir) or
- root.startswith(input_dir + '/' + subdir + '/')):
- matched = False
- break
- if matched and file.endswith(".txt"):
- filename = os.path.join(root, file)
- file_md5sum = md5sum(filename)
- files_with_same_hash[file_md5sum].append(filename)
-
- filesets = [sorted(files_with_same_hash[md5]) for md5 in sorted(list(files_with_same_hash))]
-
- combine_notice_files_text(filesets, input_dir, txt_output_file, file_title)
-
- if html_output_file is not None:
- combine_notice_files_html(filesets, input_dir, html_output_file)
-
- if xml_output_file is not None:
- combine_notice_files_xml(files_with_same_hash, input_dir, xml_output_file)
-
-if __name__ == "__main__":
- main(sys.argv)
diff --git a/scripts/hiddenapi/analyze_bcpf.py b/scripts/hiddenapi/analyze_bcpf.py
index 1ad8d07..595343b 100644
--- a/scripts/hiddenapi/analyze_bcpf.py
+++ b/scripts/hiddenapi/analyze_bcpf.py
@@ -319,12 +319,48 @@
@dataclasses.dataclass()
+class PackagePropertyReason:
+ """Provides the reasons why a package was added to a specific property.
+
+ A split package is one that contains classes from the bootclasspath_fragment
+ and other bootclasspath modules. So, for a split package this contains the
+ corresponding lists of classes.
+
+ A single package is one that contains classes sub-packages from the
+ For a split package this contains a list of classes in that package that are
+ provided by the bootclasspath_fragment and a list of classes
+ """
+
+ # The list of classes/sub-packages that is provided by the
+ # bootclasspath_fragment.
+ bcpf: typing.List[str]
+
+ # The list of classes/sub-packages that is provided by other modules on the
+ # bootclasspath.
+ other: typing.List[str]
+
+
+@dataclasses.dataclass()
class Result:
"""Encapsulates the result of the analysis."""
# The diffs in the flags.
diffs: typing.Optional[FlagDiffs] = None
+ # A map from package name to the reason why it belongs in the
+ # split_packages property.
+ split_packages: typing.Dict[str, PackagePropertyReason] = dataclasses.field(
+ default_factory=dict)
+
+ # A map from package name to the reason why it belongs in the
+ # single_packages property.
+ single_packages: typing.Dict[str,
+ PackagePropertyReason] = dataclasses.field(
+ default_factory=dict)
+
+ # The list of packages to add to the package_prefixes property.
+ package_prefixes: typing.List[str] = dataclasses.field(default_factory=list)
+
# The bootclasspath_fragment hidden API properties changes.
property_changes: typing.List[HiddenApiPropertyChange] = dataclasses.field(
default_factory=list)
@@ -394,13 +430,17 @@
def reformat_report_test(text):
return re.sub(r"(.)\n([^\s])", r"\1 \2", text)
- def report(self, text, **kwargs):
+ def report(self, text="", **kwargs):
# Concatenate lines that are not separated by a blank line together to
# eliminate formatting applied to the supplied text to adhere to python
# line length limitations.
text = self.reformat_report_test(text)
logging.info("%s", text, **kwargs)
+ def report_dedent(self, text, **kwargs):
+ text = textwrap.dedent(text)
+ self.report(text, **kwargs)
+
def run_command(self, cmd, *args, **kwargs):
cmd_line = " ".join(cmd)
logging.debug("Running %s", cmd_line)
@@ -442,9 +482,7 @@
def load_module_info(self):
module_info_file = os.path.join(self.product_out_dir,
"module-info.json")
- self.report(f"""
-Making sure that {module_info_file} is up to date.
-""")
+ self.report(f"\nMaking sure that {module_info_file} is up to date.\n")
output = self.build_file_read_output(module_info_file)
lines = output.lines()
for line in lines:
@@ -496,61 +534,62 @@
optimizations that can be applied.
"""
self.report(f"Analyzing bootclasspath_fragment module {self.bcpf}")
- self.report(f"""
-Run this tool to help initialize a bootclasspath_fragment module. Before you
-start make sure that:
+ self.report_dedent(f"""
+ Run this tool to help initialize a bootclasspath_fragment module.
+ Before you start make sure that:
-1. The current checkout is up to date.
+ 1. The current checkout is up to date.
-2. The environment has been initialized using lunch, e.g.
- lunch aosp_arm64-userdebug
+ 2. The environment has been initialized using lunch, e.g.
+ lunch aosp_arm64-userdebug
-3. You have added a bootclasspath_fragment module to the appropriate Android.bp
-file. Something like this:
+ 3. You have added a bootclasspath_fragment module to the appropriate
+ Android.bp file. Something like this:
- bootclasspath_fragment {{
- name: "{self.bcpf}",
- contents: [
- "...",
- ],
+ bootclasspath_fragment {{
+ name: "{self.bcpf}",
+ contents: [
+ "...",
+ ],
+
+ // The bootclasspath_fragments that provide APIs on which this
+ // depends.
+ fragments: [
+ {{
+ apex: "com.android.art",
+ module: "art-bootclasspath-fragment",
+ }},
+ ],
+ }}
+
+ 4. You have added it to the platform_bootclasspath module in
+ frameworks/base/boot/Android.bp. Something like this:
- // The bootclasspath_fragments that provide APIs on which this depends.
- fragments: [
- {{
- apex: "com.android.art",
- module: "art-bootclasspath-fragment",
- }},
- ],
- }}
+ platform_bootclasspath {{
+ name: "platform-bootclasspath",
+ fragments: [
+ ...
+ {{
+ apex: "{self.apex}",
+ module: "{self.bcpf}",
+ }},
+ ],
+ }}
-4. You have added it to the platform_bootclasspath module in
-frameworks/base/boot/Android.bp. Something like this:
+ 5. You have added an sdk module. Something like this:
- platform_bootclasspath {{
- name: "platform-bootclasspath",
- fragments: [
- ...
- {{
- apex: "{self.apex}",
- module: "{self.bcpf}",
- }},
- ],
- }}
-
-5. You have added an sdk module. Something like this:
-
- sdk {{
- name: "{self.sdk}",
- bootclasspath_fragments: ["{self.bcpf}"],
- }}
-""")
+ sdk {{
+ name: "{self.sdk}",
+ bootclasspath_fragments: ["{self.bcpf}"],
+ }}
+ """)
# Make sure that the module-info.json file is up to date.
self.load_module_info()
- self.report("""
-Cleaning potentially stale files.
-""")
+ self.report_dedent("""
+ Cleaning potentially stale files.
+ """)
# Remove the out/soong/hiddenapi files.
shutil.rmtree(f"{self.out_dir}/soong/hiddenapi", ignore_errors=True)
@@ -605,26 +644,26 @@
if result.file_changes:
if self.fix:
- file_change_message = """
-The following files were modified by this script:"""
+ file_change_message = textwrap.dedent("""
+ The following files were modified by this script:
+ """)
else:
- file_change_message = """
-The following modifications need to be made:"""
+ file_change_message = textwrap.dedent("""
+ The following modifications need to be made:
+ """)
- self.report(f"""
-{file_change_message}""")
+ self.report(file_change_message)
result.file_changes.sort()
for file_change in result.file_changes:
- self.report(f"""
- {file_change.path}
- {file_change.description}
-""".lstrip("\n"))
+ self.report(f" {file_change.path}")
+ self.report(f" {file_change.description}")
+ self.report()
if not self.fix:
- self.report("""
-Run the command again with the --fix option to automatically make the above
-changes.
-""".lstrip())
+ self.report_dedent("""
+ Run the command again with the --fix option to automatically
+ make the above changes.
+ """.lstrip("\n"))
def new_file_change(self, file, description):
return FileChange(
@@ -635,11 +674,10 @@
if not (module_line.startswith("< ") and
monolithic_line.startswith("> ") and not separator_line):
# Something went wrong.
- self.report(f"""Invalid build output detected:
- module_line: "{module_line}"
- monolithic_line: "{monolithic_line}"
- separator_line: "{separator_line}"
-""")
+ self.report("Invalid build output detected:")
+ self.report(f" module_line: '{module_line}'")
+ self.report(f" monolithic_line: '{monolithic_line}'")
+ self.report(f" separator_line: '{separator_line}'")
sys.exit(1)
if significant:
@@ -698,10 +736,9 @@
if module_signature != monolithic_signature:
# Something went wrong.
- self.report(f"""Inconsistent signatures detected:
- module_signature: "{module_signature}"
- monolithic_signature: "{monolithic_signature}"
-""")
+ self.report("Inconsistent signatures detected:")
+ self.report(f" module_signature: '{module_signature}'")
+ self.report(f" monolithic_signature: '{monolithic_signature}'")
sys.exit(1)
diffs[module_signature] = (module_flags, monolithic_flags)
@@ -749,85 +786,91 @@
return diffs
def build_monolithic_stubs_flags(self):
- self.report(f"""
-Attempting to build {_STUB_FLAGS_FILE} to verify that the
-bootclasspath_fragment has the correct API stubs available...
-""")
+ self.report_dedent(f"""
+ Attempting to build {_STUB_FLAGS_FILE} to verify that the
+ bootclasspath_fragment has the correct API stubs available...
+ """)
# Build the hiddenapi-stubs-flags.txt file.
diffs = self.build_hiddenapi_flags(_STUB_FLAGS_FILE)
if diffs:
- self.report(f"""
-There is a discrepancy between the stub API derived flags created by the
-bootclasspath_fragment and the platform_bootclasspath. See preceding error
-messages to see which flags are inconsistent. The inconsistencies can occur for
-a couple of reasons:
+ self.report_dedent(f"""
+ There is a discrepancy between the stub API derived flags
+ created by the bootclasspath_fragment and the
+ platform_bootclasspath. See preceding error messages to see
+ which flags are inconsistent. The inconsistencies can occur for
+ a couple of reasons:
-If you are building against prebuilts of the Android SDK, e.g. by using
-TARGET_BUILD_APPS then the prebuilt versions of the APIs this
-bootclasspath_fragment depends upon are out of date and need updating. See
-go/update-prebuilts for help.
+ If you are building against prebuilts of the Android SDK, e.g.
+ by using TARGET_BUILD_APPS then the prebuilt versions of the
+ APIs this bootclasspath_fragment depends upon are out of date
+ and need updating. See go/update-prebuilts for help.
-Otherwise, this is happening because there are some stub APIs that are either
-provided by or used by the contents of the bootclasspath_fragment but which are
-not available to it. There are 4 ways to handle this:
+ Otherwise, this is happening because there are some stub APIs
+ that are either provided by or used by the contents of the
+ bootclasspath_fragment but which are not available to it. There
+ are 4 ways to handle this:
-1. A java_sdk_library in the contents property will automatically make its stub
- APIs available to the bootclasspath_fragment so nothing needs to be done.
+ 1. A java_sdk_library in the contents property will
+ automatically make its stub APIs available to the
+ bootclasspath_fragment so nothing needs to be done.
-2. If the API provided by the bootclasspath_fragment is created by an api_only
- java_sdk_library (or a java_library that compiles files generated by a
- separate droidstubs module then it cannot be added to the contents and
- instead must be added to the api.stubs property, e.g.
+ 2. If the API provided by the bootclasspath_fragment is created
+ by an api_only java_sdk_library (or a java_library that compiles
+ files generated by a separate droidstubs module then it cannot
+ be added to the contents and instead must be added to the
+ api.stubs property, e.g.
- bootclasspath_fragment {{
- name: "{self.bcpf}",
- ...
- api: {{
- stubs: ["$MODULE-api-only"],"
- }},
- }}
+ bootclasspath_fragment {{
+ name: "{self.bcpf}",
+ ...
+ api: {{
+ stubs: ["$MODULE-api-only"],"
+ }},
+ }}
-3. If the contents use APIs provided by another bootclasspath_fragment then
- it needs to be added to the fragments property, e.g.
+ 3. If the contents use APIs provided by another
+ bootclasspath_fragment then it needs to be added to the
+ fragments property, e.g.
+
+ bootclasspath_fragment {{
+ name: "{self.bcpf}",
+ ...
+ // The bootclasspath_fragments that provide APIs on which this depends.
+ fragments: [
+ ...
+ {{
+ apex: "com.android.other",
+ module: "com.android.other-bootclasspath-fragment",
+ }},
+ ],
+ }}
+
+ 4. If the contents use APIs from a module that is not part of
+ another bootclasspath_fragment then it must be added to the
+ additional_stubs property, e.g.
- bootclasspath_fragment {{
- name: "{self.bcpf}",
- ...
- // The bootclasspath_fragments that provide APIs on which this depends.
- fragments: [
- ...
- {{
- apex: "com.android.other",
- module: "com.android.other-bootclasspath-fragment",
- }},
- ],
- }}
+ bootclasspath_fragment {{
+ name: "{self.bcpf}",
+ ...
+ additional_stubs: ["android-non-updatable"],
+ }}
-4. If the contents use APIs from a module that is not part of another
- bootclasspath_fragment then it must be added to the additional_stubs
- property, e.g.
+ Like the api.stubs property these are typically
+ java_sdk_library modules but can be java_library too.
- bootclasspath_fragment {{
- name: "{self.bcpf}",
- ...
- additional_stubs: ["android-non-updatable"],
- }}
-
- Like the api.stubs property these are typically java_sdk_library modules but
- can be java_library too.
-
- Note: The "android-non-updatable" is treated as if it was a java_sdk_library
- which it is not at the moment but will be in future.
-""")
+ Note: The "android-non-updatable" is treated as if it was a
+ java_sdk_library which it is not at the moment but will be in
+ future.
+ """)
return diffs
def build_monolithic_flags(self, result):
- self.report(f"""
-Attempting to build {_FLAGS_FILE} to verify that the
-bootclasspath_fragment has the correct hidden API flags...
-""")
+ self.report_dedent(f"""
+ Attempting to build {_FLAGS_FILE} to verify that the
+ bootclasspath_fragment has the correct hidden API flags...
+ """)
# Build the hiddenapi-flags.csv file and extract any differences in
# the flags between this bootclasspath_fragment and the monolithic
@@ -838,32 +881,34 @@
self.load_all_flags()
if result.diffs:
- self.report(f"""
-There is a discrepancy between the hidden API flags created by the
-bootclasspath_fragment and the platform_bootclasspath. See preceding error
-messages to see which flags are inconsistent. The inconsistencies can occur for
-a couple of reasons:
+ self.report_dedent(f"""
+ There is a discrepancy between the hidden API flags created by
+ the bootclasspath_fragment and the platform_bootclasspath. See
+ preceding error messages to see which flags are inconsistent.
+ The inconsistencies can occur for a couple of reasons:
-If you are building against prebuilts of this bootclasspath_fragment then the
-prebuilt version of the sdk snapshot (specifically the hidden API flag files)
-are inconsistent with the prebuilt version of the apex {self.apex}. Please
-ensure that they are both updated from the same build.
+ If you are building against prebuilts of this
+ bootclasspath_fragment then the prebuilt version of the sdk
+ snapshot (specifically the hidden API flag files) are
+ inconsistent with the prebuilt version of the apex {self.apex}.
+ Please ensure that they are both updated from the same build.
-1. There are custom hidden API flags specified in the one of the files in
- frameworks/base/boot/hiddenapi which apply to the bootclasspath_fragment but
- which are not supplied to the bootclasspath_fragment module.
+ 1. There are custom hidden API flags specified in the one of the
+ files in frameworks/base/boot/hiddenapi which apply to the
+ bootclasspath_fragment but which are not supplied to the
+ bootclasspath_fragment module.
-2. The bootclasspath_fragment specifies invalid "package_prefixes" or
- "split_packages" properties that match packages and classes that it does not
- provide.
-
-""")
+ 2. The bootclasspath_fragment specifies invalid
+ "split_packages", "single_packages" and/of "package_prefixes"
+ properties that match packages and classes that it does not
+ provide.
+ """)
# Check to see if there are any hiddenapi related properties that
# need to be added to the
- self.report("""
-Checking custom hidden API flags....
-""")
+ self.report_dedent("""
+ Checking custom hidden API flags....
+ """)
self.check_frameworks_base_boot_hidden_api_files(result)
def report_hidden_api_flag_file_changes(self, result, property_name,
@@ -1044,13 +1089,16 @@
""").strip("\n")
def analyze_hiddenapi_package_properties(self, result):
- split_packages, single_packages, package_prefixes = \
- self.compute_hiddenapi_package_properties()
+ self.compute_hiddenapi_package_properties(result)
+
+ def indent_lines(lines):
+ return "\n".join([f" {cls}" for cls in lines])
# TODO(b/202154151): Find those classes in split packages that are not
# part of an API, i.e. are an internal implementation class, and so
# can, and should, be safely moved out of the split packages.
+ split_packages = result.split_packages.keys()
result.property_changes.append(
HiddenApiPropertyChange(
property_name="split_packages",
@@ -1060,26 +1108,40 @@
))
if split_packages:
- self.report(f"""
-bootclasspath_fragment {self.bcpf} contains classes in packages that also
-contain classes provided by other sources, those packages are called split
-packages. Split packages should be avoided where possible but are often
-unavoidable when modularizing existing code.
+ self.report_dedent(f"""
+ bootclasspath_fragment {self.bcpf} contains classes in packages
+ that also contain classes provided by other bootclasspath
+ modules. Those packages are called split packages. Split
+ packages should be avoided where possible but are often
+ unavoidable when modularizing existing code.
-The hidden api processing needs to know which packages are split (and conversely
-which are not) so that it can optimize the hidden API flags to remove
-unnecessary implementation details.
+ The hidden api processing needs to know which packages are split
+ (and conversely which are not) so that it can optimize the
+ hidden API flags to remove unnecessary implementation details.
+
+ By default (for backwards compatibility) the
+ bootclasspath_fragment assumes that all packages are split
+ unless one of the package_prefixes or split_packages properties
+ are specified. While that is safe it is not optimal and can lead
+ to unnecessary implementation details leaking into the hidden
+ API flags. Adding an empty split_packages property allows the
+ flags to be optimized and remove any unnecessary implementation
+ details.
+ """)
+
+ for package in split_packages:
+ reason = result.split_packages[package]
+ self.report(f"""
+ Package {package} is split because while this bootclasspath_fragment
+ provides the following classes:
+{indent_lines(reason.bcpf)}
+
+ Other module(s) on the bootclasspath provides the following classes in
+ that package:
+{indent_lines(reason.other)}
""")
- self.report("""
-By default (for backwards compatibility) the bootclasspath_fragment assumes that
-all packages are split unless one of the package_prefixes or split_packages
-properties are specified. While that is safe it is not optimal and can lead to
-unnecessary implementation details leaking into the hidden API flags. Adding an
-empty split_packages property allows the flags to be optimized and remove any
-unnecessary implementation details.
-""")
-
+ single_packages = result.single_packages.keys()
if single_packages:
result.property_changes.append(
HiddenApiPropertyChange(
@@ -1091,10 +1153,34 @@
contain classes from other bootclasspath modules. Packages
should only be listed here when necessary for legacy
purposes, new packages should match a package prefix.
- """),
+ """),
action=PropertyChangeAction.REPLACE,
))
+ self.report_dedent(f"""
+ bootclasspath_fragment {self.bcpf} contains classes from
+ packages that has sub-packages which contain classes provided by
+ other bootclasspath modules. Those packages are called single
+ packages. Single packages should be avoided where possible but
+ are often unavoidable when modularizing existing code.
+
+ Because some sub-packages contains classes from other
+ bootclasspath modules it is not possible to use the package as a
+ package prefix as that treats the package and all its
+ sub-packages as being provided by this module.
+ """)
+ for package in single_packages:
+ reason = result.single_packages[package]
+ self.report(f"""
+ Package {package} is not a package prefix because while this
+ bootclasspath_fragment provides the following sub-packages:
+{indent_lines(reason.bcpf)}
+
+ Other module(s) on the bootclasspath provide the following sub-packages:
+{indent_lines(reason.other)}
+""")
+
+ package_prefixes = result.package_prefixes
if package_prefixes:
result.property_changes.append(
HiddenApiPropertyChange(
@@ -1111,38 +1197,40 @@
signature_patterns_files = signature_patterns_files.removeprefix(
self.top_dir)
- self.report(f"""
-The purpose of the hiddenapi split_packages and package_prefixes properties is
-to allow the removal of implementation details from the hidden API flags to
-reduce the coupling between sdk snapshots and the APEX runtime. It cannot
-eliminate that coupling completely though. Doing so may require changes to the
-code.
+ self.report_dedent(f"""
+ The purpose of the hiddenapi split_packages and package_prefixes
+ properties is to allow the removal of implementation details
+ from the hidden API flags to reduce the coupling between sdk
+ snapshots and the APEX runtime. It cannot eliminate that
+ coupling completely though. Doing so may require changes to the
+ code.
-This tool provides support for managing those properties but it cannot decide
-whether the set of package prefixes suggested is appropriate that needs the
-input of the developer.
+ This tool provides support for managing those properties but it
+ cannot decide whether the set of package prefixes suggested is
+ appropriate that needs the input of the developer.
-Please run the following command:
- m {signature_patterns_files}
+ Please run the following command:
+ m {signature_patterns_files}
-And then check the '{signature_patterns_files}' for any mention of
-implementation classes and packages (i.e. those classes/packages that do not
-contain any part of an API surface, including the hidden API). If they are
-found then the code should ideally be moved to a package unique to this module
-that is contained within a package that is part of an API surface.
+ And then check the '{signature_patterns_files}' for any mention
+ of implementation classes and packages (i.e. those
+ classes/packages that do not contain any part of an API surface,
+ including the hidden API). If they are found then the code
+ should ideally be moved to a package unique to this module that
+ is contained within a package that is part of an API surface.
-The format of the file is a list of patterns:
+ The format of the file is a list of patterns:
-* Patterns for split packages will list every class in that package.
+ * Patterns for split packages will list every class in that package.
-* Patterns for package prefixes will end with .../**.
+ * Patterns for package prefixes will end with .../**.
-* Patterns for packages which are not split but cannot use a package prefix
-because there are sub-packages which are provided by another module will end
-with .../*.
-""")
+ * Patterns for packages which are not split but cannot use a
+ package prefix because there are sub-packages which are provided
+ by another module will end with .../*.
+ """)
- def compute_hiddenapi_package_properties(self):
+ def compute_hiddenapi_package_properties(self, result):
trie = signature_trie()
# Populate the trie with the classes that are provided by the
# bootclasspath_fragment tagging them to make it clear where they
@@ -1151,6 +1239,7 @@
for class_name in sorted_classes:
trie.add(class_name + _FAKE_MEMBER, ClassProvider.BCPF)
+ # Now the same for monolithic classes.
monolithic_classes = set()
abs_flags_file = os.path.join(self.top_dir, _FLAGS_FILE)
with open(abs_flags_file, "r", encoding="utf8") as f:
@@ -1165,15 +1254,54 @@
only_if_matches=True)
monolithic_classes.add(class_name)
- split_packages = []
- single_packages = []
- package_prefixes = []
- self.recurse_hiddenapi_packages_trie(trie, split_packages,
- single_packages, package_prefixes)
- return split_packages, single_packages, package_prefixes
+ self.recurse_hiddenapi_packages_trie(trie, result)
- def recurse_hiddenapi_packages_trie(self, node, split_packages,
- single_packages, package_prefixes):
+ @staticmethod
+ def selector_to_java_reference(node):
+ return node.selector.replace("/", ".")
+
+ @staticmethod
+ def determine_reason_for_single_package(node):
+ bcpf_packages = []
+ other_packages = []
+
+ def recurse(n):
+ if n.type != "package":
+ return
+
+ providers = n.get_matching_rows("*")
+ package_ref = BcpfAnalyzer.selector_to_java_reference(n)
+ if ClassProvider.BCPF in providers:
+ bcpf_packages.append(package_ref)
+ else:
+ other_packages.append(package_ref)
+
+ children = n.child_nodes()
+ if children:
+ for child in children:
+ recurse(child)
+
+ recurse(node)
+ return PackagePropertyReason(bcpf=bcpf_packages, other=other_packages)
+
+ @staticmethod
+ def determine_reason_for_split_package(node):
+ bcpf_classes = []
+ other_classes = []
+ for child in node.child_nodes():
+ if child.type != "class":
+ continue
+
+ providers = child.values(lambda _: True)
+ class_ref = BcpfAnalyzer.selector_to_java_reference(child)
+ if ClassProvider.BCPF in providers:
+ bcpf_classes.append(class_ref)
+ else:
+ other_classes.append(class_ref)
+
+ return PackagePropertyReason(bcpf=bcpf_classes, other=other_classes)
+
+ def recurse_hiddenapi_packages_trie(self, node, result):
nodes = node.child_nodes()
if nodes:
for child in nodes:
@@ -1181,7 +1309,7 @@
if child.type != "package":
continue
- package = child.selector.replace("/", ".")
+ package = self.selector_to_java_reference(child)
providers = set(child.get_matching_rows("**"))
if not providers:
@@ -1192,7 +1320,7 @@
# The package and all its sub packages only contain
# classes provided by the bootclasspath_fragment.
logging.debug("Package '%s.**' is not split", package)
- package_prefixes.append(package)
+ result.package_prefixes.append(package)
# There is no point traversing into the sub packages.
continue
elif providers == {ClassProvider.OTHER}:
@@ -1217,8 +1345,17 @@
elif providers == {ClassProvider.BCPF}:
# The package only contains classes provided by the
# bootclasspath_fragment.
- logging.debug("Package '%s.*' is not split", package)
- single_packages.append(package)
+ logging.debug(
+ "Package '%s.*' is not split but does have "
+ "sub-packages from other modules", package)
+
+ # Partition the sub-packages into those that are provided by
+ # this bootclasspath_fragment and those provided by other
+ # modules. They can be used to explain the reason for the
+ # single package to developers.
+ reason = self.determine_reason_for_single_package(child)
+ result.single_packages[package] = reason
+
elif providers == {ClassProvider.OTHER}:
# The package contains no classes provided by the
# bootclasspath_fragment. Child nodes make contain such
@@ -1229,11 +1366,15 @@
# The package contains classes provided by both the
# bootclasspath_fragment and some other source.
logging.debug("Package '%s.*' is split", package)
- split_packages.append(package)
- self.recurse_hiddenapi_packages_trie(child, split_packages,
- single_packages,
- package_prefixes)
+ # Partition the classes in this split package into those
+ # that come from this bootclasspath_fragment and those that
+ # come from other modules. That can be used to explain the
+ # reason for the split package to developers.
+ reason = self.determine_reason_for_split_package(child)
+ result.split_packages[package] = reason
+
+ self.recurse_hiddenapi_packages_trie(child, result)
def newline_stripping_iter(iterator):
diff --git a/scripts/hiddenapi/analyze_bcpf_test.py b/scripts/hiddenapi/analyze_bcpf_test.py
index 650dd54..a32ffd0 100644
--- a/scripts/hiddenapi/analyze_bcpf_test.py
+++ b/scripts/hiddenapi/analyze_bcpf_test.py
@@ -377,6 +377,7 @@
La/b/c/D;->m()V
La/b/c/E;->m()V
La/b/c/d/E;->m()V
+La/b/c/d/e/F;->m()V
Lb/c/D;->m()V
Lb/c/E;->m()V
Lb/c/d/E;->m()V
@@ -385,11 +386,21 @@
analyzer = self.create_analyzer_for_test(fs)
analyzer.load_all_flags()
- split_packages, single_packages, package_prefixes = \
- analyzer.compute_hiddenapi_package_properties()
- self.assertEqual(["a.b"], split_packages)
- self.assertEqual(["a.b.c"], single_packages)
- self.assertEqual(["b"], package_prefixes)
+ result = ab.Result()
+ analyzer.compute_hiddenapi_package_properties(result)
+ self.assertEqual(["a.b"], list(result.split_packages.keys()))
+
+ reason = result.split_packages["a.b"]
+ self.assertEqual(["a.b.C"], reason.bcpf)
+ self.assertEqual(["a.b.D", "a.b.E"], reason.other)
+
+ self.assertEqual(["a.b.c"], list(result.single_packages.keys()))
+
+ reason = result.single_packages["a.b.c"]
+ self.assertEqual(["a.b.c"], reason.bcpf)
+ self.assertEqual(["a.b.c.d", "a.b.c.d.e"], reason.other)
+
+ self.assertEqual(["b"], result.package_prefixes)
class TestHiddenApiPropertyChange(unittest.TestCase):
diff --git a/scripts/hiddenapi/signature_trie.py b/scripts/hiddenapi/signature_trie.py
index e813a97..3650fa1 100644
--- a/scripts/hiddenapi/signature_trie.py
+++ b/scripts/hiddenapi/signature_trie.py
@@ -45,7 +45,9 @@
:return: A list of iterables of all the values associated with
this node and its children.
"""
- raise NotImplementedError("Please Implement this method")
+ values = []
+ self.append_values(values, selector)
+ return values
def append_values(self, values, selector):
"""Append the values associated with this node and its children.
@@ -313,12 +315,8 @@
node = node.nodes[element]
else:
return []
- return chain.from_iterable(node.values(selector))
- def values(self, selector):
- values = []
- self.append_values(values, selector)
- return values
+ return node.values(selector)
def append_values(self, values, selector):
for key, node in self.nodes.items():
@@ -336,11 +334,8 @@
# The value associated with this leaf.
value: typing.Any
- def values(self, selector):
- return [[self.value]]
-
def append_values(self, values, selector):
- values.append([self.value])
+ values.append(self.value)
def child_nodes(self):
return []
diff --git a/scripts/hiddenapi/signature_trie_test.py b/scripts/hiddenapi/signature_trie_test.py
index 1295691..6d4e660 100755
--- a/scripts/hiddenapi/signature_trie_test.py
+++ b/scripts/hiddenapi/signature_trie_test.py
@@ -150,6 +150,27 @@
str(context.exception))
+class TestValues(unittest.TestCase):
+ def test_add_then_get(self):
+ trie = signature_trie()
+ trie.add("La/b/C;->l()", 1)
+ trie.add("La/b/C$D;->m()", "A")
+ trie.add("La/b/C$D;->n()", {})
+
+ package_a_node = next(iter(trie.child_nodes()))
+ self.assertEqual("package", package_a_node.type)
+ self.assertEqual("a", package_a_node.selector)
+
+ package_b_node = next(iter(package_a_node.child_nodes()))
+ self.assertEqual("package", package_b_node.type)
+ self.assertEqual("a/b", package_b_node.selector)
+
+ class_c_node = next(iter(package_b_node.child_nodes()))
+ self.assertEqual("class", class_c_node.type)
+ self.assertEqual("a/b/C", class_c_node.selector)
+
+ self.assertEqual([1, "A", {}], class_c_node.values(lambda _: True))
+
class TestGetMatchingRows(unittest.TestCase):
extractInput = """
Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;
diff --git a/scripts/mergenotice.py b/scripts/mergenotice.py
deleted file mode 100755
index fe99073..0000000
--- a/scripts/mergenotice.py
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2019 The Android Open Source Project
-#
-# 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.
-#
-"""
-Merges input notice files to the output file while ignoring duplicated files
-This script shouldn't be confused with build/soong/scripts/generate-notice-files.py
-which is responsible for creating the final notice file for all artifacts
-installed. This script has rather limited scope; it is meant to create a merged
-notice file for a set of modules that are packaged together, e.g. in an APEX.
-The merged notice file does not reveal the individual files in the package.
-"""
-
-import sys
-import argparse
-
-def get_args():
- parser = argparse.ArgumentParser(description='Merge notice files.')
- parser.add_argument('--output', help='output file path.')
- parser.add_argument('inputs', metavar='INPUT', nargs='+',
- help='input notice file')
- return parser.parse_args()
-
-def main(argv):
- args = get_args()
-
- processed = set()
- with open(args.output, 'w+') as output:
- for input in args.inputs:
- with open(input, 'r') as f:
- data = f.read().strip()
- if data not in processed:
- processed.add(data)
- output.write('%s\n\n' % data)
-
-if __name__ == '__main__':
- main(sys.argv)