Merge "Correct type of dir function"
diff --git a/android/bazel.go b/android/bazel.go
index 7e2727c..e3fb0a6 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -357,6 +357,7 @@
"packages/services/Car/tests/SampleRearViewCamera": Bp2BuildDefaultTrue,
"prebuilts/clang/host/linux-x86": Bp2BuildDefaultTrueRecursively,
"prebuilts/tools/common/m2": Bp2BuildDefaultTrue,
+ "prebuilts/sdk/tools/jetifier/jetifier-standalone": Bp2BuildDefaultTrue,
"system/apex": Bp2BuildDefaultFalse, // TODO(b/207466993): flaky failures
"system/apex/proto": Bp2BuildDefaultTrueRecursively,
"system/apex/libs": Bp2BuildDefaultTrueRecursively,
@@ -393,8 +394,6 @@
// A module can either be in this list or its directory allowlisted entirely
// in bp2buildDefaultConfig, but not both at the same time.
bp2buildModuleAlwaysConvertList = []string{
- "prebuilt_junit-params-assertj-core",
-
//external/avb
"avbtool",
"libavb",
@@ -417,11 +416,22 @@
//system/extras/verity/fec
"fec",
+
+ //packages/apps/Car/libs/car-ui-lib/car-ui-androidx
+ // genrule dependencies for java_imports
+ "car-ui-androidx-annotation-nodeps",
+ "car-ui-androidx-collection-nodeps",
+ "car-ui-androidx-core-common-nodeps",
+ "car-ui-androidx-lifecycle-common-nodeps",
+ "car-ui-androidx-constraintlayout-solver-nodeps",
}
- // Per-module-type allowlist to always opt modules in of both bp2build and mixed builds
+ // Per-module-type allowlist to always opt modules in to both bp2build and mixed builds
// when they have the same type as one listed.
- bp2buildModuleTypeAlwaysConvertList = []string{}
+ bp2buildModuleTypeAlwaysConvertList = []string{
+ "java_import",
+ "java_import_host",
+ }
// Per-module denylist to always opt modules out of both bp2build and mixed builds.
bp2buildModuleDoNotConvertList = []string{
@@ -485,18 +495,12 @@
"libprotobuf-java-full", // b/210751803, we don't handle path property for filegroups
"host-libprotobuf-java-full", // b/210751803, we don't handle path property for filegroups
"libprotobuf-java-util-full", // b/210751803, we don't handle path property for filegroups
+ "apex_manifest_proto_java", // b/210751803, depends on libprotobuf-java-full
+ "conscrypt", // b/210751803, we don't handle path property for filegroups
+ "conscrypt-for-host", // b/210751803, we don't handle path property for filegroups
- "conscrypt", // b/210751803, we don't handle path property for filegroups
- "conscrypt-for-host", // b/210751803, we don't handle path property for filegroups
-
- "host-libprotobuf-java-lite", // b/217236083, java_library cannot have deps without srcs
- "host-libprotobuf-java-micro", // b/217236083, java_library cannot have deps without srcs
- "host-libprotobuf-java-nano", // b/217236083, java_library cannot have deps without srcs
- "error_prone_core", // b/217236083, java_library cannot have deps without srcs
- "bouncycastle-host", // b/217236083, java_library cannot have deps without srcs
- "mockito-robolectric-prebuilt", // b/217236083, java_library cannot have deps without srcs
-
- "apex_manifest_proto_java", // b/215230097, we don't handle .proto files in java_library srcs attribute
+ "libprotobuf-java-nano", // b/220869005, depends on non-public_current SDK
+ "host-libprotobuf-java-nano", // b/220869005, depends on libprotobuf-java-nano
"libc_musl_sysroot_bionic_arch_headers", // b/218405924, depends on soong_zip
"libc_musl_sysroot_bionic_headers", // b/218405924, depends on soong_zip and generates duplicate srcs
@@ -509,6 +513,9 @@
"brotli-fuzzer-corpus", // b/202015218: outputs are in location incompatible with bazel genrule handling.
+ // python modules
+ "analyze_bcpf", // depends on bpmodify a blueprint_go_binary.
+
// b/203369847: multiple genrules in the same package creating the same file
// //development/sdk/...
"platform_tools_properties",
@@ -563,9 +570,9 @@
"art-script", // depends on unconverted modules: dalvikvm, dex2oat
"dex2oat-script", // depends on unconverted modules: dex2oat
- "error_prone_checkerframework_dataflow_nullaway", // TODO(b/219908977): "Error in fail: deps not allowed without srcs; move to runtime_deps?"
-
- "libprotobuf-java-nano", // b/220869005, depends on non-public_current SDK
+ "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
}
// Per-module denylist of cc_library modules to only generate the static
diff --git a/android/module.go b/android/module.go
index e190e8b..66a5f60 100644
--- a/android/module.go
+++ b/android/module.go
@@ -3163,6 +3163,7 @@
symlinkTarget: "",
executable: executable,
effectiveLicenseFiles: &licenseFiles,
+ partition: fullInstallPath.partition,
}
m.packagingSpecs = append(m.packagingSpecs, spec)
return spec
@@ -3280,6 +3281,7 @@
srcPath: nil,
symlinkTarget: relPath,
executable: false,
+ partition: fullInstallPath.partition,
})
return fullInstallPath
@@ -3320,6 +3322,7 @@
srcPath: nil,
symlinkTarget: absPath,
executable: false,
+ partition: fullInstallPath.partition,
})
return fullInstallPath
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/android/packaging.go b/android/packaging.go
index e3a0b54..ecd84a2 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -40,6 +40,8 @@
executable bool
effectiveLicenseFiles *Paths
+
+ partition string
}
// Get file name of installed package
@@ -67,6 +69,10 @@
return *p.effectiveLicenseFiles
}
+func (p *PackagingSpec) Partition() string {
+ return p.partition
+}
+
type PackageModule interface {
Module
packagingBase() *PackagingBase
@@ -76,11 +82,14 @@
// be copied to a zip in CopyDepsToZip, `depTag` should implement PackagingItem marker interface.
AddDeps(ctx BottomUpMutatorContext, depTag blueprint.DependencyTag)
+ // GatherPackagingSpecs gathers PackagingSpecs of transitive dependencies.
+ GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec
+
// CopyDepsToZip zips the built artifacts of the dependencies into the given zip file and
// returns zip entries in it. This is expected to be called in GenerateAndroidBuildActions,
// followed by a build rule that unzips it and creates the final output (img, zip, tar.gz,
// etc.) from the extracted files
- CopyDepsToZip(ctx ModuleContext, zipOut WritablePath) []string
+ CopyDepsToZip(ctx ModuleContext, specs map[string]PackagingSpec, zipOut WritablePath) []string
}
// PackagingBase provides basic functionality for packaging dependencies. A module is expected to
@@ -211,7 +220,7 @@
}
}
-// Returns transitive PackagingSpecs from deps
+// See PackageModule.GatherPackagingSpecs
func (p *PackagingBase) GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec {
m := make(map[string]PackagingSpec)
ctx.VisitDirectDeps(func(child Module) {
@@ -229,10 +238,10 @@
// CopySpecsToDir is a helper that will add commands to the rule builder to copy the PackagingSpec
// entries into the specified directory.
-func (p *PackagingBase) CopySpecsToDir(ctx ModuleContext, builder *RuleBuilder, m map[string]PackagingSpec, dir ModuleOutPath) (entries []string) {
+func (p *PackagingBase) CopySpecsToDir(ctx ModuleContext, builder *RuleBuilder, specs map[string]PackagingSpec, dir ModuleOutPath) (entries []string) {
seenDir := make(map[string]bool)
- for _, k := range SortedStringKeys(m) {
- ps := m[k]
+ for _, k := range SortedStringKeys(specs) {
+ ps := specs[k]
destPath := dir.Join(ctx, ps.relPathInPackage).String()
destDir := filepath.Dir(destPath)
entries = append(entries, ps.relPathInPackage)
@@ -254,14 +263,13 @@
}
// See PackageModule.CopyDepsToZip
-func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, zipOut WritablePath) (entries []string) {
- m := p.GatherPackagingSpecs(ctx)
+func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, specs map[string]PackagingSpec, zipOut WritablePath) (entries []string) {
builder := NewRuleBuilder(pctx, ctx)
dir := PathForModuleOut(ctx, ".zip")
builder.Command().Text("rm").Flag("-rf").Text(dir.String())
builder.Command().Text("mkdir").Flag("-p").Text(dir.String())
- entries = p.CopySpecsToDir(ctx, builder, m, dir)
+ entries = p.CopySpecsToDir(ctx, builder, specs, dir)
builder.Command().
BuiltTool("soong_zip").
diff --git a/android/packaging_test.go b/android/packaging_test.go
index ff7446c..91ac1f3 100644
--- a/android/packaging_test.go
+++ b/android/packaging_test.go
@@ -95,7 +95,7 @@
func (m *packageTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
zipFile := PathForModuleOut(ctx, "myzip.zip")
- m.entries = m.CopyDepsToZip(ctx, zipFile)
+ m.entries = m.CopyDepsToZip(ctx, m.GatherPackagingSpecs(ctx), zipFile)
}
func runPackagingTest(t *testing.T, multitarget bool, bp string, expected []string) {
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..cb88f02 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()
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 85bd595..ec815c4 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")
diff --git a/apex/builder.go b/apex/builder.go
index 8c5f99b..50c8dd1 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,11 @@
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", "NOTICE.html.gz")
+ android.BuildNoticeHtmlOutputFromLicenseMetadata(ctx, a.htmlGzNotice)
+ implicitInputs = append(implicitInputs, a.htmlGzNotice)
+ optFlags = append(optFlags, "--assets_dir "+filepath.Dir(a.htmlGzNotice.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/bazel/properties.go b/bazel/properties.go
index 1300a53..f956031 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -65,6 +65,14 @@
Excludes []Label
}
+// MakeLabelList creates a LabelList from a list Label
+func MakeLabelList(labels []Label) LabelList {
+ return LabelList{
+ Includes: labels,
+ Excludes: nil,
+ }
+}
+
func (ll *LabelList) Equals(other LabelList) bool {
if len(ll.Includes) != len(other.Includes) || len(ll.Excludes) != len(other.Excludes) {
return false
@@ -354,6 +362,15 @@
return keys
}
+// MakeLabelAttribute turns a string into a LabelAttribute
+func MakeLabelAttribute(label string) *LabelAttribute {
+ return &LabelAttribute{
+ Value: &Label{
+ Label: label,
+ },
+ }
+}
+
type configToBools map[string]bool
func (ctb configToBools) setValue(config string, value *bool) {
diff --git a/bp2build/android_app_conversion_test.go b/bp2build/android_app_conversion_test.go
index b6095b2..3824586 100644
--- a/bp2build/android_app_conversion_test.go
+++ b/bp2build/android_app_conversion_test.go
@@ -51,7 +51,6 @@
"srcs": `["app.java"]`,
"manifest": `"AndroidManifest.xml"`,
"resource_files": `["res/res.png"]`,
- "deps": `["//prebuilts/sdk:public_current_android_sdk_java_import"]`,
}),
}})
}
@@ -87,10 +86,7 @@
"resb/res.png",
]`,
"custom_package": `"com.google"`,
- "deps": `[
- "//prebuilts/sdk:public_current_android_sdk_java_import",
- ":static_lib_dep",
- ]`,
+ "deps": `[":static_lib_dep"]`,
}),
}})
}
@@ -129,7 +125,6 @@
})`,
"manifest": `"AndroidManifest.xml"`,
"resource_files": `["res/res.png"]`,
- "deps": `["//prebuilts/sdk:public_current_android_sdk_java_import"]`,
}),
}})
}
diff --git a/bp2build/java_binary_host_conversion_test.go b/bp2build/java_binary_host_conversion_test.go
index 65136d9..4fc07e0 100644
--- a/bp2build/java_binary_host_conversion_test.go
+++ b/bp2build/java_binary_host_conversion_test.go
@@ -28,6 +28,7 @@
(&tc).moduleTypeUnderTestFactory = java.BinaryHostFactory
runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {
ctx.RegisterModuleType("cc_library_host_shared", cc.LibraryHostSharedFactory)
+ ctx.RegisterModuleType("java_library", java.LibraryFactory)
}, tc)
}
@@ -67,3 +68,33 @@
},
})
}
+
+func TestJavaBinaryHostRuntimeDeps(t *testing.T) {
+ runJavaBinaryHostTestCase(t, bp2buildTestCase{
+ description: "java_binary_host with srcs, exclude_srcs, jni_libs, javacflags, and manifest.",
+ filesystem: fs,
+ blueprint: `java_binary_host {
+ name: "java-binary-host-1",
+ static_libs: ["java-dep-1"],
+ manifest: "test.mf",
+ bazel_module: { bp2build_available: true },
+}
+
+java_library {
+ name: "java-dep-1",
+ srcs: ["a.java"],
+ bazel_module: { bp2build_available: false },
+}
+`,
+ expectedBazelTargets: []string{
+ makeBazelTarget("java_binary", "java-binary-host-1", attrNameToString{
+ "main_class": `"com.android.test.MainClass"`,
+ "runtime_deps": `[":java-dep-1"]`,
+ "target_compatible_with": `select({
+ "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+ "//conditions:default": [],
+ })`,
+ }),
+ },
+ })
+}
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 5c65ec2..2f6bce2 100644
--- a/bp2build/java_library_conversion_test.go
+++ b/bp2build/java_library_conversion_test.go
@@ -15,17 +15,22 @@
package bp2build
import (
+ "fmt"
"testing"
"android/soong/android"
"android/soong/java"
)
-func runJavaLibraryTestCase(t *testing.T, tc bp2buildTestCase) {
+func runJavaLibraryTestCaseWithRegistrationCtxFunc(t *testing.T, tc bp2buildTestCase, registrationCtxFunc func(ctx android.RegistrationContext)) {
t.Helper()
(&tc).moduleTypeUnderTest = "java_library"
(&tc).moduleTypeUnderTestFactory = java.LibraryFactory
- runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, tc)
+ runBp2BuildTestCase(t, registrationCtxFunc, tc)
+}
+
+func runJavaLibraryTestCase(t *testing.T, tc bp2buildTestCase) {
+ runJavaLibraryTestCaseWithRegistrationCtxFunc(t, tc, func(ctx android.RegistrationContext) {})
}
func TestJavaLibrary(t *testing.T) {
@@ -55,3 +60,99 @@
},
})
}
+
+func TestJavaLibraryConvertsStaticLibsToDepsAndExports(t *testing.T) {
+ runJavaLibraryTestCase(t, bp2buildTestCase{
+ blueprint: `java_library {
+ name: "java-lib-1",
+ srcs: ["a.java"],
+ libs: ["java-lib-2"],
+ static_libs: ["java-lib-3"],
+ bazel_module: { bp2build_available: true },
+}
+
+java_library {
+ name: "java-lib-2",
+ srcs: ["b.java"],
+ bazel_module: { bp2build_available: false },
+}
+
+java_library {
+ name: "java-lib-3",
+ srcs: ["c.java"],
+ bazel_module: { bp2build_available: false },
+}`,
+ expectedBazelTargets: []string{
+ makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+ "srcs": `["a.java"]`,
+ "deps": `[
+ ":java-lib-2",
+ ":java-lib-3",
+ ]`,
+ "exports": `[":java-lib-3"]`,
+ }),
+ },
+ })
+}
+
+func TestJavaLibraryConvertsStaticLibsToExportsIfNoSrcs(t *testing.T) {
+ runJavaLibraryTestCase(t, bp2buildTestCase{
+ blueprint: `java_library {
+ name: "java-lib-1",
+ static_libs: ["java-lib-2"],
+ bazel_module: { bp2build_available: true },
+}
+
+java_library {
+ name: "java-lib-2",
+ srcs: ["a.java"],
+ bazel_module: { bp2build_available: false },
+}`,
+ expectedBazelTargets: []string{
+ makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+ "exports": `[":java-lib-2"]`,
+ }),
+ },
+ })
+}
+
+func TestJavaLibraryFailsToConvertLibsWithNoSrcs(t *testing.T) {
+ runJavaLibraryTestCase(t, bp2buildTestCase{
+ expectedErr: fmt.Errorf("Module has direct dependencies but no sources. Bazel will not allow this."),
+ blueprint: `java_library {
+ name: "java-lib-1",
+ libs: ["java-lib-2"],
+ bazel_module: { bp2build_available: true },
+}
+
+java_library {
+ name: "java-lib-2",
+ srcs: ["a.java"],
+ bazel_module: { bp2build_available: false },
+}`,
+ expectedBazelTargets: []string{},
+ })
+}
+
+func TestJavaLibraryPlugins(t *testing.T) {
+ runJavaLibraryTestCaseWithRegistrationCtxFunc(t, bp2buildTestCase{
+ blueprint: `java_library {
+ name: "java-lib-1",
+ plugins: ["java-plugin-1"],
+ bazel_module: { bp2build_available: true },
+}
+
+java_plugin {
+ name: "java-plugin-1",
+ srcs: ["a.java"],
+ bazel_module: { bp2build_available: false },
+}`,
+ expectedBazelTargets: []string{
+ makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+ "plugins": `[":java-plugin-1"]`,
+ }),
+ },
+ }, func(ctx android.RegistrationContext) {
+ ctx.RegisterModuleType("java_plugin", java.PluginFactory)
+ })
+}
diff --git a/bp2build/java_plugin_conversion_test.go b/bp2build/java_plugin_conversion_test.go
index ff13bb0..c2a2182 100644
--- a/bp2build/java_plugin_conversion_test.go
+++ b/bp2build/java_plugin_conversion_test.go
@@ -70,3 +70,39 @@
},
})
}
+
+func TestJavaPluginNoSrcs(t *testing.T) {
+ runJavaPluginTestCase(t, bp2buildTestCase{
+ description: "java_plugin without srcs converts (static) libs to deps",
+ blueprint: `java_plugin {
+ name: "java-plug-1",
+ libs: ["java-lib-1"],
+ static_libs: ["java-lib-2"],
+ bazel_module: { bp2build_available: true },
+}
+
+java_library {
+ name: "java-lib-1",
+ srcs: ["b.java"],
+ bazel_module: { bp2build_available: false },
+}
+
+java_library {
+ name: "java-lib-2",
+ srcs: ["c.java"],
+ bazel_module: { bp2build_available: false },
+}`,
+ expectedBazelTargets: []string{
+ makeBazelTarget("java_plugin", "java-plug-1", attrNameToString{
+ "target_compatible_with": `select({
+ "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+ "//conditions:default": [],
+ })`,
+ "deps": `[
+ ":java-lib-1",
+ ":java-lib-2",
+ ]`,
+ }),
+ },
+ })
+}
diff --git a/bp2build/java_proto_conversion_test.go b/bp2build/java_proto_conversion_test.go
index 61a398c..67f8044 100644
--- a/bp2build/java_proto_conversion_test.go
+++ b/bp2build/java_proto_conversion_test.go
@@ -89,7 +89,7 @@
"deps": `[":java-protos_proto"]`,
}),
makeBazelTarget("java_library", "java-protos", attrNameToString{
- "deps": fmt.Sprintf(`[":%s"]`, javaLibraryName),
+ "exports": fmt.Sprintf(`[":%s"]`, javaLibraryName),
}),
},
})
@@ -98,7 +98,7 @@
func TestJavaProtoDefault(t *testing.T) {
runJavaProtoTestCase(t, bp2buildTestCase{
- description: "java_proto",
+ description: "java_library proto default",
blueprint: `java_library_static {
name: "java-protos",
srcs: ["a.proto"],
@@ -115,7 +115,7 @@
"deps": `[":java-protos_proto"]`,
}),
makeBazelTarget("java_library", "java-protos", attrNameToString{
- "deps": `[":java-protos_java_proto_lite"]`,
+ "exports": `[":java-protos_java_proto_lite"]`,
}),
},
})
diff --git a/cc/config/global.go b/cc/config/global.go
index 8dda537..b8790c7 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -287,8 +287,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/config/x86_linux_host.go b/cc/config/x86_linux_host.go
index ce6836b..e1659d3 100644
--- a/cc/config/x86_linux_host.go
+++ b/cc/config/x86_linux_host.go
@@ -65,7 +65,6 @@
linuxMuslLdflags = []string{
"-nostdlib",
- "-lgcc", "-lgcc_eh",
"--sysroot /dev/null",
}
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 3cf8b58..814fef6 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -480,8 +480,8 @@
s.Diag.Cfi = nil
}
- // Disable sanitizers that depend on the UBSan runtime for windows/darwin/musl builds.
- if !ctx.Os().Linux() || ctx.Os() == android.LinuxMusl {
+ // Disable sanitizers that depend on the UBSan runtime for windows/darwin builds.
+ if !ctx.Os().Linux() {
s.Cfi = nil
s.Diag.Cfi = nil
s.Misc_undefined = nil
@@ -490,6 +490,12 @@
s.Integer_overflow = nil
}
+ // Disable CFI for musl
+ if ctx.toolchain().Musl() {
+ s.Cfi = nil
+ s.Diag.Cfi = nil
+ }
+
// Also disable CFI for VNDK variants of components
if ctx.isVndk() && ctx.useVndk() {
if ctx.static() {
@@ -702,10 +708,10 @@
flags.Local.AsFlags = append(flags.Local.AsFlags, sanitizeArg)
flags.Local.LdFlags = append(flags.Local.LdFlags, sanitizeArg)
- if ctx.toolchain().Bionic() {
- // Bionic sanitizer runtimes have already been added as dependencies so that
- // the right variant of the runtime will be used (with the "-android"
- // suffix), so don't let clang the runtime library.
+ if ctx.toolchain().Bionic() || ctx.toolchain().Musl() {
+ // Bionic and musl sanitizer runtimes have already been added as dependencies so that
+ // the right variant of the runtime will be used (with the "-android" or "-musl"
+ // suffixes), so don't let clang the runtime library.
flags.Local.LdFlags = append(flags.Local.LdFlags, "-fno-sanitize-link-runtime")
} else {
// Host sanitizers only link symbols in the final executable, so
@@ -1217,7 +1223,7 @@
addStaticDeps(config.BuiltinsRuntimeLibrary(toolchain))
}
- if runtimeLibrary != "" && (toolchain.Bionic() || c.sanitize.Properties.UbsanRuntimeDep) {
+ if runtimeLibrary != "" && (toolchain.Bionic() || toolchain.Musl() || c.sanitize.Properties.UbsanRuntimeDep) {
// UBSan is supported on non-bionic linux host builds as well
// Adding dependency to the runtime library. We are using *FarVariation*
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 0796258..ccf9e9d 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -43,8 +43,14 @@
// Function that builds extra files under the root directory and returns the files
buildExtraFiles func(ctx android.ModuleContext, root android.OutputPath) android.OutputPaths
+ // Function that filters PackagingSpecs returned by PackagingBase.GatherPackagingSpecs()
+ filterPackagingSpecs func(specs map[string]android.PackagingSpec)
+
output android.OutputPath
installDir android.InstallPath
+
+ // For testing. Keeps the result of CopyDepsToZip()
+ entries []string
}
type symlinkDefinition struct {
@@ -226,7 +232,7 @@
func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) android.OutputPath {
depsZipFile := android.PathForModuleOut(ctx, "deps.zip").OutputPath
- f.CopyDepsToZip(ctx, depsZipFile)
+ f.entries = f.CopyDepsToZip(ctx, f.gatherFilteredPackagingSpecs(ctx), depsZipFile)
builder := android.NewRuleBuilder(pctx, ctx)
depsBase := proptools.StringDefault(f.properties.Base_dir, ".")
@@ -345,7 +351,7 @@
}
depsZipFile := android.PathForModuleOut(ctx, "deps.zip").OutputPath
- f.CopyDepsToZip(ctx, depsZipFile)
+ f.entries = f.CopyDepsToZip(ctx, f.gatherFilteredPackagingSpecs(ctx), depsZipFile)
builder := android.NewRuleBuilder(pctx, ctx)
depsBase := proptools.StringDefault(f.properties.Base_dir, ".")
@@ -434,3 +440,14 @@
}
return nil
}
+
+// Filter the result of GatherPackagingSpecs to discard items targeting outside "system" partition.
+// Note that "apex" module installs its contents to "apex"(fake partition) as well
+// for symbol lookup by imitating "activated" paths.
+func (f *filesystem) gatherFilteredPackagingSpecs(ctx android.ModuleContext) map[string]android.PackagingSpec {
+ specs := f.PackagingBase.GatherPackagingSpecs(ctx)
+ if f.filterPackagingSpecs != nil {
+ f.filterPackagingSpecs(specs)
+ }
+ return specs
+}
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index e78fdff..cda06d9 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -45,11 +45,11 @@
func TestFileSystemFillsLinkerConfigWithStubLibs(t *testing.T) {
result := fixture.RunTestWithBp(t, `
- android_system_image {
+ android_system_image {
name: "myfilesystem",
deps: [
"libfoo",
- "libbar",
+ "libbar",
],
linker_config_src: "linker.config.json",
}
@@ -74,3 +74,54 @@
android.AssertStringDoesNotContain(t, "linker.config.pb should not have libbar",
output.RuleParams.Command, "libbar.so")
}
+
+func registerComponent(ctx android.RegistrationContext) {
+ ctx.RegisterModuleType("component", componentFactory)
+}
+
+func componentFactory() android.Module {
+ m := &component{}
+ m.AddProperties(&m.properties)
+ android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
+ return m
+}
+
+type component struct {
+ android.ModuleBase
+ properties struct {
+ Install_copy_in_data []string
+ }
+}
+
+func (c *component) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ output := android.PathForModuleOut(ctx, c.Name())
+ dir := android.PathForModuleInstall(ctx, "components")
+ ctx.InstallFile(dir, c.Name(), output)
+
+ dataDir := android.PathForModuleInPartitionInstall(ctx, "data", "components")
+ for _, d := range c.properties.Install_copy_in_data {
+ ctx.InstallFile(dataDir, d, output)
+ }
+}
+
+func TestFileSystemGathersItemsOnlyInSystemPartition(t *testing.T) {
+ f := android.GroupFixturePreparers(fixture, android.FixtureRegisterWithContext(registerComponent))
+ result := f.RunTestWithBp(t, `
+ android_system_image {
+ name: "myfilesystem",
+ multilib: {
+ common: {
+ deps: ["foo"],
+ },
+ },
+ linker_config_src: "linker.config.json",
+ }
+ component {
+ name: "foo",
+ install_copy_in_data: ["bar"],
+ }
+ `)
+
+ module := result.ModuleForTests("myfilesystem", "android_common").Module().(*systemImage)
+ android.AssertDeepEquals(t, "entries should have foo only", []string{"components/foo"}, module.entries)
+}
diff --git a/filesystem/system_image.go b/filesystem/system_image.go
index 1d24d6d..75abf70 100644
--- a/filesystem/system_image.go
+++ b/filesystem/system_image.go
@@ -37,6 +37,7 @@
module := &systemImage{}
module.AddProperties(&module.properties)
module.filesystem.buildExtraFiles = module.buildExtraFiles
+ module.filesystem.filterPackagingSpecs = module.filterPackagingSpecs
initFilesystemModule(&module.filesystem)
return module
}
@@ -53,7 +54,7 @@
// we need "Module"s for packaging items
var otherModules []android.Module
- deps := s.GatherPackagingSpecs(ctx)
+ deps := s.gatherFilteredPackagingSpecs(ctx)
ctx.WalkDeps(func(child, parent android.Module) bool {
for _, ps := range child.PackagingSpecs() {
if _, ok := deps[ps.RelPathInPackage()]; ok {
@@ -68,3 +69,14 @@
builder.Build("conv_linker_config", "Generate linker config protobuf "+output.String())
return output
}
+
+// Filter the result of GatherPackagingSpecs to discard items targeting outside "system" partition.
+// Note that "apex" module installs its contents to "apex"(fake partition) as well
+// for symbol lookup by imitating "activated" paths.
+func (s *systemImage) filterPackagingSpecs(specs map[string]android.PackagingSpec) {
+ for k, ps := range specs {
+ if ps.Partition() != "system" {
+ delete(specs, k)
+ }
+ }
+}
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 96fd61a..5b1daa4 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,10 @@
}
a.onDeviceDir = android.InstallPathToOnDevicePath(ctx, a.installDir)
- a.noticeBuildActions(ctx)
+ noticeFile := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
+ android.BuildNoticeHtmlOutputFromLicenseMetadata(ctx, noticeFile)
if Bool(a.appProperties.Embed_notices) || ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") {
- a.aapt.noticeFile = a.noticeOutputs.HtmlGzOutput
+ a.aapt.noticeFile = android.OptionalPathForPath(noticeFile)
}
a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
@@ -1450,7 +1401,8 @@
}
type bazelAndroidAppAttributes struct {
- *javaLibraryAttributes
+ *javaCommonAttributes
+ Deps bazel.LabelListAttribute
Manifest bazel.Label
Custom_package *string
Resource_files bazel.LabelListAttribute
@@ -1460,7 +1412,16 @@
// ConvertWithBp2build is used to convert android_app to Bazel.
func (a *AndroidApp) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
- libAttrs := a.convertLibraryAttrsBp2Build(ctx)
+ commonAttrs, depLabels := a.convertLibraryAttrsBp2Build(ctx)
+
+ deps := depLabels.Deps
+ if !commonAttrs.Srcs.IsEmpty() {
+ deps.Append(depLabels.StaticDeps) // we should only append these if there are sources to use them
+ } else if !deps.IsEmpty() || !depLabels.StaticDeps.IsEmpty() {
+ ctx.ModuleErrorf("android_app has dynamic or static dependencies but no sources." +
+ " Bazel does not allow direct dependencies without sources nor exported" +
+ " dependencies on android_binary rule.")
+ }
manifest := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml")
@@ -1483,7 +1444,8 @@
}
attrs := &bazelAndroidAppAttributes{
- libAttrs,
+ commonAttrs,
+ deps,
android.BazelLabelForModuleSrcSingle(ctx, manifest),
// TODO(b/209576404): handle package name override by product variable PRODUCT_MANIFEST_PACKAGE_NAME_OVERRIDES
a.overridableAppProperties.Package_name,
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/droidstubs.go b/java/droidstubs.go
index 2921c3e..e7aeeb8 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -477,7 +477,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 ddef34d..ecbbc32 100644
--- a/java/java.go
+++ b/java/java.go
@@ -1725,6 +1725,7 @@
android.InitPrebuiltModule(module, &module.properties.Jars)
android.InitApexModule(module)
+ android.InitBazelModule(module)
InitJavaModule(module, android.HostSupported)
return module
}
@@ -2023,13 +2024,24 @@
}
}
-type javaLibraryAttributes struct {
+type javaCommonAttributes struct {
Srcs bazel.LabelListAttribute
- Deps bazel.LabelListAttribute
+ Plugins bazel.LabelListAttribute
Javacopts bazel.StringListAttribute
}
-func (m *Library) convertLibraryAttrsBp2Build(ctx android.TopDownMutatorContext) *javaLibraryAttributes {
+type javaDependencyLabels struct {
+ // Dependencies which DO NOT contribute to the API visible to upstream dependencies.
+ Deps bazel.LabelListAttribute
+ // Dependencies which DO contribute to the API visible to upstream dependencies.
+ StaticDeps bazel.LabelListAttribute
+}
+
+// convertLibraryAttrsBp2Build converts a few shared attributes from java_* modules
+// and also separates dependencies into dynamic dependencies and static dependencies.
+// Each corresponding Bazel target type, can have a different method for handling
+// dynamic vs. static dependencies, and so these are returned to the calling function.
+func (m *Library) convertLibraryAttrsBp2Build(ctx android.TopDownMutatorContext) (*javaCommonAttributes, *javaDependencyLabels) {
var srcs bazel.LabelListAttribute
archVariantProps := m.GetArchVariantProperties(ctx, &CommonProperties{})
for axis, configToProps := range archVariantProps {
@@ -2048,40 +2060,71 @@
protoSrcPartition: android.ProtoSrcLabelPartition,
})
- attrs := &javaLibraryAttributes{
+ commonAttrs := &javaCommonAttributes{
Srcs: srcPartitions[javaSrcPartition],
+ Plugins: bazel.MakeLabelListAttribute(
+ android.BazelLabelForModuleDeps(ctx, m.properties.Plugins),
+ ),
}
if m.properties.Javacflags != nil {
- attrs.Javacopts = bazel.MakeStringListAttribute(m.properties.Javacflags)
+ commonAttrs.Javacopts = bazel.MakeStringListAttribute(m.properties.Javacflags)
}
+ depLabels := &javaDependencyLabels{}
+
var deps bazel.LabelList
- sdkVersion := m.SdkVersion(ctx)
- if sdkVersion.Kind == android.SdkPublic && sdkVersion.ApiLevel == android.FutureApiLevel {
- // TODO(b/220869005) remove forced dependency on current public android.jar
- deps.Add(&bazel.Label{Label: "//prebuilts/sdk:public_current_android_sdk_java_import"})
- }
if m.properties.Libs != nil {
deps.Append(android.BazelLabelForModuleDeps(ctx, m.properties.Libs))
}
+
+ var staticDeps bazel.LabelList
if m.properties.Static_libs != nil {
- //TODO(b/217236083) handle static libs similarly to Soong
- deps.Append(android.BazelLabelForModuleDeps(ctx, m.properties.Static_libs))
+ staticDeps.Append(android.BazelLabelForModuleDeps(ctx, m.properties.Static_libs))
}
- protoDeps := bp2buildProto(ctx, &m.Module, srcPartitions[protoSrcPartition])
- if protoDeps != nil {
- deps.Add(protoDeps)
- }
+ protoDepLabel := bp2buildProto(ctx, &m.Module, srcPartitions[protoSrcPartition])
+ // Soong does not differentiate between a java_library and the Bazel equivalent of
+ // a java_proto_library + proto_library pair. Instead, in Soong proto sources are
+ // listed directly in the srcs of a java_library, and the classes produced
+ // by protoc are included directly in the resulting JAR. Thus upstream dependencies
+ // that depend on a java_library with proto sources can link directly to the protobuf API,
+ // and so this should be a static dependency.
+ staticDeps.Add(protoDepLabel)
- attrs.Deps = bazel.MakeLabelListAttribute(deps)
+ depLabels.Deps = bazel.MakeLabelListAttribute(deps)
+ depLabels.StaticDeps = bazel.MakeLabelListAttribute(staticDeps)
- return attrs
+ return commonAttrs, depLabels
+}
+
+type javaLibraryAttributes struct {
+ *javaCommonAttributes
+ Deps bazel.LabelListAttribute
+ Exports bazel.LabelListAttribute
}
func javaLibraryBp2Build(ctx android.TopDownMutatorContext, m *Library) {
- attrs := m.convertLibraryAttrsBp2Build(ctx)
+ commonAttrs, depLabels := m.convertLibraryAttrsBp2Build(ctx)
+
+ deps := depLabels.Deps
+ if !commonAttrs.Srcs.IsEmpty() {
+ deps.Append(depLabels.StaticDeps) // we should only append these if there are sources to use them
+
+ sdkVersion := m.SdkVersion(ctx)
+ if sdkVersion.Kind == android.SdkPublic && sdkVersion.ApiLevel == android.FutureApiLevel {
+ // TODO(b/220869005) remove forced dependency on current public android.jar
+ deps.Add(bazel.MakeLabelAttribute("//prebuilts/sdk:public_current_android_sdk_java_import"))
+ }
+ } else if !depLabels.Deps.IsEmpty() {
+ ctx.ModuleErrorf("Module has direct dependencies but no sources. Bazel will not allow this.")
+ }
+
+ attrs := &javaLibraryAttributes{
+ javaCommonAttributes: commonAttrs,
+ Deps: deps,
+ Exports: depLabels.StaticDeps,
+ }
props := bazel.BazelTargetModuleProperties{
Rule_class: "java_library",
@@ -2092,15 +2135,30 @@
}
type javaBinaryHostAttributes struct {
- Srcs bazel.LabelListAttribute
- Deps bazel.LabelListAttribute
- Main_class string
- Jvm_flags bazel.StringListAttribute
- Javacopts bazel.StringListAttribute
+ *javaCommonAttributes
+ Deps bazel.LabelListAttribute
+ Runtime_deps bazel.LabelListAttribute
+ Main_class string
+ Jvm_flags bazel.StringListAttribute
}
// JavaBinaryHostBp2Build is for java_binary_host bp2build.
func javaBinaryHostBp2Build(ctx android.TopDownMutatorContext, m *Binary) {
+ commonAttrs, depLabels := m.convertLibraryAttrsBp2Build(ctx)
+
+ deps := depLabels.Deps
+ deps.Append(depLabels.StaticDeps)
+ if m.binaryProperties.Jni_libs != nil {
+ deps.Append(bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, m.binaryProperties.Jni_libs)))
+ }
+
+ var runtimeDeps bazel.LabelListAttribute
+ if commonAttrs.Srcs.IsEmpty() {
+ // if there are no sources, then the dependencies can only be used at runtime
+ runtimeDeps = deps
+ deps = bazel.LabelListAttribute{}
+ }
+
mainClass := ""
if m.binaryProperties.Main_class != nil {
mainClass = *m.binaryProperties.Main_class
@@ -2112,26 +2170,12 @@
}
mainClass = mainClassInManifest
}
- srcs := bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrcExcludes(ctx, m.properties.Srcs, m.properties.Exclude_srcs))
+
attrs := &javaBinaryHostAttributes{
- Srcs: srcs,
- Main_class: mainClass,
- }
-
- if m.properties.Javacflags != nil {
- attrs.Javacopts = bazel.MakeStringListAttribute(m.properties.Javacflags)
- }
-
- // Attribute deps
- deps := []string{}
- if m.properties.Static_libs != nil {
- deps = append(deps, m.properties.Static_libs...)
- }
- if m.binaryProperties.Jni_libs != nil {
- deps = append(deps, m.binaryProperties.Jni_libs...)
- }
- if len(deps) > 0 {
- attrs.Deps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, deps))
+ javaCommonAttributes: commonAttrs,
+ Deps: deps,
+ Runtime_deps: runtimeDeps,
+ Main_class: mainClass,
}
// Attribute jvm_flags
@@ -2174,8 +2218,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/java/plugin.go b/java/plugin.go
index 4b174b9..123dbd4 100644
--- a/java/plugin.go
+++ b/java/plugin.go
@@ -58,27 +58,32 @@
}
type pluginAttributes struct {
- *javaLibraryAttributes
- Processor_class *string
- Target_compatible_with bazel.LabelListAttribute
+ *javaCommonAttributes
+ Deps bazel.LabelListAttribute
+ Processor_class *string
}
// ConvertWithBp2build is used to convert android_app to Bazel.
func (p *Plugin) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
- libAttrs := p.convertLibraryAttrsBp2Build(ctx)
- attrs := &pluginAttributes{
- libAttrs,
- nil,
- bazel.LabelListAttribute{},
+ pluginName := p.Name()
+ commonAttrs, depLabels := p.convertLibraryAttrsBp2Build(ctx)
+
+ deps := depLabels.Deps
+ deps.Append(depLabels.StaticDeps)
+
+ var processorClass *string
+ if p.pluginProperties.Processor_class != nil {
+ processorClass = p.pluginProperties.Processor_class
}
- if p.pluginProperties.Processor_class != nil {
- attrs.Processor_class = p.pluginProperties.Processor_class
+ attrs := &pluginAttributes{
+ javaCommonAttributes: commonAttrs,
+ Deps: deps,
+ Processor_class: processorClass,
}
props := bazel.BazelTargetModuleProperties{
Rule_class: "java_plugin",
}
-
- ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: p.Name()}, attrs)
+ ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: pluginName}, attrs)
}
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index bc36b20..30700dd 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -26,6 +26,7 @@
"packages/modules/Virtualization",
"platform_testing/tests/codecoverage/native/rust",
"prebuilts/rust",
+ "system/core/debuggerd/rust",
"system/core/libstats/pull_rust",
"system/extras/profcollectd",
"system/extras/simpleperf",
diff --git a/rust/config/x86_linux_host.go b/rust/config/x86_linux_host.go
index 7608349..4d7c422 100644
--- a/rust/config/x86_linux_host.go
+++ b/rust/config/x86_linux_host.go
@@ -42,8 +42,6 @@
"-nodefaultlibs",
"-nostdlib",
"-Wl,--no-dynamic-linker",
- // for unwind
- "-lgcc", "-lgcc_eh",
}
linuxX86Rustflags = []string{}
linuxX86Linkflags = []string{}
diff --git a/rust/prebuilt.go b/rust/prebuilt.go
index 6f17272..6cdd07d 100644
--- a/rust/prebuilt.go
+++ b/rust/prebuilt.go
@@ -22,6 +22,7 @@
android.RegisterModuleType("rust_prebuilt_library", PrebuiltLibraryFactory)
android.RegisterModuleType("rust_prebuilt_dylib", PrebuiltDylibFactory)
android.RegisterModuleType("rust_prebuilt_rlib", PrebuiltRlibFactory)
+ android.RegisterModuleType("rust_prebuilt_proc_macro", PrebuiltProcMacroFactory)
}
type PrebuiltProperties struct {
@@ -38,8 +39,42 @@
Properties PrebuiltProperties
}
+type prebuiltProcMacroDecorator struct {
+ android.Prebuilt
+
+ *procMacroDecorator
+ Properties PrebuiltProperties
+}
+
+func PrebuiltProcMacroFactory() android.Module {
+ module, _ := NewPrebuiltProcMacro(android.HostSupportedNoCross)
+ return module.Init()
+}
+
+type rustPrebuilt interface {
+ prebuiltSrcs() []string
+ prebuilt() *android.Prebuilt
+}
+
+func NewPrebuiltProcMacro(hod android.HostOrDeviceSupported) (*Module, *prebuiltProcMacroDecorator) {
+ module, library := NewProcMacro(hod)
+ prebuilt := &prebuiltProcMacroDecorator{
+ procMacroDecorator: library,
+ }
+ module.compiler = prebuilt
+
+ addSrcSupplier(module, prebuilt)
+
+ return module, prebuilt
+}
+
var _ compiler = (*prebuiltLibraryDecorator)(nil)
var _ exportedFlagsProducer = (*prebuiltLibraryDecorator)(nil)
+var _ rustPrebuilt = (*prebuiltLibraryDecorator)(nil)
+
+var _ compiler = (*prebuiltProcMacroDecorator)(nil)
+var _ exportedFlagsProducer = (*prebuiltProcMacroDecorator)(nil)
+var _ rustPrebuilt = (*prebuiltProcMacroDecorator)(nil)
func PrebuiltLibraryFactory() android.Module {
module, _ := NewPrebuiltLibrary(android.HostAndDeviceSupported)
@@ -56,7 +91,7 @@
return module.Init()
}
-func addSrcSupplier(module android.PrebuiltInterface, prebuilt *prebuiltLibraryDecorator) {
+func addSrcSupplier(module android.PrebuiltInterface, prebuilt rustPrebuilt) {
srcsSupplier := func(_ android.BaseModuleContext, _ android.Module) []string {
return prebuilt.prebuiltSrcs()
}
@@ -152,3 +187,44 @@
func (prebuilt *prebuiltLibraryDecorator) prebuilt() *android.Prebuilt {
return &prebuilt.Prebuilt
}
+
+func (prebuilt *prebuiltProcMacroDecorator) prebuiltSrcs() []string {
+ srcs := prebuilt.Properties.Srcs
+ return srcs
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) prebuilt() *android.Prebuilt {
+ return &prebuilt.Prebuilt
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) compilerProps() []interface{} {
+ return append(prebuilt.procMacroDecorator.compilerProps(),
+ &prebuilt.Properties)
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
+ prebuilt.flagExporter.exportLinkDirs(android.PathsForModuleSrc(ctx, prebuilt.Properties.Link_dirs).Strings()...)
+ prebuilt.flagExporter.setProvider(ctx)
+
+ srcPath, paths := srcPathFromModuleSrcs(ctx, prebuilt.prebuiltSrcs())
+ if len(paths) > 0 {
+ ctx.PropertyErrorf("srcs", "prebuilt libraries can only have one entry in srcs (the prebuilt path)")
+ }
+ prebuilt.baseCompiler.unstrippedOutputFile = srcPath
+ return srcPath
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) rustdoc(ctx ModuleContext, flags Flags,
+ deps PathDeps) android.OptionalPath {
+
+ return android.OptionalPath{}
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) compilerDeps(ctx DepsContext, deps Deps) Deps {
+ deps = prebuilt.baseCompiler.compilerDeps(ctx, deps)
+ return deps
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) nativeCoverage() bool {
+ return false
+}
diff --git a/rust/proc_macro.go b/rust/proc_macro.go
index 974c096..f8a4bbd 100644
--- a/rust/proc_macro.go
+++ b/rust/proc_macro.go
@@ -33,6 +33,7 @@
}
type procMacroInterface interface {
+ ProcMacro() bool
}
var _ compiler = (*procMacroDecorator)(nil)
@@ -90,6 +91,10 @@
return rlibAutoDep
}
+func (procMacro *procMacroDecorator) ProcMacro() bool {
+ return true
+}
+
func (procMacro *procMacroDecorator) everInstallable() bool {
// Proc_macros are never installed
return false
diff --git a/rust/rust.go b/rust/rust.go
index 1c718a4..d627261 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -27,6 +27,7 @@
cc_config "android/soong/cc/config"
"android/soong/fuzz"
"android/soong/rust/config"
+ "android/soong/snapshot"
)
var pctx = android.NewPackageContext("android/soong/rust")
@@ -806,6 +807,13 @@
return mod.Properties.Installable
}
+func (mod *Module) ProcMacro() bool {
+ if pm, ok := mod.compiler.(procMacroInterface); ok {
+ return pm.ProcMacro()
+ }
+ return false
+}
+
func (mod *Module) toolchain(ctx android.BaseModuleContext) config.Toolchain {
if mod.cachedToolchain == nil {
mod.cachedToolchain = config.FindToolchain(ctx.Os(), ctx.Arch())
@@ -920,12 +928,13 @@
}
apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
- if !proptools.BoolDefault(mod.Installable(), mod.EverInstallable()) {
+ if !proptools.BoolDefault(mod.Installable(), mod.EverInstallable()) && !mod.ProcMacro() {
// If the module has been specifically configure to not be installed then
// hide from make as otherwise it will break when running inside make as the
// output path to install will not be specified. Not all uninstallable
// modules can be hidden from make as some are needed for resolving make
- // side dependencies.
+ // side dependencies. In particular, proc-macros need to be captured in the
+ // host snapshot.
mod.HideFromMake()
} else if !mod.installable(apexInfo) {
mod.SkipInstall()
@@ -1046,7 +1055,7 @@
}
func (mod *Module) Prebuilt() *android.Prebuilt {
- if p, ok := mod.compiler.(*prebuiltLibraryDecorator); ok {
+ if p, ok := mod.compiler.(rustPrebuilt); ok {
return p.prebuilt()
}
return nil
@@ -1501,6 +1510,7 @@
}
var _ android.HostToolProvider = (*Module)(nil)
+var _ snapshot.RelativeInstallPath = (*Module)(nil)
func (mod *Module) HostToolPath() android.OptionalPath {
if !mod.Host() {
@@ -1508,6 +1518,10 @@
}
if binary, ok := mod.compiler.(*binaryDecorator); ok {
return android.OptionalPathForPath(binary.baseCompiler.path)
+ } else if pm, ok := mod.compiler.(*procMacroDecorator); ok {
+ // Even though proc-macros aren't strictly "tools", since they target the compiler
+ // and act as compiler plugins, we treat them similarly.
+ return android.OptionalPathForPath(pm.baseCompiler.path)
}
return android.OptionalPath{}
}
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/Android.bp b/scripts/hiddenapi/Android.bp
index 8a47c5d..07878f9 100644
--- a/scripts/hiddenapi/Android.bp
+++ b/scripts/hiddenapi/Android.bp
@@ -19,6 +19,52 @@
}
python_binary_host {
+ name: "analyze_bcpf",
+ main: "analyze_bcpf.py",
+ srcs: ["analyze_bcpf.py"],
+ // Make sure that the bpmodify tool is built.
+ data: [":bpmodify"],
+ libs: [
+ "signature_trie",
+ ],
+ version: {
+ py2: {
+ enabled: false,
+ },
+ py3: {
+ enabled: true,
+ embedded_launcher: true,
+ },
+ },
+}
+
+python_test_host {
+ name: "analyze_bcpf_test",
+ main: "analyze_bcpf_test.py",
+ srcs: [
+ "analyze_bcpf.py",
+ "analyze_bcpf_test.py",
+ ],
+ // Make sure that the bpmodify tool is built.
+ data: [":bpmodify"],
+ libs: [
+ "signature_trie",
+ ],
+ version: {
+ py2: {
+ enabled: false,
+ },
+ py3: {
+ enabled: true,
+ embedded_launcher: true,
+ },
+ },
+ test_options: {
+ unit_test: true,
+ },
+}
+
+python_binary_host {
name: "merge_csv",
main: "merge_csv.py",
srcs: ["merge_csv.py"],
diff --git a/scripts/hiddenapi/analyze_bcpf.py b/scripts/hiddenapi/analyze_bcpf.py
new file mode 100644
index 0000000..1ad8d07
--- /dev/null
+++ b/scripts/hiddenapi/analyze_bcpf.py
@@ -0,0 +1,1336 @@
+#!/usr/bin/env -S python -u
+#
+# 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.
+"""Analyze bootclasspath_fragment usage."""
+import argparse
+import dataclasses
+import enum
+import json
+import logging
+import os
+import re
+import shutil
+import subprocess
+import tempfile
+import textwrap
+import typing
+from enum import Enum
+
+import sys
+
+from signature_trie import signature_trie
+
+_STUB_FLAGS_FILE = "out/soong/hiddenapi/hiddenapi-stub-flags.txt"
+
+_FLAGS_FILE = "out/soong/hiddenapi/hiddenapi-flags.csv"
+
+_INCONSISTENT_FLAGS = "ERROR: Hidden API flags are inconsistent:"
+
+
+class BuildOperation:
+
+ def __init__(self, popen):
+ self.popen = popen
+ self.returncode = None
+
+ def lines(self):
+ """Return an iterator over the lines output by the build operation.
+
+ The lines have had any trailing white space, including the newline
+ stripped.
+ """
+ return newline_stripping_iter(self.popen.stdout.readline)
+
+ def wait(self, *args, **kwargs):
+ self.popen.wait(*args, **kwargs)
+ self.returncode = self.popen.returncode
+
+
+@dataclasses.dataclass()
+class FlagDiffs:
+ """Encapsulates differences in flags reported by the build"""
+
+ # Map from member signature to the (module flags, monolithic flags)
+ diffs: typing.Dict[str, typing.Tuple[str, str]]
+
+
+@dataclasses.dataclass()
+class ModuleInfo:
+ """Provides access to the generated module-info.json file.
+
+ This is used to find the location of the file within which specific modules
+ are defined.
+ """
+
+ modules: typing.Dict[str, typing.Dict[str, typing.Any]]
+
+ @staticmethod
+ def load(filename):
+ with open(filename, "r", encoding="utf8") as f:
+ j = json.load(f)
+ return ModuleInfo(j)
+
+ def _module(self, module_name):
+ """Find module by name in module-info.json file"""
+ if module_name in self.modules:
+ return self.modules[module_name]
+
+ raise Exception(f"Module {module_name} could not be found")
+
+ def module_path(self, module_name):
+ module = self._module(module_name)
+ # The "path" is actually a list of paths, one for each class of module
+ # but as the modules are all created from bp files if a module does
+ # create multiple classes of make modules they should all have the same
+ # path.
+ paths = module["path"]
+ unique_paths = set(paths)
+ if len(unique_paths) != 1:
+ raise Exception(f"Expected module '{module_name}' to have a "
+ f"single unique path but found {unique_paths}")
+ return paths[0]
+
+
+def extract_indent(line):
+ return re.match(r"([ \t]*)", line).group(1)
+
+
+_SPECIAL_PLACEHOLDER: str = "SPECIAL_PLACEHOLDER"
+
+
+@dataclasses.dataclass
+class BpModifyRunner:
+
+ bpmodify_path: str
+
+ def add_values_to_property(self, property_name, values, module_name,
+ bp_file):
+ cmd = [
+ self.bpmodify_path, "-a", values, "-property", property_name, "-m",
+ module_name, "-w", bp_file, bp_file
+ ]
+
+ logging.debug(" ".join(cmd))
+ subprocess.run(
+ cmd,
+ stderr=subprocess.STDOUT,
+ stdout=log_stream_for_subprocess(),
+ check=True)
+
+
+@dataclasses.dataclass
+class FileChange:
+ path: str
+
+ description: str
+
+ def __lt__(self, other):
+ return self.path < other.path
+
+
+class PropertyChangeAction(Enum):
+ """Allowable actions that are supported by HiddenApiPropertyChange."""
+
+ # New values are appended to any existing values.
+ APPEND = 1
+
+ # New values replace any existing values.
+ REPLACE = 2
+
+
+@dataclasses.dataclass
+class HiddenApiPropertyChange:
+
+ property_name: str
+
+ values: typing.List[str]
+
+ property_comment: str = ""
+
+ # The action that indicates how this change is applied.
+ action: PropertyChangeAction = PropertyChangeAction.APPEND
+
+ def snippet(self, indent):
+ snippet = "\n"
+ snippet += format_comment_as_text(self.property_comment, indent)
+ snippet += f"{indent}{self.property_name}: ["
+ if self.values:
+ snippet += "\n"
+ for value in self.values:
+ snippet += f'{indent} "{value}",\n'
+ snippet += f"{indent}"
+ snippet += "],\n"
+ return snippet
+
+ def fix_bp_file(self, bcpf_bp_file, bcpf, bpmodify_runner: BpModifyRunner):
+ # Add an additional placeholder value to identify the modification that
+ # bpmodify makes.
+ bpmodify_values = [_SPECIAL_PLACEHOLDER]
+
+ if self.action == PropertyChangeAction.APPEND:
+ # If adding the values to the existing values then pass the new
+ # values to bpmodify.
+ bpmodify_values.extend(self.values)
+ elif self.action == PropertyChangeAction.REPLACE:
+ # If replacing the existing values then it is not possible to use
+ # bpmodify for that directly. It could be used twice to remove the
+ # existing property and then add a new one but that does not remove
+ # any related comments and loses the position of the existing
+ # property as the new property is always added to the end of the
+ # containing block.
+ #
+ # So, instead of passing the new values to bpmodify this this just
+ # adds an extra placeholder to force bpmodify to format the list
+ # across multiple lines to ensure a consistent structure for the
+ # code that removes all the existing values and adds the new ones.
+ #
+ # This placeholder has to be different to the other placeholder as
+ # bpmodify dedups values.
+ bpmodify_values.append(_SPECIAL_PLACEHOLDER + "_REPLACE")
+ else:
+ raise ValueError(f"unknown action {self.action}")
+
+ packages = ",".join(bpmodify_values)
+ bpmodify_runner.add_values_to_property(
+ f"hidden_api.{self.property_name}", packages, bcpf, bcpf_bp_file)
+
+ with open(bcpf_bp_file, "r", encoding="utf8") as tio:
+ lines = tio.readlines()
+ lines = [line.rstrip("\n") for line in lines]
+
+ if self.fixup_bpmodify_changes(bcpf_bp_file, lines):
+ with open(bcpf_bp_file, "w", encoding="utf8") as tio:
+ for line in lines:
+ print(line, file=tio)
+
+ def fixup_bpmodify_changes(self, bcpf_bp_file, lines):
+ """Fixup the output of bpmodify.
+
+ The bpmodify tool does not support all the capabilities that this needs
+ so it is used to do what it can, including marking the place in the
+ Android.bp file where it makes its changes and then this gets passed a
+ list of lines from that file which it then modifies to complete the
+ change.
+
+ This analyzes the list of lines to find the indices of the significant
+ lines and then applies some changes. As those changes can insert and
+ delete lines (changing the indices of following lines) the changes are
+ generally done in reverse order starting from the end and working
+ towards the beginning. That ensures that the changes do not invalidate
+ the indices of following lines.
+ """
+
+ # Find the line containing the placeholder that has been inserted.
+ place_holder_index = -1
+ for i, line in enumerate(lines):
+ if _SPECIAL_PLACEHOLDER in line:
+ place_holder_index = i
+ break
+ if place_holder_index == -1:
+ logging.debug("Could not find %s in %s", _SPECIAL_PLACEHOLDER,
+ bcpf_bp_file)
+ return False
+
+ # Remove the place holder. Do this before inserting the comment as that
+ # would change the location of the place holder in the list.
+ place_holder_line = lines[place_holder_index]
+ if place_holder_line.endswith("],"):
+ place_holder_line = place_holder_line.replace(
+ f'"{_SPECIAL_PLACEHOLDER}"', "")
+ lines[place_holder_index] = place_holder_line
+ else:
+ del lines[place_holder_index]
+
+ # Scan forward to the end of the property block to remove a blank line
+ # that bpmodify inserts.
+ end_property_array_index = -1
+ for i in range(place_holder_index, len(lines)):
+ line = lines[i]
+ if line.endswith("],"):
+ end_property_array_index = i
+ break
+ if end_property_array_index == -1:
+ logging.debug("Could not find end of property array in %s",
+ bcpf_bp_file)
+ return False
+
+ # If bdmodify inserted a blank line afterwards then remove it.
+ if (not lines[end_property_array_index + 1] and
+ lines[end_property_array_index + 2].endswith("},")):
+ del lines[end_property_array_index + 1]
+
+ # Scan back to find the preceding property line.
+ property_line_index = -1
+ for i in range(place_holder_index, 0, -1):
+ line = lines[i]
+ if line.lstrip().startswith(f"{self.property_name}: ["):
+ property_line_index = i
+ break
+ if property_line_index == -1:
+ logging.debug("Could not find property line in %s", bcpf_bp_file)
+ return False
+
+ # If this change is replacing the existing values then they need to be
+ # removed and replaced with the new values. That will change the lines
+ # after the property but it is necessary to do here as the following
+ # code operates on earlier lines.
+ if self.action == PropertyChangeAction.REPLACE:
+ # This removes the existing values and replaces them with the new
+ # values.
+ indent = extract_indent(lines[property_line_index + 1])
+ insert = [f'{indent}"{x}",' for x in self.values]
+ lines[property_line_index + 1:end_property_array_index] = insert
+ if not self.values:
+ # If the property has no values then merge the ], onto the
+ # same line as the property name.
+ del lines[property_line_index + 1]
+ lines[property_line_index] = lines[property_line_index] + "],"
+
+ # Only insert a comment if the property does not already have a comment.
+ line_preceding_property = lines[(property_line_index - 1)]
+ if (self.property_comment and
+ not re.match("([ \t]+)// ", line_preceding_property)):
+ # Extract the indent from the property line and use it to format the
+ # comment.
+ indent = extract_indent(lines[property_line_index])
+ comment_lines = format_comment_as_lines(self.property_comment,
+ indent)
+
+ # If the line before the comment is not blank then insert an extra
+ # blank line at the beginning of the comment.
+ if line_preceding_property:
+ comment_lines.insert(0, "")
+
+ # Insert the comment before the property.
+ lines[property_line_index:property_line_index] = comment_lines
+ return True
+
+
+@dataclasses.dataclass()
+class Result:
+ """Encapsulates the result of the analysis."""
+
+ # The diffs in the flags.
+ diffs: typing.Optional[FlagDiffs] = None
+
+ # The bootclasspath_fragment hidden API properties changes.
+ property_changes: typing.List[HiddenApiPropertyChange] = dataclasses.field(
+ default_factory=list)
+
+ # The list of file changes.
+ file_changes: typing.List[FileChange] = dataclasses.field(
+ default_factory=list)
+
+
+class ClassProvider(enum.Enum):
+ """The source of a class found during the hidden API processing"""
+ BCPF = "bcpf"
+ OTHER = "other"
+
+
+# A fake member to use when using the signature trie to compute the package
+# properties from hidden API flags. This is needed because while that
+# computation only cares about classes the trie expects a class to be an
+# interior node but without a member it makes the class a leaf node. That causes
+# problems when analyzing inner classes as the outer class is a leaf node for
+# its own entry but is used as an interior node for inner classes.
+_FAKE_MEMBER = ";->fake()V"
+
+
+@dataclasses.dataclass()
+class BcpfAnalyzer:
+ # Path to this tool.
+ tool_path: str
+
+ # Directory pointed to by ANDROID_BUILD_OUT
+ top_dir: str
+
+ # Directory pointed to by OUT_DIR of {top_dir}/out if that is not set.
+ out_dir: str
+
+ # Directory pointed to by ANDROID_PRODUCT_OUT.
+ product_out_dir: str
+
+ # The name of the bootclasspath_fragment module.
+ bcpf: str
+
+ # The name of the apex module containing {bcpf}, only used for
+ # informational purposes.
+ apex: str
+
+ # The name of the sdk module containing {bcpf}, only used for
+ # informational purposes.
+ sdk: str
+
+ # If true then this will attempt to automatically fix any issues that are
+ # found.
+ fix: bool = False
+
+ # All the signatures, loaded from all-flags.csv, initialized by
+ # load_all_flags().
+ _signatures: typing.Set[str] = dataclasses.field(default_factory=set)
+
+ # All the classes, loaded from all-flags.csv, initialized by
+ # load_all_flags().
+ _classes: typing.Set[str] = dataclasses.field(default_factory=set)
+
+ # Information loaded from module-info.json, initialized by
+ # load_module_info().
+ module_info: ModuleInfo = None
+
+ @staticmethod
+ def reformat_report_test(text):
+ return re.sub(r"(.)\n([^\s])", r"\1 \2", text)
+
+ 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 run_command(self, cmd, *args, **kwargs):
+ cmd_line = " ".join(cmd)
+ logging.debug("Running %s", cmd_line)
+ subprocess.run(
+ cmd,
+ *args,
+ check=True,
+ cwd=self.top_dir,
+ stderr=subprocess.STDOUT,
+ stdout=log_stream_for_subprocess(),
+ text=True,
+ **kwargs)
+
+ @property
+ def signatures(self):
+ if not self._signatures:
+ raise Exception("signatures has not been initialized")
+ return self._signatures
+
+ @property
+ def classes(self):
+ if not self._classes:
+ raise Exception("classes has not been initialized")
+ return self._classes
+
+ def load_all_flags(self):
+ all_flags = self.find_bootclasspath_fragment_output_file(
+ "all-flags.csv")
+
+ # Extract the set of signatures and a separate set of classes produced
+ # by the bootclasspath_fragment.
+ with open(all_flags, "r", encoding="utf8") as f:
+ for line in newline_stripping_iter(f.readline):
+ signature = self.line_to_signature(line)
+ self._signatures.add(signature)
+ class_name = self.signature_to_class(signature)
+ self._classes.add(class_name)
+
+ 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.
+""")
+ output = self.build_file_read_output(module_info_file)
+ lines = output.lines()
+ for line in lines:
+ logging.debug("%s", line)
+ output.wait(timeout=10)
+ if output.returncode:
+ raise Exception(f"Error building {module_info_file}")
+ abs_module_info_file = os.path.join(self.top_dir, module_info_file)
+ self.module_info = ModuleInfo.load(abs_module_info_file)
+
+ @staticmethod
+ def line_to_signature(line):
+ return line.split(",")[0]
+
+ @staticmethod
+ def signature_to_class(signature):
+ return signature.split(";->")[0]
+
+ @staticmethod
+ def to_parent_package(pkg_or_class):
+ return pkg_or_class.rsplit("/", 1)[0]
+
+ def module_path(self, module_name):
+ return self.module_info.module_path(module_name)
+
+ def module_out_dir(self, module_name):
+ module_path = self.module_path(module_name)
+ return os.path.join(self.out_dir, "soong/.intermediates", module_path,
+ module_name)
+
+ def find_bootclasspath_fragment_output_file(self, basename, required=True):
+ # Find the output file of the bootclasspath_fragment with the specified
+ # base name.
+ found_file = ""
+ bcpf_out_dir = self.module_out_dir(self.bcpf)
+ for (dirpath, _, filenames) in os.walk(bcpf_out_dir):
+ for f in filenames:
+ if f == basename:
+ found_file = os.path.join(dirpath, f)
+ break
+ if not found_file and required:
+ raise Exception(f"Could not find {basename} in {bcpf_out_dir}")
+ return found_file
+
+ def analyze(self):
+ """Analyze a bootclasspath_fragment module.
+
+ Provides help in resolving any existing issues and provides
+ 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:
+
+1. The current checkout is up to date.
+
+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:
+
+ 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:
+
+ 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}"],
+ }}
+""")
+
+ # Make sure that the module-info.json file is up to date.
+ self.load_module_info()
+
+ self.report("""
+Cleaning potentially stale files.
+""")
+ # Remove the out/soong/hiddenapi files.
+ shutil.rmtree(f"{self.out_dir}/soong/hiddenapi", ignore_errors=True)
+
+ # Remove any bootclasspath_fragment output files.
+ shutil.rmtree(self.module_out_dir(self.bcpf), ignore_errors=True)
+
+ self.build_monolithic_stubs_flags()
+
+ result = Result()
+
+ self.build_monolithic_flags(result)
+ self.analyze_hiddenapi_package_properties(result)
+ self.explain_how_to_check_signature_patterns()
+
+ # If there were any changes that need to be made to the Android.bp
+ # file then either apply or report them.
+ if result.property_changes:
+ bcpf_dir = self.module_info.module_path(self.bcpf)
+ bcpf_bp_file = os.path.join(self.top_dir, bcpf_dir, "Android.bp")
+ if self.fix:
+ tool_dir = os.path.dirname(self.tool_path)
+ bpmodify_path = os.path.join(tool_dir, "bpmodify")
+ bpmodify_runner = BpModifyRunner(bpmodify_path)
+ for property_change in result.property_changes:
+ property_change.fix_bp_file(bcpf_bp_file, self.bcpf,
+ bpmodify_runner)
+
+ result.file_changes.append(
+ self.new_file_change(
+ bcpf_bp_file,
+ f"Updated hidden_api properties of '{self.bcpf}'"))
+
+ else:
+ hiddenapi_snippet = ""
+ for property_change in result.property_changes:
+ hiddenapi_snippet += property_change.snippet(" ")
+
+ # Remove leading and trailing blank lines.
+ hiddenapi_snippet = hiddenapi_snippet.strip("\n")
+
+ result.file_changes.append(
+ self.new_file_change(
+ bcpf_bp_file, f"""
+Add the following snippet into the {self.bcpf} bootclasspath_fragment module
+in the {bcpf_dir}/Android.bp file. If the hidden_api block already exists then
+merge these properties into it.
+
+ hidden_api: {{
+{hiddenapi_snippet}
+ }},
+"""))
+
+ if result.file_changes:
+ if self.fix:
+ file_change_message = """
+The following files were modified by this script:"""
+ else:
+ file_change_message = """
+The following modifications need to be made:"""
+
+ self.report(f"""
+{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"))
+
+ if not self.fix:
+ self.report("""
+Run the command again with the --fix option to automatically make the above
+changes.
+""".lstrip())
+
+ def new_file_change(self, file, description):
+ return FileChange(
+ path=os.path.relpath(file, self.top_dir), description=description)
+
+ def check_inconsistent_flag_lines(self, significant, module_line,
+ monolithic_line, separator_line):
+ 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}"
+""")
+ sys.exit(1)
+
+ if significant:
+ logging.debug("%s", module_line)
+ logging.debug("%s", monolithic_line)
+ logging.debug("%s", separator_line)
+
+ def scan_inconsistent_flags_report(self, lines):
+ """Scans a hidden API flags report
+
+ The hidden API inconsistent flags report which looks something like
+ this.
+
+ < out/soong/.intermediates/.../filtered-stub-flags.csv
+ > out/soong/hiddenapi/hiddenapi-stub-flags.txt
+
+ < Landroid/compat/Compatibility;->clearOverrides()V
+ > Landroid/compat/Compatibility;->clearOverrides()V,core-platform-api
+
+ """
+
+ # The basic format of an entry in the inconsistent flags report is:
+ # <module specific flag>
+ # <monolithic flag>
+ # <separator>
+ #
+ # Wrap the lines iterator in an iterator which returns a tuple
+ # consisting of the three separate lines.
+ triples = zip(lines, lines, lines)
+
+ module_line, monolithic_line, separator_line = next(triples)
+ significant = False
+ bcpf_dir = self.module_info.module_path(self.bcpf)
+ if os.path.join(bcpf_dir, self.bcpf) in module_line:
+ # These errors are related to the bcpf being analyzed so
+ # keep them.
+ significant = True
+ else:
+ self.report(f"Filtering out errors related to {module_line}")
+
+ self.check_inconsistent_flag_lines(significant, module_line,
+ monolithic_line, separator_line)
+
+ diffs = {}
+ for module_line, monolithic_line, separator_line in triples:
+ self.check_inconsistent_flag_lines(significant, module_line,
+ monolithic_line, "")
+
+ module_parts = module_line.removeprefix("< ").split(",")
+ module_signature = module_parts[0]
+ module_flags = module_parts[1:]
+
+ monolithic_parts = monolithic_line.removeprefix("> ").split(",")
+ monolithic_signature = monolithic_parts[0]
+ monolithic_flags = monolithic_parts[1:]
+
+ if module_signature != monolithic_signature:
+ # Something went wrong.
+ self.report(f"""Inconsistent signatures detected:
+ module_signature: "{module_signature}"
+ monolithic_signature: "{monolithic_signature}"
+""")
+ sys.exit(1)
+
+ diffs[module_signature] = (module_flags, monolithic_flags)
+
+ if separator_line:
+ # If the separator line is not blank then it is the end of the
+ # current report, and possibly the start of another.
+ return separator_line, diffs
+
+ return "", diffs
+
+ def build_file_read_output(self, filename):
+ # Make sure the filename is relative to top if possible as the build
+ # may be using relative paths as the target.
+ rel_filename = filename.removeprefix(self.top_dir)
+ cmd = ["build/soong/soong_ui.bash", "--make-mode", rel_filename]
+ cmd_line = " ".join(cmd)
+ logging.debug("%s", cmd_line)
+ # pylint: disable=consider-using-with
+ output = subprocess.Popen(
+ cmd,
+ cwd=self.top_dir,
+ stderr=subprocess.STDOUT,
+ stdout=subprocess.PIPE,
+ text=True,
+ )
+ return BuildOperation(popen=output)
+
+ def build_hiddenapi_flags(self, filename):
+ output = self.build_file_read_output(filename)
+
+ lines = output.lines()
+ diffs = None
+ for line in lines:
+ logging.debug("%s", line)
+ while line == _INCONSISTENT_FLAGS:
+ line, diffs = self.scan_inconsistent_flags_report(lines)
+
+ output.wait(timeout=10)
+ if output.returncode != 0:
+ logging.debug("Command failed with %s", output.returncode)
+ else:
+ logging.debug("Command succeeded")
+
+ 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...
+""")
+
+ # 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:
+
+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:
+
+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.
+
+ 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.
+
+ 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}",
+ ...
+ 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.
+""")
+
+ 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...
+""")
+
+ # Build the hiddenapi-flags.csv file and extract any differences in
+ # the flags between this bootclasspath_fragment and the monolithic
+ # files.
+ result.diffs = self.build_hiddenapi_flags(_FLAGS_FILE)
+
+ # Load information from the bootclasspath_fragment's all-flags.csv file.
+ 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:
+
+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.
+
+2. The bootclasspath_fragment specifies invalid "package_prefixes" or
+ "split_packages" 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.check_frameworks_base_boot_hidden_api_files(result)
+
+ def report_hidden_api_flag_file_changes(self, result, property_name,
+ flags_file, rel_bcpf_flags_file,
+ bcpf_flags_file):
+ matched_signatures = set()
+ # Open the flags file to read the flags from.
+ with open(flags_file, "r", encoding="utf8") as f:
+ for signature in newline_stripping_iter(f.readline):
+ if signature in self.signatures:
+ # The signature is provided by the bootclasspath_fragment so
+ # it will need to be moved to the bootclasspath_fragment
+ # specific file.
+ matched_signatures.add(signature)
+
+ # If the bootclasspath_fragment specific flags file is not empty
+ # then it contains flags. That could either be new flags just moved
+ # from frameworks/base or previous contents of the file. In either
+ # case the file must not be removed.
+ if matched_signatures:
+ insert = textwrap.indent("\n".join(matched_signatures),
+ " ")
+ result.file_changes.append(
+ self.new_file_change(
+ flags_file, f"""Remove the following entries:
+{insert}
+"""))
+
+ result.file_changes.append(
+ self.new_file_change(
+ bcpf_flags_file, f"""Add the following entries:
+{insert}
+"""))
+
+ result.property_changes.append(
+ HiddenApiPropertyChange(
+ property_name=property_name,
+ values=[rel_bcpf_flags_file],
+ ))
+
+ def fix_hidden_api_flag_files(self, result, property_name, flags_file,
+ rel_bcpf_flags_file, bcpf_flags_file):
+ # Read the file in frameworks/base/boot/hiddenapi/<file> copy any
+ # flags that relate to the bootclasspath_fragment into a local
+ # file in the hiddenapi subdirectory.
+ tmp_flags_file = flags_file + ".tmp"
+
+ # Make sure the directory containing the bootclasspath_fragment specific
+ # hidden api flags exists.
+ os.makedirs(os.path.dirname(bcpf_flags_file), exist_ok=True)
+
+ bcpf_flags_file_exists = os.path.exists(bcpf_flags_file)
+
+ matched_signatures = set()
+ # Open the flags file to read the flags from.
+ with open(flags_file, "r", encoding="utf8") as f:
+ # Open a temporary file to write the flags (minus any removed
+ # flags).
+ with open(tmp_flags_file, "w", encoding="utf8") as t:
+ # Open the bootclasspath_fragment file for append just in
+ # case it already exists.
+ with open(bcpf_flags_file, "a", encoding="utf8") as b:
+ for line in iter(f.readline, ""):
+ signature = line.rstrip()
+ if signature in self.signatures:
+ # The signature is provided by the
+ # bootclasspath_fragment so write it to the new
+ # bootclasspath_fragment specific file.
+ print(line, file=b, end="")
+ matched_signatures.add(signature)
+ else:
+ # The signature is NOT provided by the
+ # bootclasspath_fragment. Copy it to the new
+ # monolithic file.
+ print(line, file=t, end="")
+
+ # If the bootclasspath_fragment specific flags file is not empty
+ # then it contains flags. That could either be new flags just moved
+ # from frameworks/base or previous contents of the file. In either
+ # case the file must not be removed.
+ if matched_signatures:
+ # There are custom flags related to the bootclasspath_fragment
+ # so replace the frameworks/base/boot/hiddenapi file with the
+ # file that does not contain those flags.
+ shutil.move(tmp_flags_file, flags_file)
+
+ result.file_changes.append(
+ self.new_file_change(flags_file,
+ f"Removed '{self.bcpf}' specific entries"))
+
+ result.property_changes.append(
+ HiddenApiPropertyChange(
+ property_name=property_name,
+ values=[rel_bcpf_flags_file],
+ ))
+
+ # Make sure that the files are sorted.
+ self.run_command([
+ "tools/platform-compat/hiddenapi/sort_api.sh",
+ bcpf_flags_file,
+ ])
+
+ if bcpf_flags_file_exists:
+ desc = f"Added '{self.bcpf}' specific entries"
+ else:
+ desc = f"Created with '{self.bcpf}' specific entries"
+ result.file_changes.append(
+ self.new_file_change(bcpf_flags_file, desc))
+ else:
+ # There are no custom flags related to the
+ # bootclasspath_fragment so clean up the working files.
+ os.remove(tmp_flags_file)
+ if not bcpf_flags_file_exists:
+ os.remove(bcpf_flags_file)
+
+ def check_frameworks_base_boot_hidden_api_files(self, result):
+ hiddenapi_dir = os.path.join(self.top_dir,
+ "frameworks/base/boot/hiddenapi")
+ for basename in sorted(os.listdir(hiddenapi_dir)):
+ if not (basename.startswith("hiddenapi-") and
+ basename.endswith(".txt")):
+ continue
+
+ flags_file = os.path.join(hiddenapi_dir, basename)
+
+ logging.debug("Checking %s for flags related to %s", flags_file,
+ self.bcpf)
+
+ # Map the file name in frameworks/base/boot/hiddenapi into a
+ # slightly more meaningful name for use by the
+ # bootclasspath_fragment.
+ if basename == "hiddenapi-max-target-o.txt":
+ basename = "hiddenapi-max-target-o-low-priority.txt"
+ elif basename == "hiddenapi-max-target-r-loprio.txt":
+ basename = "hiddenapi-max-target-r-low-priority.txt"
+
+ property_name = basename.removeprefix("hiddenapi-")
+ property_name = property_name.removesuffix(".txt")
+ property_name = property_name.replace("-", "_")
+
+ rel_bcpf_flags_file = f"hiddenapi/{basename}"
+ bcpf_dir = self.module_info.module_path(self.bcpf)
+ bcpf_flags_file = os.path.join(self.top_dir, bcpf_dir,
+ rel_bcpf_flags_file)
+
+ if self.fix:
+ self.fix_hidden_api_flag_files(result, property_name,
+ flags_file, rel_bcpf_flags_file,
+ bcpf_flags_file)
+ else:
+ self.report_hidden_api_flag_file_changes(
+ result, property_name, flags_file, rel_bcpf_flags_file,
+ bcpf_flags_file)
+
+ @staticmethod
+ def split_package_comment(split_packages):
+ if split_packages:
+ return textwrap.dedent("""
+ The following packages contain classes from other modules on the
+ bootclasspath. That means that the hidden API flags for this
+ module has to explicitly list every single class this module
+ provides in that package to differentiate them from the classes
+ provided by other modules. That can include private classes that
+ are not part of the API.
+ """).strip("\n")
+
+ return "This module does not contain any split packages."
+
+ @staticmethod
+ def package_prefixes_comment():
+ return textwrap.dedent("""
+ The following packages and all their subpackages currently only
+ contain classes from this bootclasspath_fragment. Listing a package
+ here won't prevent other bootclasspath modules from adding classes
+ in any of those packages but it will prevent them from adding those
+ classes into an API surface, e.g. public, system, etc.. Doing so
+ will result in a build failure due to inconsistent flags.
+ """).strip("\n")
+
+ def analyze_hiddenapi_package_properties(self, result):
+ split_packages, single_packages, package_prefixes = \
+ self.compute_hiddenapi_package_properties()
+
+ # 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.
+
+ result.property_changes.append(
+ HiddenApiPropertyChange(
+ property_name="split_packages",
+ values=split_packages,
+ property_comment=self.split_package_comment(split_packages),
+ action=PropertyChangeAction.REPLACE,
+ ))
+
+ 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.
+
+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.
+""")
+
+ 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.
+""")
+
+ if single_packages:
+ result.property_changes.append(
+ HiddenApiPropertyChange(
+ property_name="single_packages",
+ values=single_packages,
+ property_comment=textwrap.dedent("""
+ The following packages currently only contain classes from
+ this bootclasspath_fragment but some of their sub-packages
+ 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,
+ ))
+
+ if package_prefixes:
+ result.property_changes.append(
+ HiddenApiPropertyChange(
+ property_name="package_prefixes",
+ values=package_prefixes,
+ property_comment=self.package_prefixes_comment(),
+ action=PropertyChangeAction.REPLACE,
+ ))
+
+ def explain_how_to_check_signature_patterns(self):
+ signature_patterns_files = self.find_bootclasspath_fragment_output_file(
+ "signature-patterns.csv", required=False)
+ if signature_patterns_files:
+ 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.
+
+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}
+
+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:
+
+* Patterns for split packages will list every class in that package.
+
+* 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 .../*.
+""")
+
+ def compute_hiddenapi_package_properties(self):
+ trie = signature_trie()
+ # Populate the trie with the classes that are provided by the
+ # bootclasspath_fragment tagging them to make it clear where they
+ # are from.
+ sorted_classes = sorted(self.classes)
+ for class_name in sorted_classes:
+ trie.add(class_name + _FAKE_MEMBER, ClassProvider.BCPF)
+
+ monolithic_classes = set()
+ abs_flags_file = os.path.join(self.top_dir, _FLAGS_FILE)
+ with open(abs_flags_file, "r", encoding="utf8") as f:
+ for line in iter(f.readline, ""):
+ signature = self.line_to_signature(line)
+ class_name = self.signature_to_class(signature)
+ if (class_name not in monolithic_classes and
+ class_name not in self.classes):
+ trie.add(
+ class_name + _FAKE_MEMBER,
+ ClassProvider.OTHER,
+ 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
+
+ def recurse_hiddenapi_packages_trie(self, node, split_packages,
+ single_packages, package_prefixes):
+ nodes = node.child_nodes()
+ if nodes:
+ for child in nodes:
+ # Ignore any non-package nodes.
+ if child.type != "package":
+ continue
+
+ package = child.selector.replace("/", ".")
+
+ providers = set(child.get_matching_rows("**"))
+ if not providers:
+ # The package and all its sub packages contain no
+ # classes. This should never happen.
+ pass
+ elif providers == {ClassProvider.BCPF}:
+ # 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)
+ # There is no point traversing into the sub packages.
+ continue
+ elif providers == {ClassProvider.OTHER}:
+ # The package and all its sub packages contain no
+ # classes provided by the bootclasspath_fragment.
+ # There is no point traversing into the sub packages.
+ logging.debug("Package '%s.**' contains no classes from %s",
+ package, self.bcpf)
+ continue
+ elif ClassProvider.BCPF in providers:
+ # The package and all its sub packages contain classes
+ # provided by the bootclasspath_fragment and other
+ # sources.
+ logging.debug(
+ "Package '%s.**' contains classes from "
+ "%s and other sources", package, self.bcpf)
+
+ providers = set(child.get_matching_rows("*"))
+ if not providers:
+ # The package contains no classes.
+ logging.debug("Package: %s contains no classes", package)
+ 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)
+ elif providers == {ClassProvider.OTHER}:
+ # The package contains no classes provided by the
+ # bootclasspath_fragment. Child nodes make contain such
+ # classes.
+ logging.debug("Package '%s.*' contains no classes from %s",
+ package, self.bcpf)
+ elif ClassProvider.BCPF in providers:
+ # 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)
+
+
+def newline_stripping_iter(iterator):
+ """Return an iterator over the iterator that strips trailing white space."""
+ lines = iter(iterator, "")
+ lines = (line.rstrip() for line in lines)
+ return lines
+
+
+def format_comment_as_text(text, indent):
+ return "".join(
+ [f"{line}\n" for line in format_comment_as_lines(text, indent)])
+
+
+def format_comment_as_lines(text, indent):
+ lines = textwrap.wrap(text.strip("\n"), width=77 - len(indent))
+ lines = [f"{indent}// {line}" for line in lines]
+ return lines
+
+
+def log_stream_for_subprocess():
+ stream = subprocess.DEVNULL
+ for handler in logging.root.handlers:
+ if handler.level == logging.DEBUG:
+ if isinstance(handler, logging.StreamHandler):
+ stream = handler.stream
+ return stream
+
+
+def main(argv):
+ args_parser = argparse.ArgumentParser(
+ description="Analyze a bootclasspath_fragment module.")
+ args_parser.add_argument(
+ "--bcpf",
+ help="The bootclasspath_fragment module to analyze",
+ required=True,
+ )
+ args_parser.add_argument(
+ "--apex",
+ help="The apex module to which the bootclasspath_fragment belongs. It "
+ "is not strictly necessary at the moment but providing it will "
+ "allow this script to give more useful messages and it may be"
+ "required in future.",
+ default="SPECIFY-APEX-OPTION")
+ args_parser.add_argument(
+ "--sdk",
+ help="The sdk module to which the bootclasspath_fragment belongs. It "
+ "is not strictly necessary at the moment but providing it will "
+ "allow this script to give more useful messages and it may be"
+ "required in future.",
+ default="SPECIFY-SDK-OPTION")
+ args_parser.add_argument(
+ "--fix",
+ help="Attempt to fix any issues found automatically.",
+ action="store_true",
+ default=False)
+ args = args_parser.parse_args(argv[1:])
+ top_dir = os.environ["ANDROID_BUILD_TOP"] + "/"
+ out_dir = os.environ.get("OUT_DIR", os.path.join(top_dir, "out"))
+ product_out_dir = os.environ.get("ANDROID_PRODUCT_OUT", top_dir)
+ # Make product_out_dir relative to the top so it can be used as part of a
+ # build target.
+ product_out_dir = product_out_dir.removeprefix(top_dir)
+ log_fd, abs_log_file = tempfile.mkstemp(
+ suffix="_analyze_bcpf.log", text=True)
+
+ with os.fdopen(log_fd, "w") as log_file:
+ # Set up debug logging to the log file.
+ logging.basicConfig(
+ level=logging.DEBUG,
+ format="%(levelname)-8s %(message)s",
+ stream=log_file)
+
+ # define a Handler which writes INFO messages or higher to the
+ # sys.stdout with just the message.
+ console = logging.StreamHandler()
+ console.setLevel(logging.INFO)
+ console.setFormatter(logging.Formatter("%(message)s"))
+ # add the handler to the root logger
+ logging.getLogger("").addHandler(console)
+
+ print(f"Writing log to {abs_log_file}")
+ try:
+ analyzer = BcpfAnalyzer(
+ tool_path=argv[0],
+ top_dir=top_dir,
+ out_dir=out_dir,
+ product_out_dir=product_out_dir,
+ bcpf=args.bcpf,
+ apex=args.apex,
+ sdk=args.sdk,
+ fix=args.fix,
+ )
+ analyzer.analyze()
+ finally:
+ print(f"Log written to {abs_log_file}")
+
+
+if __name__ == "__main__":
+ main(sys.argv)
diff --git a/scripts/hiddenapi/analyze_bcpf_test.py b/scripts/hiddenapi/analyze_bcpf_test.py
new file mode 100644
index 0000000..650dd54
--- /dev/null
+++ b/scripts/hiddenapi/analyze_bcpf_test.py
@@ -0,0 +1,650 @@
+#!/usr/bin/env python
+#
+# 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.
+"""Unit tests for analyzing bootclasspath_fragment modules."""
+import os.path
+import shutil
+import tempfile
+import unittest
+import unittest.mock
+
+import sys
+
+import analyze_bcpf as ab
+
+_FRAMEWORK_HIDDENAPI = "frameworks/base/boot/hiddenapi"
+_MAX_TARGET_O = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-o.txt"
+_MAX_TARGET_P = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-p.txt"
+_MAX_TARGET_Q = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-q.txt"
+_MAX_TARGET_R = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-r-loprio.txt"
+
+_MULTI_LINE_COMMENT = """
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu justo,
+bibendum eu malesuada vel, fringilla in odio. Etiam gravida ultricies sem
+tincidunt luctus.""".replace("\n", " ").strip()
+
+
+class FakeBuildOperation(ab.BuildOperation):
+
+ def __init__(self, lines, return_code):
+ ab.BuildOperation.__init__(self, None)
+ self._lines = lines
+ self.returncode = return_code
+
+ def lines(self):
+ return iter(self._lines)
+
+ def wait(self, *args, **kwargs):
+ return
+
+
+class TestAnalyzeBcpf(unittest.TestCase):
+
+ def setUp(self):
+ # Create a temporary directory
+ self.test_dir = tempfile.mkdtemp()
+
+ def tearDown(self):
+ # Remove the directory after the test
+ shutil.rmtree(self.test_dir)
+
+ @staticmethod
+ def write_abs_file(abs_path, contents):
+ os.makedirs(os.path.dirname(abs_path), exist_ok=True)
+ with open(abs_path, "w", encoding="utf8") as f:
+ print(contents.removeprefix("\n"), file=f, end="")
+
+ def populate_fs(self, fs):
+ for path, contents in fs.items():
+ abs_path = os.path.join(self.test_dir, path)
+ self.write_abs_file(abs_path, contents)
+
+ def create_analyzer_for_test(self,
+ fs=None,
+ bcpf="bcpf",
+ apex="apex",
+ sdk="sdk",
+ fix=False):
+ if fs:
+ self.populate_fs(fs)
+
+ top_dir = self.test_dir
+ out_dir = os.path.join(self.test_dir, "out")
+ product_out_dir = "out/product"
+
+ bcpf_dir = f"{bcpf}-dir"
+ modules = {bcpf: {"path": [bcpf_dir]}}
+ module_info = ab.ModuleInfo(modules)
+
+ analyzer = ab.BcpfAnalyzer(
+ tool_path=os.path.join(out_dir, "bin"),
+ top_dir=top_dir,
+ out_dir=out_dir,
+ product_out_dir=product_out_dir,
+ bcpf=bcpf,
+ apex=apex,
+ sdk=sdk,
+ fix=fix,
+ module_info=module_info,
+ )
+ analyzer.load_all_flags()
+ return analyzer
+
+ def test_reformat_report_text(self):
+ lines = """
+99. An item in a numbered list
+that traverses multiple lines.
+
+ An indented example
+ that should not be reformatted.
+"""
+ reformatted = ab.BcpfAnalyzer.reformat_report_test(lines)
+ self.assertEqual(
+ """
+99. An item in a numbered list that traverses multiple lines.
+
+ An indented example
+ that should not be reformatted.
+""", reformatted)
+
+ def do_test_build_flags(self, fix):
+ lines = """
+ERROR: Hidden API flags are inconsistent:
+< out/soong/.intermediates/bcpf-dir/bcpf-dir/filtered-flags.csv
+> out/soong/hiddenapi/hiddenapi-flags.csv
+
+< Lacme/test/Class;-><init>()V,blocked
+> Lacme/test/Class;-><init>()V,max-target-o
+
+< Lacme/test/Other;->getThing()Z,blocked
+> Lacme/test/Other;->getThing()Z,max-target-p
+
+< Lacme/test/Widget;-><init()V,blocked
+> Lacme/test/Widget;-><init()V,max-target-q
+
+< Lacme/test/Gadget;->NAME:Ljava/lang/String;,blocked
+> Lacme/test/Gadget;->NAME:Ljava/lang/String;,lo-prio,max-target-r
+16:37:32 ninja failed with: exit status 1
+""".strip().splitlines()
+ operation = FakeBuildOperation(lines=lines, return_code=1)
+
+ fs = {
+ _MAX_TARGET_O:
+ """
+Lacme/items/Magnet;->size:I
+Lacme/test/Class;-><init>()V
+""",
+ _MAX_TARGET_P:
+ """
+Lacme/items/Rocket;->size:I
+Lacme/test/Other;->getThing()Z
+""",
+ _MAX_TARGET_Q:
+ """
+Lacme/items/Rock;->size:I
+Lacme/test/Widget;-><init()V
+""",
+ _MAX_TARGET_R:
+ """
+Lacme/items/Lever;->size:I
+Lacme/test/Gadget;->NAME:Ljava/lang/String;
+""",
+ "bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt":
+ """
+Lacme/old/Class;->getWidget()Lacme/test/Widget;
+""",
+ "out/soong/.intermediates/bcpf-dir/bcpf/all-flags.csv":
+ """
+Lacme/test/Gadget;->NAME:Ljava/lang/String;,blocked
+Lacme/test/Widget;-><init()V,blocked
+Lacme/test/Class;-><init>()V,blocked
+Lacme/test/Other;->getThing()Z,blocked
+""",
+ }
+
+ analyzer = self.create_analyzer_for_test(fs, fix=fix)
+
+ # Override the build_file_read_output() method to just return a fake
+ # build operation.
+ analyzer.build_file_read_output = unittest.mock.Mock(
+ return_value=operation)
+
+ # Override the run_command() method to do nothing.
+ analyzer.run_command = unittest.mock.Mock()
+
+ result = ab.Result()
+
+ analyzer.build_monolithic_flags(result)
+ expected_diffs = {
+ "Lacme/test/Gadget;->NAME:Ljava/lang/String;":
+ (["blocked"], ["lo-prio", "max-target-r"]),
+ "Lacme/test/Widget;-><init()V": (["blocked"], ["max-target-q"]),
+ "Lacme/test/Class;-><init>()V": (["blocked"], ["max-target-o"]),
+ "Lacme/test/Other;->getThing()Z": (["blocked"], ["max-target-p"])
+ }
+ self.assertEqual(expected_diffs, result.diffs, msg="flag differences")
+
+ expected_property_changes = [
+ ab.HiddenApiPropertyChange(
+ property_name="max_target_o_low_priority",
+ values=["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+ property_comment=""),
+ ab.HiddenApiPropertyChange(
+ property_name="max_target_p",
+ values=["hiddenapi/hiddenapi-max-target-p.txt"],
+ property_comment=""),
+ ab.HiddenApiPropertyChange(
+ property_name="max_target_q",
+ values=["hiddenapi/hiddenapi-max-target-q.txt"],
+ property_comment=""),
+ ab.HiddenApiPropertyChange(
+ property_name="max_target_r_low_priority",
+ values=["hiddenapi/hiddenapi-max-target-r-low-priority.txt"],
+ property_comment=""),
+ ]
+ self.assertEqual(
+ expected_property_changes,
+ result.property_changes,
+ msg="property changes")
+
+ return result
+
+ def test_build_flags_report(self):
+ result = self.do_test_build_flags(fix=False)
+
+ expected_file_changes = [
+ ab.FileChange(
+ path="bcpf-dir/hiddenapi/"
+ "hiddenapi-max-target-o-low-priority.txt",
+ description="""Add the following entries:
+ Lacme/test/Class;-><init>()V
+""",
+ ),
+ ab.FileChange(
+ path="bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt",
+ description="""Add the following entries:
+ Lacme/test/Other;->getThing()Z
+""",
+ ),
+ ab.FileChange(
+ path="bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt",
+ description="""Add the following entries:
+ Lacme/test/Widget;-><init()V
+"""),
+ ab.FileChange(
+ path="bcpf-dir/hiddenapi/"
+ "hiddenapi-max-target-r-low-priority.txt",
+ description="""Add the following entries:
+ Lacme/test/Gadget;->NAME:Ljava/lang/String;
+"""),
+ ab.FileChange(
+ path="frameworks/base/boot/hiddenapi/"
+ "hiddenapi-max-target-o.txt",
+ description="""Remove the following entries:
+ Lacme/test/Class;-><init>()V
+"""),
+ ab.FileChange(
+ path="frameworks/base/boot/hiddenapi/"
+ "hiddenapi-max-target-p.txt",
+ description="""Remove the following entries:
+ Lacme/test/Other;->getThing()Z
+"""),
+ ab.FileChange(
+ path="frameworks/base/boot/hiddenapi/"
+ "hiddenapi-max-target-q.txt",
+ description="""Remove the following entries:
+ Lacme/test/Widget;-><init()V
+"""),
+ ab.FileChange(
+ path="frameworks/base/boot/hiddenapi/"
+ "hiddenapi-max-target-r-loprio.txt",
+ description="""Remove the following entries:
+ Lacme/test/Gadget;->NAME:Ljava/lang/String;
+""")
+ ]
+ result.file_changes.sort()
+ self.assertEqual(
+ expected_file_changes, result.file_changes, msg="file_changes")
+
+ def test_build_flags_fix(self):
+ result = self.do_test_build_flags(fix=True)
+
+ expected_file_changes = [
+ ab.FileChange(
+ path="bcpf-dir/hiddenapi/"
+ "hiddenapi-max-target-o-low-priority.txt",
+ description="Created with 'bcpf' specific entries"),
+ ab.FileChange(
+ path="bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt",
+ description="Added 'bcpf' specific entries"),
+ ab.FileChange(
+ path="bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt",
+ description="Created with 'bcpf' specific entries"),
+ ab.FileChange(
+ path="bcpf-dir/hiddenapi/"
+ "hiddenapi-max-target-r-low-priority.txt",
+ description="Created with 'bcpf' specific entries"),
+ ab.FileChange(
+ path=_MAX_TARGET_O,
+ description="Removed 'bcpf' specific entries"),
+ ab.FileChange(
+ path=_MAX_TARGET_P,
+ description="Removed 'bcpf' specific entries"),
+ ab.FileChange(
+ path=_MAX_TARGET_Q,
+ description="Removed 'bcpf' specific entries"),
+ ab.FileChange(
+ path=_MAX_TARGET_R,
+ description="Removed 'bcpf' specific entries")
+ ]
+
+ result.file_changes.sort()
+ self.assertEqual(
+ expected_file_changes, result.file_changes, msg="file_changes")
+
+ expected_file_contents = {
+ "bcpf-dir/hiddenapi/hiddenapi-max-target-o-low-priority.txt":
+ """
+Lacme/test/Class;-><init>()V
+""",
+ "bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt":
+ """
+Lacme/old/Class;->getWidget()Lacme/test/Widget;
+Lacme/test/Other;->getThing()Z
+""",
+ "bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt":
+ """
+Lacme/test/Widget;-><init()V
+""",
+ "bcpf-dir/hiddenapi/hiddenapi-max-target-r-low-priority.txt":
+ """
+Lacme/test/Gadget;->NAME:Ljava/lang/String;
+""",
+ _MAX_TARGET_O:
+ """
+Lacme/items/Magnet;->size:I
+""",
+ _MAX_TARGET_P:
+ """
+Lacme/items/Rocket;->size:I
+""",
+ _MAX_TARGET_Q:
+ """
+Lacme/items/Rock;->size:I
+""",
+ _MAX_TARGET_R:
+ """
+Lacme/items/Lever;->size:I
+""",
+ }
+ for file_change in result.file_changes:
+ path = file_change.path
+ expected_contents = expected_file_contents[path].lstrip()
+ abs_path = os.path.join(self.test_dir, path)
+ with open(abs_path, "r", encoding="utf8") as tio:
+ contents = tio.read()
+ self.assertEqual(
+ expected_contents, contents, msg=f"{path} contents")
+
+ def test_compute_hiddenapi_package_properties(self):
+ fs = {
+ "out/soong/.intermediates/bcpf-dir/bcpf/all-flags.csv":
+ """
+La/b/C;->m()V
+La/b/c/D;->m()V
+La/b/c/E;->m()V
+Lb/c/D;->m()V
+Lb/c/E;->m()V
+Lb/c/d/E;->m()V
+""",
+ "out/soong/hiddenapi/hiddenapi-flags.csv":
+ """
+La/b/C;->m()V
+La/b/D;->m()V
+La/b/E;->m()V
+La/b/c/D;->m()V
+La/b/c/E;->m()V
+La/b/c/d/E;->m()V
+Lb/c/D;->m()V
+Lb/c/E;->m()V
+Lb/c/d/E;->m()V
+"""
+ }
+ 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)
+
+
+class TestHiddenApiPropertyChange(unittest.TestCase):
+
+ def setUp(self):
+ # Create a temporary directory
+ self.test_dir = tempfile.mkdtemp()
+
+ def tearDown(self):
+ # Remove the directory after the test
+ shutil.rmtree(self.test_dir)
+
+ def check_change_fix(self, change, bpmodify_output, expected):
+ file = os.path.join(self.test_dir, "Android.bp")
+
+ with open(file, "w", encoding="utf8") as tio:
+ tio.write(bpmodify_output.strip("\n"))
+
+ bpmodify_runner = ab.BpModifyRunner(
+ os.path.join(os.path.dirname(sys.argv[0]), "bpmodify"))
+ change.fix_bp_file(file, "bcpf", bpmodify_runner)
+
+ with open(file, "r", encoding="utf8") as tio:
+ contents = tio.read()
+ self.assertEqual(expected.lstrip("\n"), contents)
+
+ def check_change_snippet(self, change, expected):
+ snippet = change.snippet(" ")
+ self.assertEqual(expected, snippet)
+
+ def test_change_property_with_value_no_comment(self):
+ change = ab.HiddenApiPropertyChange(
+ property_name="split_packages",
+ values=["android.provider"],
+ )
+
+ self.check_change_snippet(
+ change, """
+ split_packages: [
+ "android.provider",
+ ],
+""")
+
+ self.check_change_fix(
+ change, """
+bootclasspath_fragment {
+ name: "bcpf",
+
+ // modified by the Soong or platform compat team.
+ hidden_api: {
+ max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+ split_packages: [
+ "android.provider",
+ ],
+ },
+}
+""", """
+bootclasspath_fragment {
+ name: "bcpf",
+
+ // modified by the Soong or platform compat team.
+ hidden_api: {
+ max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+ split_packages: [
+ "android.provider",
+ ],
+ },
+}
+""")
+
+ def test_change_property_with_value_and_comment(self):
+ change = ab.HiddenApiPropertyChange(
+ property_name="split_packages",
+ values=["android.provider"],
+ property_comment=_MULTI_LINE_COMMENT,
+ )
+
+ self.check_change_snippet(
+ change, """
+ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
+ // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
+ // ultricies sem tincidunt luctus.
+ split_packages: [
+ "android.provider",
+ ],
+""")
+
+ self.check_change_fix(
+ change, """
+bootclasspath_fragment {
+ name: "bcpf",
+
+ // modified by the Soong or platform compat team.
+ hidden_api: {
+ max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+ split_packages: [
+ "android.provider",
+ ],
+
+ single_packages: [
+ "android.system",
+ ],
+
+ },
+}
+""", """
+bootclasspath_fragment {
+ name: "bcpf",
+
+ // modified by the Soong or platform compat team.
+ hidden_api: {
+ max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+
+ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
+ // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
+ // ultricies sem tincidunt luctus.
+ split_packages: [
+ "android.provider",
+ ],
+
+ single_packages: [
+ "android.system",
+ ],
+
+ },
+}
+""")
+
+ def test_set_property_with_value_and_comment(self):
+ change = ab.HiddenApiPropertyChange(
+ property_name="split_packages",
+ values=["another.provider", "other.system"],
+ property_comment=_MULTI_LINE_COMMENT,
+ action=ab.PropertyChangeAction.REPLACE,
+ )
+
+ self.check_change_snippet(
+ change, """
+ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
+ // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
+ // ultricies sem tincidunt luctus.
+ split_packages: [
+ "another.provider",
+ "other.system",
+ ],
+""")
+
+ self.check_change_fix(
+ change, """
+bootclasspath_fragment {
+ name: "bcpf",
+
+ // modified by the Soong or platform compat team.
+ hidden_api: {
+ max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+ split_packages: [
+ "another.provider",
+ "other.system",
+ ],
+ },
+}
+""", """
+bootclasspath_fragment {
+ name: "bcpf",
+
+ // modified by the Soong or platform compat team.
+ hidden_api: {
+ max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+
+ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
+ // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
+ // ultricies sem tincidunt luctus.
+ split_packages: [
+ "another.provider",
+ "other.system",
+ ],
+ },
+}
+""")
+
+ def test_set_property_with_no_value_or_comment(self):
+ change = ab.HiddenApiPropertyChange(
+ property_name="split_packages",
+ values=[],
+ action=ab.PropertyChangeAction.REPLACE,
+ )
+
+ self.check_change_snippet(change, """
+ split_packages: [],
+""")
+
+ self.check_change_fix(
+ change, """
+bootclasspath_fragment {
+ name: "bcpf",
+
+ // modified by the Soong or platform compat team.
+ hidden_api: {
+ max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+ split_packages: [
+ "another.provider",
+ "other.system",
+ ],
+ package_prefixes: ["android.provider"],
+ },
+}
+""", """
+bootclasspath_fragment {
+ name: "bcpf",
+
+ // modified by the Soong or platform compat team.
+ hidden_api: {
+ max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+ split_packages: [],
+ package_prefixes: ["android.provider"],
+ },
+}
+""")
+
+ def test_set_empty_property_with_no_value_or_comment(self):
+ change = ab.HiddenApiPropertyChange(
+ property_name="split_packages",
+ values=[],
+ action=ab.PropertyChangeAction.REPLACE,
+ )
+
+ self.check_change_snippet(change, """
+ split_packages: [],
+""")
+
+ self.check_change_fix(
+ change, """
+bootclasspath_fragment {
+ name: "bcpf",
+
+ // modified by the Soong or platform compat team.
+ hidden_api: {
+ max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+ split_packages: [],
+ package_prefixes: ["android.provider"],
+ },
+}
+""", """
+bootclasspath_fragment {
+ name: "bcpf",
+
+ // modified by the Soong or platform compat team.
+ hidden_api: {
+ max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+ split_packages: [],
+ package_prefixes: ["android.provider"],
+ },
+}
+""")
+
+
+if __name__ == "__main__":
+ unittest.main(verbosity=3)
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)
diff --git a/scripts/microfactory.bash b/scripts/microfactory.bash
index 5e702e0..192b38f 100644
--- a/scripts/microfactory.bash
+++ b/scripts/microfactory.bash
@@ -59,7 +59,7 @@
BUILDDIR=$(getoutdir) \
SRCDIR=${TOP} \
BLUEPRINTDIR=${TOP}/build/blueprint \
- EXTRA_ARGS="-pkg-path android/soong=${TOP}/build/soong -pkg-path google.golang.org/protobuf=${TOP}/external/golang-protobuf" \
+ EXTRA_ARGS="-pkg-path android/soong=${TOP}/build/soong -pkg-path rbcrun=${TOP}/build/make/tools/rbcrun -pkg-path google.golang.org/protobuf=${TOP}/external/golang-protobuf -pkg-path go.starlark.net=${TOP}/external/starlark-go" \
build_go $@
}
diff --git a/scripts/rbc-run b/scripts/rbc-run
index b8a6c0c..8d93f0e 100755
--- a/scripts/rbc-run
+++ b/scripts/rbc-run
@@ -6,8 +6,8 @@
set -eu
declare -r output_root="${OUT_DIR:-out}"
-declare -r runner="${output_root}/soong/rbcrun"
-declare -r converter="${output_root}/soong/mk2rbc"
+declare -r runner="${output_root}/rbcrun"
+declare -r converter="${output_root}/mk2rbc"
declare -r launcher="${output_root}/rbc/launcher.rbc"
declare -r makefile_list="${output_root}/.module_paths/configuration.list"
declare -r makefile="$1"
diff --git a/snapshot/host_fake_snapshot.go b/snapshot/host_fake_snapshot.go
index 6b4e12b..b04657d 100644
--- a/snapshot/host_fake_snapshot.go
+++ b/snapshot/host_fake_snapshot.go
@@ -68,6 +68,12 @@
registerHostSnapshotComponents(android.InitRegistrationContext)
}
+// Add prebuilt information to snapshot data
+type hostSnapshotFakeJsonFlags struct {
+ SnapshotJsonFlags
+ Prebuilt bool `json:",omitempty"`
+}
+
func registerHostSnapshotComponents(ctx android.RegistrationContext) {
ctx.RegisterSingletonType("host-fake-snapshot", HostToolsFakeAndroidSingleton)
}
@@ -94,7 +100,9 @@
// Find all host binary modules add 'fake' versions to snapshot
var outputs android.Paths
seen := make(map[string]bool)
- var jsonData []SnapshotJsonFlags
+ var jsonData []hostSnapshotFakeJsonFlags
+ prebuilts := make(map[string]bool)
+
ctx.VisitAllModules(func(module android.Module) {
if module.Target().Os != ctx.Config().BuildOSTarget.Os {
return
@@ -104,9 +112,10 @@
}
if android.IsModulePrebuilt(module) {
+ // Add non-prebuilt module name to map of prebuilts
+ prebuilts[android.RemoveOptionalPrebuiltPrefix(module.Name())] = true
return
}
-
if !module.Enabled() || module.IsHideFromMake() {
return
}
@@ -114,17 +123,23 @@
if !apexInfo.IsForPlatform() {
return
}
- path := hostBinToolPath(module)
+ path := hostToolPath(module)
if path.Valid() && path.String() != "" {
outFile := filepath.Join(c.snapshotDir, path.String())
if !seen[outFile] {
seen[outFile] = true
outputs = append(outputs, WriteStringToFileRule(ctx, "", outFile))
- jsonData = append(jsonData, *hostBinJsonDesc(module))
+ jsonData = append(jsonData, hostSnapshotFakeJsonFlags{*hostJsonDesc(module), false})
}
}
})
-
+ // Update any module prebuilt information
+ for idx, _ := range jsonData {
+ if _, ok := prebuilts[jsonData[idx].ModuleName]; ok {
+ // Prebuilt exists for this module
+ jsonData[idx].Prebuilt = true
+ }
+ }
marsh, err := json.Marshal(jsonData)
if err != nil {
ctx.Errorf("host fake snapshot json marshal failure: %#v", err)
diff --git a/snapshot/host_snapshot.go b/snapshot/host_snapshot.go
index 09a382e..9793218 100644
--- a/snapshot/host_snapshot.go
+++ b/snapshot/host_snapshot.go
@@ -19,6 +19,7 @@
"fmt"
"path/filepath"
"sort"
+ "strings"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
@@ -62,6 +63,11 @@
installDir android.InstallPath
}
+type ProcMacro interface {
+ ProcMacro() bool
+ CrateName() string
+}
+
func hostSnapshotFactory() android.Module {
module := &hostSnapshot{}
initHostToolsModule(module)
@@ -94,7 +100,7 @@
// Create JSON file based on the direct dependencies
ctx.VisitDirectDeps(func(dep android.Module) {
- desc := hostBinJsonDesc(dep)
+ desc := hostJsonDesc(dep)
if desc != nil {
jsonData = append(jsonData, *desc)
}
@@ -145,7 +151,7 @@
f.installDir = android.PathForModuleInstall(ctx)
- f.CopyDepsToZip(ctx, depsZipFile)
+ f.CopyDepsToZip(ctx, f.GatherPackagingSpecs(ctx), depsZipFile)
builder := android.NewRuleBuilder(pctx, ctx)
builder.Command().
@@ -183,7 +189,7 @@
}
// Get host tools path and relative install string helpers
-func hostBinToolPath(m android.Module) android.OptionalPath {
+func hostToolPath(m android.Module) android.OptionalPath {
if provider, ok := m.(android.HostToolProvider); ok {
return provider.HostToolPath()
}
@@ -198,18 +204,30 @@
return outString
}
-// Create JSON description for given module, only create descriptions for binary modueles which
-// provide a valid HostToolPath
-func hostBinJsonDesc(m android.Module) *SnapshotJsonFlags {
- path := hostBinToolPath(m)
+// Create JSON description for given module, only create descriptions for binary modules
+// and rust_proc_macro modules which provide a valid HostToolPath
+func hostJsonDesc(m android.Module) *SnapshotJsonFlags {
+ path := hostToolPath(m)
relPath := hostRelativePathString(m)
+ procMacro := false
+ moduleStem := filepath.Base(path.String())
+ crateName := ""
+
+ if pm, ok := m.(ProcMacro); ok && pm.ProcMacro() {
+ procMacro = pm.ProcMacro()
+ moduleStem = strings.TrimSuffix(moduleStem, filepath.Ext(moduleStem))
+ crateName = pm.CrateName()
+ }
+
if path.Valid() && path.String() != "" {
return &SnapshotJsonFlags{
ModuleName: m.Name(),
- ModuleStemName: filepath.Base(path.String()),
+ ModuleStemName: moduleStem,
Filename: path.String(),
Required: append(m.HostRequiredModuleNames(), m.RequiredModuleNames()...),
RelativeInstallPath: relPath,
+ RustProcMacro: procMacro,
+ CrateName: crateName,
}
}
return nil
diff --git a/snapshot/snapshot_base.go b/snapshot/snapshot_base.go
index 79d3cf6..4a14f2e 100644
--- a/snapshot/snapshot_base.go
+++ b/snapshot/snapshot_base.go
@@ -114,6 +114,8 @@
RelativeInstallPath string `json:",omitempty"`
Filename string `json:",omitempty"`
ModuleStemName string `json:",omitempty"`
+ RustProcMacro bool `json:",omitempty"`
+ CrateName string `json:",omitempty"`
// dependencies
Required []string `json:",omitempty"`
diff --git a/soong_ui.bash b/soong_ui.bash
index c1c236b..49c4b78 100755
--- a/soong_ui.bash
+++ b/soong_ui.bash
@@ -53,6 +53,8 @@
source ${TOP}/build/soong/scripts/microfactory.bash
soong_build_go soong_ui android/soong/cmd/soong_ui
+soong_build_go mk2rbc android/soong/mk2rbc/cmd
+soong_build_go rbcrun rbcrun/cmd
cd ${TOP}
exec "$(getoutdir)/soong_ui" "$@"
diff --git a/tests/lib.sh b/tests/lib.sh
index 55f9ab4..1bb2df9 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -83,12 +83,14 @@
function create_mock_soong {
copy_directory build/blueprint
copy_directory build/soong
+ copy_directory build/make/tools/rbcrun
symlink_directory prebuilts/go
symlink_directory prebuilts/build-tools
symlink_directory prebuilts/clang/host
symlink_directory external/go-cmp
symlink_directory external/golang-protobuf
+ symlink_directory external/starlark-go
touch "$MOCK_TOP/Android.bp"
}
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index 3f10f75..11311f9 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -262,12 +262,6 @@
"BUILD_BROKEN_USES_BUILD_STATIC_LIBRARY",
}, exportEnvVars...), BannerVars...)
- // We need Roboleaf converter and runner in the mixed mode
- runMicrofactory(ctx, config, "mk2rbc", "android/soong/mk2rbc/cmd",
- map[string]string{"android/soong": "build/soong"})
- runMicrofactory(ctx, config, "rbcrun", "rbcrun/cmd",
- map[string]string{"go.starlark.net": "external/starlark-go", "rbcrun": "build/make/tools/rbcrun"})
-
makeVars, err := dumpMakeVars(ctx, config, config.Arguments(), allVars, true, "")
if err != nil {
ctx.Fatalln("Error dumping make vars:", err)