Merge "Don't allow using framework and SDK at the same time."
diff --git a/Android.bp b/Android.bp
index 380a388..42a8e5c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -61,6 +61,9 @@
linux_bionic: {
enabled: true,
},
+ linux_musl: {
+ enabled: false,
+ },
linux_glibc: {
enabled: false,
},
@@ -82,6 +85,9 @@
linux_bionic: {
enabled: true,
},
+ linux_musl: {
+ enabled: false,
+ },
linux_glibc: {
enabled: false,
},
diff --git a/android/bazel.go b/android/bazel.go
index e3fb0a6..af5de12 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -573,6 +573,8 @@
"prebuilt_car-ui-androidx-core-common", // b/224773339, genrule dependency creates an .aar, not a .jar
"prebuilt_platform-robolectric-4.4-prebuilt", // aosp/1999250, needs .aar support in Jars
"prebuilt_platform-robolectric-4.5.1-prebuilt", // aosp/1999250, needs .aar support in Jars
+
+ "libtombstoned_client_rust_bridge_code", "libtombstoned_client_wrapper", // rust conversions are not supported
}
// Per-module denylist of cc_library modules to only generate the static
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 098c1fc..11da36c 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -101,12 +101,7 @@
}
// Restat marks the rule as a restat rule, which will be passed to ModuleContext.Rule in BuildParams.Restat.
-//
-// Restat is not compatible with Sbox()
func (r *RuleBuilder) Restat() *RuleBuilder {
- if r.sbox {
- panic("Restat() is not compatible with Sbox()")
- }
r.restat = true
return r
}
@@ -141,8 +136,6 @@
// point to a location where sbox's manifest will be written and must be outside outputDir. sbox
// will ensure that all outputs have been written, and will discard any output files that were not
// specified.
-//
-// Sbox is not compatible with Restat()
func (r *RuleBuilder) Sbox(outputDir WritablePath, manifestPath WritablePath) *RuleBuilder {
if r.sbox {
panic("Sbox() may not be called more than once")
@@ -150,9 +143,6 @@
if len(r.commands) > 0 {
panic("Sbox() may not be called after Command()")
}
- if r.restat {
- panic("Sbox() is not compatible with Restat()")
- }
r.sbox = true
r.outDir = outputDir
r.sboxManifestPath = manifestPath
@@ -636,11 +626,14 @@
ctx: r.ctx,
},
}
- sboxCmd.Text("rm -rf").Output(r.outDir)
- sboxCmd.Text("&&")
sboxCmd.builtToolWithoutDeps("sbox").
- Flag("--sandbox-path").Text(shared.TempDirForOutDir(PathForOutput(r.ctx).String())).
- Flag("--manifest").Input(r.sboxManifestPath)
+ FlagWithArg("--sandbox-path ", shared.TempDirForOutDir(PathForOutput(r.ctx).String())).
+ FlagWithArg("--output-dir ", r.outDir.String()).
+ FlagWithInput("--manifest ", r.sboxManifestPath)
+
+ if r.restat {
+ sboxCmd.Flag("--write-if-changed")
+ }
// Replace the command string, and add the sbox tool and manifest textproto to the
// dependencies of the final sbox rule.
diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go
index 3766bb0..86647eb 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -678,32 +678,32 @@
})
t.Run("sbox", func(t *testing.T) {
outDir := "out/soong/.intermediates/foo_sbox"
- outFile := filepath.Join(outDir, "gen/foo_sbox")
- depFile := filepath.Join(outDir, "gen/foo_sbox.d")
+ sboxOutDir := filepath.Join(outDir, "gen")
+ outFile := filepath.Join(sboxOutDir, "foo_sbox")
+ depFile := filepath.Join(sboxOutDir, "foo_sbox.d")
rspFile := filepath.Join(outDir, "rsp")
rspFile2 := filepath.Join(outDir, "rsp2")
manifest := filepath.Join(outDir, "sbox.textproto")
sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox")
sandboxPath := shared.TempDirForOutDir("out/soong")
- cmd := `rm -rf ` + outDir + `/gen && ` +
- sbox + ` --sandbox-path ` + sandboxPath + ` --manifest ` + manifest
+ cmd := sbox + ` --sandbox-path ` + sandboxPath + ` --output-dir ` + sboxOutDir + ` --manifest ` + manifest
module := result.ModuleForTests("foo_sbox", "")
check(t, module.Output("gen/foo_sbox"), module.Output(rspFile2),
cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox})
})
t.Run("sbox_inputs", func(t *testing.T) {
outDir := "out/soong/.intermediates/foo_sbox_inputs"
- outFile := filepath.Join(outDir, "gen/foo_sbox_inputs")
- depFile := filepath.Join(outDir, "gen/foo_sbox_inputs.d")
+ sboxOutDir := filepath.Join(outDir, "gen")
+ outFile := filepath.Join(sboxOutDir, "foo_sbox_inputs")
+ depFile := filepath.Join(sboxOutDir, "foo_sbox_inputs.d")
rspFile := filepath.Join(outDir, "rsp")
rspFile2 := filepath.Join(outDir, "rsp2")
manifest := filepath.Join(outDir, "sbox.textproto")
sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox")
sandboxPath := shared.TempDirForOutDir("out/soong")
- cmd := `rm -rf ` + outDir + `/gen && ` +
- sbox + ` --sandbox-path ` + sandboxPath + ` --manifest ` + manifest
+ cmd := sbox + ` --sandbox-path ` + sandboxPath + ` --output-dir ` + sboxOutDir + ` --manifest ` + manifest
module := result.ModuleForTests("foo_sbox_inputs", "")
check(t, module.Output("gen/foo_sbox_inputs"), module.Output(rspFile2),
diff --git a/apex/builder.go b/apex/builder.go
index 50c8dd1..ea61e1a 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -617,10 +617,16 @@
}
// Create a NOTICE file, and embed it as an asset file in the APEX.
- a.htmlGzNotice = android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
+ a.htmlGzNotice = android.PathForModuleOut(ctx, "NOTICE.html.gz")
android.BuildNoticeHtmlOutputFromLicenseMetadata(ctx, a.htmlGzNotice)
- implicitInputs = append(implicitInputs, a.htmlGzNotice)
- optFlags = append(optFlags, "--assets_dir "+filepath.Dir(a.htmlGzNotice.String()))
+ noticeAssetPath := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
+ builder := android.NewRuleBuilder(pctx, ctx)
+ builder.Command().Text("cp").
+ Input(a.htmlGzNotice).
+ Output(noticeAssetPath)
+ builder.Build("notice_dir", "Building notice dir")
+ implicitInputs = append(implicitInputs, noticeAssetPath)
+ optFlags = append(optFlags, "--assets_dir "+filepath.Dir(noticeAssetPath.String()))
if (moduleMinSdkVersion.GreaterThan(android.SdkVersion_Android10) && !a.shouldGenerateHashtree()) && !compressionEnabled {
// Apexes which are supposed to be installed in builtin dirs(/system, etc)
diff --git a/bp2build/java_library_conversion_test.go b/bp2build/java_library_conversion_test.go
index 2f6bce2..4b75e3b 100644
--- a/bp2build/java_library_conversion_test.go
+++ b/bp2build/java_library_conversion_test.go
@@ -30,6 +30,7 @@
}
func runJavaLibraryTestCase(t *testing.T, tc bp2buildTestCase) {
+ t.Helper()
runJavaLibraryTestCaseWithRegistrationCtxFunc(t, tc, func(ctx android.RegistrationContext) {})
}
@@ -156,3 +157,65 @@
ctx.RegisterModuleType("java_plugin", java.PluginFactory)
})
}
+
+func TestJavaLibraryErrorproneJavacflagsEnabledManually(t *testing.T) {
+ runJavaLibraryTestCase(t, bp2buildTestCase{
+ blueprint: `java_library {
+ name: "java-lib-1",
+ srcs: ["a.java"],
+ javacflags: ["-Xsuper-fast"],
+ errorprone: {
+ enabled: true,
+ javacflags: ["-Xep:SpeedLimit:OFF"],
+ },
+}`,
+ expectedBazelTargets: []string{
+ makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+ "javacopts": `[
+ "-Xsuper-fast",
+ "-Xep:SpeedLimit:OFF",
+ ]`,
+ "srcs": `["a.java"]`,
+ }),
+ },
+ })
+}
+
+func TestJavaLibraryErrorproneJavacflagsErrorproneDisabledByDefault(t *testing.T) {
+ runJavaLibraryTestCase(t, bp2buildTestCase{
+ blueprint: `java_library {
+ name: "java-lib-1",
+ srcs: ["a.java"],
+ javacflags: ["-Xsuper-fast"],
+ errorprone: {
+ javacflags: ["-Xep:SpeedLimit:OFF"],
+ },
+}`,
+ expectedBazelTargets: []string{
+ makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+ "javacopts": `["-Xsuper-fast"]`,
+ "srcs": `["a.java"]`,
+ }),
+ },
+ })
+}
+
+func TestJavaLibraryErrorproneJavacflagsErrorproneDisabledManually(t *testing.T) {
+ runJavaLibraryTestCase(t, bp2buildTestCase{
+ blueprint: `java_library {
+ name: "java-lib-1",
+ srcs: ["a.java"],
+ javacflags: ["-Xsuper-fast"],
+ errorprone: {
+ enabled: false,
+ javacflags: ["-Xep:SpeedLimit:OFF"],
+ },
+}`,
+ expectedBazelTargets: []string{
+ makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+ "javacopts": `["-Xsuper-fast"]`,
+ "srcs": `["a.java"]`,
+ }),
+ },
+ })
+}
diff --git a/cc/androidmk.go b/cc/androidmk.go
index 318cd7c..ff5ba45 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -331,6 +331,14 @@
})
}
+func (test *testDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+ entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+ if len(test.InstallerProperties.Test_suites) > 0 {
+ entries.AddCompatibilityTestSuites(test.InstallerProperties.Test_suites...)
+ }
+ })
+}
+
func (binary *binaryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
ctx.subAndroidMk(entries, binary.baseInstaller)
@@ -379,14 +387,13 @@
func (test *testBinary) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
ctx.subAndroidMk(entries, test.binaryDecorator)
+ ctx.subAndroidMk(entries, test.testDecorator)
+
entries.Class = "NATIVE_TESTS"
if Bool(test.Properties.Test_per_src) {
entries.SubName = "_" + String(test.binaryDecorator.Properties.Stem)
}
entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
- if len(test.Properties.Test_suites) > 0 {
- entries.AddCompatibilityTestSuites(test.Properties.Test_suites...)
- }
if test.testConfig != nil {
entries.SetString("LOCAL_FULL_TEST_CONFIG", test.testConfig.String())
}
@@ -445,6 +452,7 @@
func (test *testLibrary) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
ctx.subAndroidMk(entries, test.libraryDecorator)
+ ctx.subAndroidMk(entries, test.testDecorator)
}
func (installer *baseInstaller) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
diff --git a/cc/cc.go b/cc/cc.go
index 58ab28c..ac6da05 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -3595,7 +3595,8 @@
&SharedProperties{},
&FlagExporterProperties{},
&BinaryLinkerProperties{},
- &TestProperties{},
+ &TestLinkerProperties{},
+ &TestInstallerProperties{},
&TestBinaryProperties{},
&BenchmarkProperties{},
&fuzz.FuzzProperties{},
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 278efa1..09cc352 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -779,6 +779,68 @@
}
}
+func TestTestBinaryTestSuites(t *testing.T) {
+ bp := `
+ cc_test {
+ name: "main_test",
+ srcs: ["main_test.cpp"],
+ test_suites: [
+ "suite_1",
+ "suite_2",
+ ],
+ gtest: false,
+ }
+ `
+
+ ctx := prepareForCcTest.RunTestWithBp(t, bp).TestContext
+ module := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon").Module()
+
+ entries := android.AndroidMkEntriesForTest(t, ctx, module)[0]
+ compatEntries := entries.EntryMap["LOCAL_COMPATIBILITY_SUITE"]
+ if len(compatEntries) != 2 {
+ t.Errorf("expected two elements in LOCAL_COMPATIBILITY_SUITE. got %d", len(compatEntries))
+ }
+ if compatEntries[0] != "suite_1" {
+ t.Errorf("expected LOCAL_COMPATIBILITY_SUITE to be`suite_1`,"+
+ " but was '%s'", compatEntries[0])
+ }
+ if compatEntries[1] != "suite_2" {
+ t.Errorf("expected LOCAL_COMPATIBILITY_SUITE to be`suite_2`,"+
+ " but was '%s'", compatEntries[1])
+ }
+}
+
+func TestTestLibraryTestSuites(t *testing.T) {
+ bp := `
+ cc_test_library {
+ name: "main_test_lib",
+ srcs: ["main_test_lib.cpp"],
+ test_suites: [
+ "suite_1",
+ "suite_2",
+ ],
+ gtest: false,
+ }
+ `
+
+ ctx := prepareForCcTest.RunTestWithBp(t, bp).TestContext
+ module := ctx.ModuleForTests("main_test_lib", "android_arm_armv7-a-neon_shared").Module()
+
+ entries := android.AndroidMkEntriesForTest(t, ctx, module)[0]
+ compatEntries := entries.EntryMap["LOCAL_COMPATIBILITY_SUITE"]
+ if len(compatEntries) != 2 {
+ t.Errorf("expected two elements in LOCAL_COMPATIBILITY_SUITE. got %d", len(compatEntries))
+ }
+ if compatEntries[0] != "suite_1" {
+ t.Errorf("expected LOCAL_COMPATIBILITY_SUITE to be`suite_1`,"+
+ " but was '%s'", compatEntries[0])
+ }
+ if compatEntries[1] != "suite_2" {
+ t.Errorf("expected LOCAL_COMPATIBILITY_SUITE to be`suite_2`,"+
+ " but was '%s'", compatEntries[1])
+ }
+}
+
func TestVndkWhenVndkVersionIsNotSet(t *testing.T) {
ctx := testCcNoVndk(t, `
cc_library {
diff --git a/cc/config/global.go b/cc/config/global.go
index fad675a..0f31931 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -225,7 +225,6 @@
"-Wno-deprecated-enum-enum-conversion", // http://b/153746563
"-Wno-string-compare", // http://b/153764102
"-Wno-enum-enum-conversion", // http://b/154138986
- "-Wno-enum-float-conversion", // http://b/154255917
"-Wno-pessimizing-move", // http://b/154270751
// New warnings to be fixed after clang-r399163
"-Wno-non-c-typedef-for-linkage", // http://b/161304145
diff --git a/cc/test.go b/cc/test.go
index d8b7833..ead7877 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -25,7 +25,8 @@
"android/soong/tradefed"
)
-type TestProperties struct {
+// TestLinkerProperties properties to be registered via the linker
+type TestLinkerProperties struct {
// if set, build against the gtest library. Defaults to true.
Gtest *bool
@@ -33,6 +34,12 @@
Isolated *bool
}
+// TestInstallerProperties properties to be registered via the installer
+type TestInstallerProperties struct {
+ // list of compatibility suites (for example "cts", "vts") that the module should be installed into.
+ Test_suites []string `android:"arch_variant"`
+}
+
// Test option struct.
type TestOptions struct {
// The UID that you want to run the test as on a device.
@@ -83,10 +90,6 @@
// list of binary modules that should be installed alongside the test
Data_bins []string `android:"arch_variant"`
- // list of compatibility suites (for example "cts", "vts") that the module should be
- // installed into.
- Test_suites []string `android:"arch_variant"`
-
// the name of the test configuration (for example "AndroidTest.xml") that should be
// installed with the module.
Test_config *string `android:"path,arch_variant"`
@@ -243,12 +246,14 @@
}
type testDecorator struct {
- Properties TestProperties
- linker *baseLinker
+ LinkerProperties TestLinkerProperties
+ InstallerProperties TestInstallerProperties
+ installer *baseInstaller
+ linker *baseLinker
}
func (test *testDecorator) gtest() bool {
- return BoolDefault(test.Properties.Gtest, true)
+ return BoolDefault(test.LinkerProperties.Gtest, true)
}
func (test *testDecorator) testBinary() bool {
@@ -283,7 +288,7 @@
if test.gtest() {
if ctx.useSdk() && ctx.Device() {
deps.StaticLibs = append(deps.StaticLibs, "libgtest_main_ndk_c++", "libgtest_ndk_c++")
- } else if BoolDefault(test.Properties.Isolated, false) {
+ } else if BoolDefault(test.LinkerProperties.Isolated, false) {
deps.StaticLibs = append(deps.StaticLibs, "libgtest_isolated_main")
// The isolated library requires liblog, but adding it
// as a static library means unit tests cannot override
@@ -316,7 +321,11 @@
}
func (test *testDecorator) linkerProps() []interface{} {
- return []interface{}{&test.Properties}
+ return []interface{}{&test.LinkerProperties}
+}
+
+func (test *testDecorator) installerProps() []interface{} {
+ return []interface{}{&test.InstallerProperties}
}
func NewTestInstaller() *baseInstaller {
@@ -324,7 +333,7 @@
}
type testBinary struct {
- testDecorator
+ *testDecorator
*binaryDecorator
*baseCompiler
Properties TestBinaryProperties
@@ -358,6 +367,10 @@
return flags
}
+func (test *testBinary) installerProps() []interface{} {
+ return append(test.baseInstaller.installerProps(), test.testDecorator.installerProps()...)
+}
+
func (test *testBinary) install(ctx ModuleContext, file android.Path) {
// TODO: (b/167308193) Switch to /data/local/tests/unrestricted as the default install base.
testInstallBase := "/data/local/tmp"
@@ -411,7 +424,7 @@
var options []tradefed.Option
configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.StopServicesSetup", options})
}
- if Bool(test.testDecorator.Properties.Isolated) {
+ if Bool(test.testDecorator.LinkerProperties.Isolated) {
configs = append(configs, tradefed.Option{Name: "not-shardable", Value: "true"})
}
if test.Properties.Test_options.Run_test_as != nil {
@@ -441,7 +454,7 @@
}
test.testConfig = tradefed.AutoGenNativeTestConfig(ctx, test.Properties.Test_config,
- test.Properties.Test_config_template, test.Properties.Test_suites, configs, test.Properties.Auto_gen_config, testInstallBase)
+ test.Properties.Test_config_template, test.testDecorator.InstallerProperties.Test_suites, configs, test.Properties.Auto_gen_config, testInstallBase)
test.extraTestConfigs = android.PathsForModuleSrc(ctx, test.Properties.Test_options.Extra_test_configs)
@@ -466,8 +479,9 @@
binary.baseInstaller = NewTestInstaller()
test := &testBinary{
- testDecorator: testDecorator{
- linker: binary.baseLinker,
+ testDecorator: &testDecorator{
+ linker: binary.baseLinker,
+ installer: binary.baseInstaller,
},
binaryDecorator: binary,
baseCompiler: NewBaseCompiler(),
@@ -479,12 +493,14 @@
}
type testLibrary struct {
- testDecorator
+ *testDecorator
*libraryDecorator
}
func (test *testLibrary) linkerProps() []interface{} {
- return append(test.testDecorator.linkerProps(), test.libraryDecorator.linkerProps()...)
+ var props []interface{}
+ props = append(props, test.testDecorator.linkerProps()...)
+ return append(props, test.libraryDecorator.linkerProps()...)
}
func (test *testLibrary) linkerInit(ctx BaseModuleContext) {
@@ -504,16 +520,22 @@
return flags
}
+func (test *testLibrary) installerProps() []interface{} {
+ return append(test.baseInstaller.installerProps(), test.testDecorator.installerProps()...)
+}
+
func NewTestLibrary(hod android.HostOrDeviceSupported) *Module {
module, library := NewLibrary(android.HostAndDeviceSupported)
library.baseInstaller = NewTestInstaller()
test := &testLibrary{
- testDecorator: testDecorator{
- linker: library.baseLinker,
+ testDecorator: &testDecorator{
+ linker: library.baseLinker,
+ installer: library.baseInstaller,
},
libraryDecorator: library,
}
module.linker = test
+ module.installer = test
return module
}
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
index 4fa7486..418826c 100644
--- a/cmd/sbox/sbox.go
+++ b/cmd/sbox/sbox.go
@@ -38,9 +38,11 @@
)
var (
- sandboxesRoot string
- manifestFile string
- keepOutDir bool
+ sandboxesRoot string
+ outputDir string
+ manifestFile string
+ keepOutDir bool
+ writeIfChanged bool
)
const (
@@ -51,10 +53,14 @@
func init() {
flag.StringVar(&sandboxesRoot, "sandbox-path", "",
"root of temp directory to put the sandbox into")
+ flag.StringVar(&outputDir, "output-dir", "",
+ "directory which will contain all output files and only output files")
flag.StringVar(&manifestFile, "manifest", "",
"textproto manifest describing the sandboxed command(s)")
flag.BoolVar(&keepOutDir, "keep-out-dir", false,
"whether to keep the sandbox directory when done")
+ flag.BoolVar(&writeIfChanged, "write-if-changed", false,
+ "only write the output files if they have changed")
}
func usageViolation(violation string) {
@@ -241,6 +247,12 @@
return "", fmt.Errorf("command is required")
}
+ // Remove files from the output directory
+ err = clearOutputDirectory(command.CopyAfter, outputDir, writeType(writeIfChanged))
+ if err != nil {
+ return "", err
+ }
+
pathToTempDirInSbox := tempDir
if command.GetChdir() {
pathToTempDirInSbox = "."
@@ -252,7 +264,7 @@
}
// Copy in any files specified by the manifest.
- err = copyFiles(command.CopyBefore, "", tempDir, false)
+ err = copyFiles(command.CopyBefore, "", tempDir, requireFromExists, alwaysWrite)
if err != nil {
return "", err
}
@@ -306,7 +318,7 @@
// especially useful for linters with baselines that print an error message on failure
// with a command to copy the output lint errors to the new baseline. Use a copy instead of
// a move to leave the sandbox intact for manual inspection
- copyFiles(command.CopyAfter, tempDir, "", true)
+ copyFiles(command.CopyAfter, tempDir, "", allowFromNotExists, writeType(writeIfChanged))
}
// If the command was executed but failed with an error, print a debugging message before
@@ -327,39 +339,16 @@
return "", err
}
- missingOutputErrors := validateOutputFiles(command.CopyAfter, tempDir)
-
- if len(missingOutputErrors) > 0 {
- // find all created files for making a more informative error message
- createdFiles := findAllFilesUnder(tempDir)
-
- // build error message
- errorMessage := "mismatch between declared and actual outputs\n"
- errorMessage += "in sbox command(" + rawCommand + ")\n\n"
- errorMessage += "in sandbox " + tempDir + ",\n"
- errorMessage += fmt.Sprintf("failed to create %v files:\n", len(missingOutputErrors))
- for _, missingOutputError := range missingOutputErrors {
- errorMessage += " " + missingOutputError.Error() + "\n"
- }
- if len(createdFiles) < 1 {
- errorMessage += "created 0 files."
- } else {
- errorMessage += fmt.Sprintf("did create %v files:\n", len(createdFiles))
- creationMessages := createdFiles
- maxNumCreationLines := 10
- if len(creationMessages) > maxNumCreationLines {
- creationMessages = creationMessages[:maxNumCreationLines]
- creationMessages = append(creationMessages, fmt.Sprintf("...%v more", len(createdFiles)-maxNumCreationLines))
- }
- for _, creationMessage := range creationMessages {
- errorMessage += " " + creationMessage + "\n"
- }
- }
-
- return "", errors.New(errorMessage)
+ err = validateOutputFiles(command.CopyAfter, tempDir, outputDir, rawCommand)
+ if err != nil {
+ return "", err
}
+
// the created files match the declared files; now move them
- err = moveFiles(command.CopyAfter, tempDir, "")
+ err = moveFiles(command.CopyAfter, tempDir, "", writeType(writeIfChanged))
+ if err != nil {
+ return "", err
+ }
return depFile, nil
}
@@ -380,8 +369,9 @@
// validateOutputFiles verifies that all files that have a rule to be copied out of the sandbox
// were created by the command.
-func validateOutputFiles(copies []*sbox_proto.Copy, sandboxDir string) []error {
+func validateOutputFiles(copies []*sbox_proto.Copy, sandboxDir, outputDir, rawCommand string) error {
var missingOutputErrors []error
+ var incorrectOutputDirectoryErrors []error
for _, copyPair := range copies {
fromPath := joinPath(sandboxDir, copyPair.GetFrom())
fileInfo, err := os.Stat(fromPath)
@@ -392,17 +382,91 @@
if fileInfo.IsDir() {
missingOutputErrors = append(missingOutputErrors, fmt.Errorf("%s: not a file", fromPath))
}
+
+ toPath := copyPair.GetTo()
+ if rel, err := filepath.Rel(outputDir, toPath); err != nil {
+ return err
+ } else if strings.HasPrefix(rel, "../") {
+ incorrectOutputDirectoryErrors = append(incorrectOutputDirectoryErrors,
+ fmt.Errorf("%s is not under %s", toPath, outputDir))
+ }
}
- return missingOutputErrors
+
+ const maxErrors = 10
+
+ if len(incorrectOutputDirectoryErrors) > 0 {
+ errorMessage := ""
+ more := 0
+ if len(incorrectOutputDirectoryErrors) > maxErrors {
+ more = len(incorrectOutputDirectoryErrors) - maxErrors
+ incorrectOutputDirectoryErrors = incorrectOutputDirectoryErrors[:maxErrors]
+ }
+
+ for _, err := range incorrectOutputDirectoryErrors {
+ errorMessage += err.Error() + "\n"
+ }
+ if more > 0 {
+ errorMessage += fmt.Sprintf("...%v more", more)
+ }
+
+ return errors.New(errorMessage)
+ }
+
+ if len(missingOutputErrors) > 0 {
+ // find all created files for making a more informative error message
+ createdFiles := findAllFilesUnder(sandboxDir)
+
+ // build error message
+ errorMessage := "mismatch between declared and actual outputs\n"
+ errorMessage += "in sbox command(" + rawCommand + ")\n\n"
+ errorMessage += "in sandbox " + sandboxDir + ",\n"
+ errorMessage += fmt.Sprintf("failed to create %v files:\n", len(missingOutputErrors))
+ for _, missingOutputError := range missingOutputErrors {
+ errorMessage += " " + missingOutputError.Error() + "\n"
+ }
+ if len(createdFiles) < 1 {
+ errorMessage += "created 0 files."
+ } else {
+ errorMessage += fmt.Sprintf("did create %v files:\n", len(createdFiles))
+ creationMessages := createdFiles
+ if len(creationMessages) > maxErrors {
+ creationMessages = creationMessages[:maxErrors]
+ creationMessages = append(creationMessages, fmt.Sprintf("...%v more", len(createdFiles)-maxErrors))
+ }
+ for _, creationMessage := range creationMessages {
+ errorMessage += " " + creationMessage + "\n"
+ }
+ }
+
+ return errors.New(errorMessage)
+ }
+
+ return nil
}
-// copyFiles copies files in or out of the sandbox. If allowFromNotExists is true then errors
-// caused by a from path not existing are ignored.
-func copyFiles(copies []*sbox_proto.Copy, fromDir, toDir string, allowFromNotExists bool) error {
+type existsType bool
+
+const (
+ requireFromExists existsType = false
+ allowFromNotExists = true
+)
+
+type writeType bool
+
+const (
+ alwaysWrite writeType = false
+ onlyWriteIfChanged = true
+)
+
+// copyFiles copies files in or out of the sandbox. If exists is allowFromNotExists then errors
+// caused by a from path not existing are ignored. If write is onlyWriteIfChanged then the output
+// file is compared to the input file and not written to if it is the same, avoiding updating
+// the timestamp.
+func copyFiles(copies []*sbox_proto.Copy, fromDir, toDir string, exists existsType, write writeType) error {
for _, copyPair := range copies {
fromPath := joinPath(fromDir, copyPair.GetFrom())
toPath := joinPath(toDir, copyPair.GetTo())
- err := copyOneFile(fromPath, toPath, copyPair.GetExecutable(), allowFromNotExists)
+ err := copyOneFile(fromPath, toPath, copyPair.GetExecutable(), exists, write)
if err != nil {
return fmt.Errorf("error copying %q to %q: %w", fromPath, toPath, err)
}
@@ -411,8 +475,11 @@
}
// copyOneFile copies a file and its permissions. If forceExecutable is true it adds u+x to the
-// permissions. If allowFromNotExists is true it returns nil if the from path doesn't exist.
-func copyOneFile(from string, to string, forceExecutable, allowFromNotExists bool) error {
+// permissions. If exists is allowFromNotExists it returns nil if the from path doesn't exist.
+// If write is onlyWriteIfChanged then the output file is compared to the input file and not written to
+// if it is the same, avoiding updating the timestamp.
+func copyOneFile(from string, to string, forceExecutable bool, exists existsType,
+ write writeType) error {
err := os.MkdirAll(filepath.Dir(to), 0777)
if err != nil {
return err
@@ -420,7 +487,7 @@
stat, err := os.Stat(from)
if err != nil {
- if os.IsNotExist(err) && allowFromNotExists {
+ if os.IsNotExist(err) && exists == allowFromNotExists {
return nil
}
return err
@@ -431,6 +498,10 @@
perm = perm | 0100 // u+x
}
+ if write == onlyWriteIfChanged && filesHaveSameContents(from, to) {
+ return nil
+ }
+
in, err := os.Open(from)
if err != nil {
return err
@@ -504,7 +575,7 @@
to := applyPathMappings(rspFile.PathMappings, from)
// Copy the file into the sandbox.
- err := copyOneFile(from, joinPath(toDir, to), false, false)
+ err := copyOneFile(from, joinPath(toDir, to), false, requireFromExists, alwaysWrite)
if err != nil {
return err
}
@@ -551,9 +622,10 @@
// moveFiles moves files specified by a set of copy rules. It uses os.Rename, so it is restricted
// to moving files where the source and destination are in the same filesystem. This is OK for
-// sbox because the temporary directory is inside the out directory. It updates the timestamp
-// of the new file.
-func moveFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
+// sbox because the temporary directory is inside the out directory. If write is onlyWriteIfChanged
+// then the output file is compared to the input file and not written to if it is the same, avoiding
+// updating the timestamp. Otherwise it always updates the timestamp of the new file.
+func moveFiles(copies []*sbox_proto.Copy, fromDir, toDir string, write writeType) error {
for _, copyPair := range copies {
fromPath := joinPath(fromDir, copyPair.GetFrom())
toPath := joinPath(toDir, copyPair.GetTo())
@@ -562,6 +634,10 @@
return err
}
+ if write == onlyWriteIfChanged && filesHaveSameContents(fromPath, toPath) {
+ continue
+ }
+
err = os.Rename(fromPath, toPath)
if err != nil {
return err
@@ -578,6 +654,37 @@
return nil
}
+// clearOutputDirectory removes all files in the output directory if write is alwaysWrite, or
+// any files not listed in copies if write is onlyWriteIfChanged
+func clearOutputDirectory(copies []*sbox_proto.Copy, outputDir string, write writeType) error {
+ if outputDir == "" {
+ return fmt.Errorf("output directory must be set")
+ }
+
+ if write == alwaysWrite {
+ // When writing all the output files remove the whole output directory
+ return os.RemoveAll(outputDir)
+ }
+
+ outputFiles := make(map[string]bool, len(copies))
+ for _, copyPair := range copies {
+ outputFiles[copyPair.GetTo()] = true
+ }
+
+ existingFiles := findAllFilesUnder(outputDir)
+ for _, existingFile := range existingFiles {
+ fullExistingFile := filepath.Join(outputDir, existingFile)
+ if !outputFiles[fullExistingFile] {
+ err := os.Remove(fullExistingFile)
+ if err != nil {
+ return fmt.Errorf("failed to remove obsolete output file %s: %w", fullExistingFile, err)
+ }
+ }
+ }
+
+ return nil
+}
+
// Rewrite one or more depfiles so that it doesn't include the (randomized) sandbox directory
// to an output file.
func rewriteDepFiles(ins []string, out string) error {
@@ -621,6 +728,66 @@
return filepath.Join(dir, file)
}
+// filesHaveSameContents compares the contents if two files, returning true if they are the same
+// and returning false if they are different or any errors occur.
+func filesHaveSameContents(a, b string) bool {
+ // Compare the sizes of the two files
+ statA, err := os.Stat(a)
+ if err != nil {
+ return false
+ }
+ statB, err := os.Stat(b)
+ if err != nil {
+ return false
+ }
+
+ if statA.Size() != statB.Size() {
+ return false
+ }
+
+ // Open the two files
+ fileA, err := os.Open(a)
+ if err != nil {
+ return false
+ }
+ defer fileA.Close()
+ fileB, err := os.Open(a)
+ if err != nil {
+ return false
+ }
+ defer fileB.Close()
+
+ // Compare the files 1MB at a time
+ const bufSize = 1 * 1024 * 1024
+ bufA := make([]byte, bufSize)
+ bufB := make([]byte, bufSize)
+
+ remain := statA.Size()
+ for remain > 0 {
+ toRead := int64(bufSize)
+ if toRead > remain {
+ toRead = remain
+ }
+
+ _, err = io.ReadFull(fileA, bufA[:toRead])
+ if err != nil {
+ return false
+ }
+ _, err = io.ReadFull(fileB, bufB[:toRead])
+ if err != nil {
+ return false
+ }
+
+ if bytes.Compare(bufA[:toRead], bufB[:toRead]) != 0 {
+ return false
+ }
+
+ remain -= toRead
+ }
+
+ return true
+}
+
func makeAbsPathEnv(pathEnv string) (string, error) {
pathEnvElements := filepath.SplitList(pathEnv)
for i, p := range pathEnvElements {
diff --git a/cmd/symbols_map/Android.bp b/cmd/symbols_map/Android.bp
new file mode 100644
index 0000000..0ba3b07
--- /dev/null
+++ b/cmd/symbols_map/Android.bp
@@ -0,0 +1,34 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+ name: "symbols_map",
+ srcs: [
+ "elf.go",
+ "r8.go",
+ "symbols_map.go",
+ ],
+ testSrcs: [
+ "elf_test.go",
+ "r8_test.go",
+ ],
+ deps: [
+ "blueprint-pathtools",
+ "golang-protobuf-encoding-prototext",
+ "soong-response",
+ "symbols_map_proto",
+ ],
+}
+
+bootstrap_go_package {
+ name: "symbols_map_proto",
+ pkgPath: "android/soong/cmd/symbols_map/symbols_map_proto",
+ deps: [
+ "golang-protobuf-reflect-protoreflect",
+ "golang-protobuf-runtime-protoimpl",
+ ],
+ srcs: [
+ "symbols_map_proto/symbols_map.pb.go",
+ ],
+}
diff --git a/cmd/symbols_map/elf.go b/cmd/symbols_map/elf.go
new file mode 100644
index 0000000..b38896a
--- /dev/null
+++ b/cmd/symbols_map/elf.go
@@ -0,0 +1,95 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "debug/elf"
+ "encoding/binary"
+ "encoding/hex"
+ "fmt"
+ "io"
+)
+
+const gnuBuildID = "GNU\x00"
+
+// elfIdentifier extracts the elf build ID from an elf file. If allowMissing is true it returns
+// an empty identifier if the file exists but the build ID note does not.
+func elfIdentifier(filename string, allowMissing bool) (string, error) {
+ f, err := elf.Open(filename)
+ if err != nil {
+ return "", fmt.Errorf("failed to open %s: %w", filename, err)
+ }
+ defer f.Close()
+
+ buildIDNote := f.Section(".note.gnu.build-id")
+ if buildIDNote == nil {
+ if allowMissing {
+ return "", nil
+ }
+ return "", fmt.Errorf("failed to find .note.gnu.build-id in %s", filename)
+ }
+
+ buildIDs, err := readNote(buildIDNote.Open(), f.ByteOrder)
+ if err != nil {
+ return "", fmt.Errorf("failed to read .note.gnu.build-id: %w", err)
+ }
+
+ for name, desc := range buildIDs {
+ if name == gnuBuildID {
+ return hex.EncodeToString(desc), nil
+ }
+ }
+
+ return "", nil
+}
+
+// readNote reads the contents of a note section, returning it as a map from name to descriptor.
+func readNote(note io.Reader, byteOrder binary.ByteOrder) (map[string][]byte, error) {
+ var noteHeader struct {
+ Namesz uint32
+ Descsz uint32
+ Type uint32
+ }
+
+ notes := make(map[string][]byte)
+ for {
+ err := binary.Read(note, byteOrder, ¬eHeader)
+ if err != nil {
+ if err == io.EOF {
+ return notes, nil
+ }
+ return nil, fmt.Errorf("failed to read note header: %w", err)
+ }
+
+ nameBuf := make([]byte, align4(noteHeader.Namesz))
+ err = binary.Read(note, byteOrder, &nameBuf)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read note name: %w", err)
+ }
+ name := string(nameBuf[:noteHeader.Namesz])
+
+ descBuf := make([]byte, align4(noteHeader.Descsz))
+ err = binary.Read(note, byteOrder, &descBuf)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read note desc: %w", err)
+ }
+ notes[name] = descBuf[:noteHeader.Descsz]
+ }
+}
+
+// align4 rounds the input up to the next multiple of 4.
+func align4(i uint32) uint32 {
+ return (i + 3) &^ 3
+}
diff --git a/cmd/symbols_map/elf_test.go b/cmd/symbols_map/elf_test.go
new file mode 100644
index 0000000..e616228
--- /dev/null
+++ b/cmd/symbols_map/elf_test.go
@@ -0,0 +1,45 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "bytes"
+ "encoding/binary"
+ "reflect"
+ "testing"
+)
+
+func Test_readNote(t *testing.T) {
+ note := []byte{
+ 0x04, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00,
+ 0x47, 0x4e, 0x55, 0x00,
+ 0xca, 0xaf, 0x44, 0xd2, 0x82, 0x78, 0x68, 0xfe, 0xc0, 0x90, 0xa3, 0x43, 0x85, 0x36, 0x6c, 0xc7,
+ }
+
+ descs, err := readNote(bytes.NewBuffer(note), binary.LittleEndian)
+ if err != nil {
+ t.Fatalf("unexpected error in readNote: %s", err)
+ }
+
+ expectedDescs := map[string][]byte{
+ "GNU\x00": []byte{0xca, 0xaf, 0x44, 0xd2, 0x82, 0x78, 0x68, 0xfe, 0xc0, 0x90, 0xa3, 0x43, 0x85, 0x36, 0x6c, 0xc7},
+ }
+
+ if !reflect.DeepEqual(descs, expectedDescs) {
+ t.Errorf("incorrect return, want %#v got %#v", expectedDescs, descs)
+ }
+}
diff --git a/cmd/symbols_map/r8.go b/cmd/symbols_map/r8.go
new file mode 100644
index 0000000..6f73e09
--- /dev/null
+++ b/cmd/symbols_map/r8.go
@@ -0,0 +1,56 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+)
+
+const hashPrefix = "# pg_map_hash: "
+const hashTypePrefix = "SHA-256 "
+const commentPrefix = "#"
+
+// r8Identifier extracts the hash from the comments of a dictionary produced by R8. It returns
+// an empty identifier if no matching comment was found before the first non-comment line.
+func r8Identifier(filename string) (string, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return "", fmt.Errorf("failed to open %s: %w", filename, err)
+ }
+ defer f.Close()
+
+ return extractR8CompilerHash(f)
+}
+
+func extractR8CompilerHash(r io.Reader) (string, error) {
+ s := bufio.NewScanner(r)
+ for s.Scan() {
+ line := s.Text()
+ if strings.HasPrefix(line, hashPrefix) {
+ hash := strings.TrimPrefix(line, hashPrefix)
+ if !strings.HasPrefix(hash, hashTypePrefix) {
+ return "", fmt.Errorf("invalid hash type found in %q", line)
+ }
+ return strings.TrimPrefix(hash, hashTypePrefix), nil
+ } else if !strings.HasPrefix(line, commentPrefix) {
+ break
+ }
+ }
+ return "", nil
+}
diff --git a/cmd/symbols_map/r8_test.go b/cmd/symbols_map/r8_test.go
new file mode 100644
index 0000000..5712da9
--- /dev/null
+++ b/cmd/symbols_map/r8_test.go
@@ -0,0 +1,91 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "bytes"
+ "strings"
+ "testing"
+)
+
+func Test_extractR8CompilerHash(t *testing.T) {
+ testCases := []struct {
+ name string
+ data string
+
+ hash string
+ err string
+ }{
+ {
+ name: "simple",
+ data: `# compiler: R8
+# compiler_version: 3.3.18-dev
+# min_api: 10000
+# compiler_hash: bab44c1a04a2201b55fe10394f477994205c34e0
+# common_typos_disable
+# {"id":"com.android.tools.r8.mapping","version":"2.0"}
+# pg_map_id: 7fe8b95
+# pg_map_hash: SHA-256 7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da
+android.car.userlib.UserHelper -> android.car.userlib.UserHelper:
+`,
+ hash: "7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da",
+ },
+ {
+ name: "empty",
+ data: ``,
+ hash: "",
+ },
+ {
+ name: "non comment line",
+ data: `# compiler: R8
+# compiler_version: 3.3.18-dev
+# min_api: 10000
+# compiler_hash: bab44c1a04a2201b55fe10394f477994205c34e0
+# common_typos_disable
+# {"id":"com.android.tools.r8.mapping","version":"2.0"}
+# pg_map_id: 7fe8b95
+android.car.userlib.UserHelper -> android.car.userlib.UserHelper:
+# pg_map_hash: SHA-256 7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da
+`,
+ hash: "",
+ },
+ {
+ name: "invalid hash",
+ data: `# pg_map_hash: foobar 7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da`,
+ err: "invalid hash type",
+ },
+ }
+
+ for _, tt := range testCases {
+ t.Run(tt.name, func(t *testing.T) {
+ hash, err := extractR8CompilerHash(bytes.NewBufferString(tt.data))
+ if err != nil {
+ if tt.err != "" {
+ if !strings.Contains(err.Error(), tt.err) {
+ t.Fatalf("incorrect error in extractR8CompilerHash, want %s got %s", tt.err, err)
+ }
+ } else {
+ t.Fatalf("unexpected error in extractR8CompilerHash: %s", err)
+ }
+ } else if tt.err != "" {
+ t.Fatalf("missing error in extractR8CompilerHash, want %s", tt.err)
+ }
+
+ if g, w := hash, tt.hash; g != w {
+ t.Errorf("incorrect hash, want %q got %q", w, g)
+ }
+ })
+ }
+}
diff --git a/cmd/symbols_map/symbols_map.go b/cmd/symbols_map/symbols_map.go
new file mode 100644
index 0000000..938446d
--- /dev/null
+++ b/cmd/symbols_map/symbols_map.go
@@ -0,0 +1,202 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "strings"
+
+ "android/soong/cmd/symbols_map/symbols_map_proto"
+ "android/soong/response"
+
+ "github.com/google/blueprint/pathtools"
+ "google.golang.org/protobuf/encoding/prototext"
+ "google.golang.org/protobuf/proto"
+)
+
+// This tool is used to extract a hash from an elf file or an r8 dictionary and store it as a
+// textproto, or to merge multiple textprotos together.
+
+func main() {
+ var expandedArgs []string
+ for _, arg := range os.Args[1:] {
+ if strings.HasPrefix(arg, "@") {
+ f, err := os.Open(strings.TrimPrefix(arg, "@"))
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err.Error())
+ os.Exit(1)
+ }
+
+ respArgs, err := response.ReadRspFile(f)
+ f.Close()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err.Error())
+ os.Exit(1)
+ }
+ expandedArgs = append(expandedArgs, respArgs...)
+ } else {
+ expandedArgs = append(expandedArgs, arg)
+ }
+ }
+
+ flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+ // Hide the flag package to prevent accidental references to flag instead of flags.
+ flag := struct{}{}
+ _ = flag
+
+ flags.Usage = func() {
+ fmt.Fprintf(flags.Output(), "Usage of %s:\n", os.Args[0])
+ fmt.Fprintf(flags.Output(), " %s -elf|-r8 <input file> [-write_if_changed] <output file>\n", os.Args[0])
+ fmt.Fprintf(flags.Output(), " %s -merge <output file> [-write_if_changed] [-ignore_missing_files] [-strip_prefix <prefix>] [<input file>...]\n", os.Args[0])
+ fmt.Fprintln(flags.Output())
+
+ flags.PrintDefaults()
+ }
+
+ elfFile := flags.String("elf", "", "extract identifier from an elf file")
+ r8File := flags.String("r8", "", "extract identifier from an r8 dictionary")
+ merge := flags.String("merge", "", "merge multiple identifier protos")
+
+ writeIfChanged := flags.Bool("write_if_changed", false, "only write output file if it is modified")
+ ignoreMissingFiles := flags.Bool("ignore_missing_files", false, "ignore missing input files in merge mode")
+ stripPrefix := flags.String("strip_prefix", "", "prefix to strip off of the location field in merge mode")
+
+ flags.Parse(expandedArgs)
+
+ if *merge != "" {
+ // If merge mode was requested perform the merge and exit early.
+ err := mergeProtos(*merge, flags.Args(), *stripPrefix, *writeIfChanged, *ignoreMissingFiles)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "failed to merge protos: %s", err)
+ os.Exit(1)
+ }
+ os.Exit(0)
+ }
+
+ if *elfFile == "" && *r8File == "" {
+ fmt.Fprintf(os.Stderr, "-elf or -r8 argument is required\n")
+ flags.Usage()
+ os.Exit(1)
+ }
+
+ if *elfFile != "" && *r8File != "" {
+ fmt.Fprintf(os.Stderr, "only one of -elf or -r8 argument is allowed\n")
+ flags.Usage()
+ os.Exit(1)
+ }
+
+ if flags.NArg() != 1 {
+ flags.Usage()
+ os.Exit(1)
+ }
+
+ output := flags.Arg(0)
+
+ var identifier string
+ var location string
+ var typ symbols_map_proto.Mapping_Type
+ var err error
+
+ if *elfFile != "" {
+ typ = symbols_map_proto.Mapping_ELF
+ location = *elfFile
+ identifier, err = elfIdentifier(*elfFile, true)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error reading elf identifier: %s\n", err)
+ os.Exit(1)
+ }
+ } else if *r8File != "" {
+ typ = symbols_map_proto.Mapping_R8
+ identifier, err = r8Identifier(*r8File)
+ location = *r8File
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error reading r8 identifier: %s\n", err)
+ os.Exit(1)
+ }
+ } else {
+ panic("shouldn't get here")
+ }
+
+ mapping := symbols_map_proto.Mapping{
+ Identifier: proto.String(identifier),
+ Location: proto.String(location),
+ Type: typ.Enum(),
+ }
+
+ err = writeTextProto(output, &mapping, *writeIfChanged)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error writing output: %s\n", err)
+ os.Exit(1)
+ }
+}
+
+// writeTextProto writes a proto to an output file as a textproto, optionally leaving the file
+// unmodified if it was already up to date.
+func writeTextProto(output string, message proto.Message, writeIfChanged bool) error {
+ marshaller := prototext.MarshalOptions{Multiline: true}
+ data, err := marshaller.Marshal(message)
+ if err != nil {
+ return fmt.Errorf("error marshalling textproto: %w", err)
+ }
+
+ if writeIfChanged {
+ err = pathtools.WriteFileIfChanged(output, data, 0666)
+ } else {
+ err = ioutil.WriteFile(output, data, 0666)
+ }
+
+ if err != nil {
+ return fmt.Errorf("error writing to %s: %w\n", output, err)
+ }
+
+ return nil
+}
+
+// mergeProtos merges a list of textproto files containing Mapping messages into a single textproto
+// containing a Mappings message.
+func mergeProtos(output string, inputs []string, stripPrefix string, writeIfChanged bool, ignoreMissingFiles bool) error {
+ mappings := symbols_map_proto.Mappings{}
+ for _, input := range inputs {
+ mapping := symbols_map_proto.Mapping{}
+ data, err := ioutil.ReadFile(input)
+ if err != nil {
+ if ignoreMissingFiles && os.IsNotExist(err) {
+ // Merge mode is used on a list of files in the packaging directory. If multiple
+ // goals are included on the build command line, for example `dist` and `tests`,
+ // then the symbols packaging rule for `dist` can run while a dependency of `tests`
+ // is modifying the symbols packaging directory. That can result in a file that
+ // existed when the file list was generated being deleted as part of updating it,
+ // resulting in sporadic ENOENT errors. Ignore them if -ignore_missing_files
+ // was passed on the command line.
+ continue
+ }
+ return fmt.Errorf("failed to read %s: %w", input, err)
+ }
+ err = prototext.Unmarshal(data, &mapping)
+ if err != nil {
+ return fmt.Errorf("failed to parse textproto %s: %w", input, err)
+ }
+ if stripPrefix != "" && mapping.Location != nil {
+ mapping.Location = proto.String(strings.TrimPrefix(*mapping.Location, stripPrefix))
+ }
+ mappings.Mappings = append(mappings.Mappings, &mapping)
+ }
+
+ return writeTextProto(output, &mappings, writeIfChanged)
+}
diff --git a/cmd/symbols_map/symbols_map_proto/symbols_map.pb.go b/cmd/symbols_map/symbols_map_proto/symbols_map.pb.go
new file mode 100644
index 0000000..f9c0ce5
--- /dev/null
+++ b/cmd/symbols_map/symbols_map_proto/symbols_map.pb.go
@@ -0,0 +1,315 @@
+// Copyright 2022 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.9.1
+// source: symbols_map.proto
+
+package symbols_map_proto
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// Type is the valid types of a mapping.
+type Mapping_Type int32
+
+const (
+ // ELF denotes a mapping from an elf build ID to an unstripped elf file.
+ Mapping_ELF Mapping_Type = 0
+ // R8 denotes a mapping from an R8 dictionary hash to an R8 dictionary.
+ Mapping_R8 Mapping_Type = 1
+)
+
+// Enum value maps for Mapping_Type.
+var (
+ Mapping_Type_name = map[int32]string{
+ 0: "ELF",
+ 1: "R8",
+ }
+ Mapping_Type_value = map[string]int32{
+ "ELF": 0,
+ "R8": 1,
+ }
+)
+
+func (x Mapping_Type) Enum() *Mapping_Type {
+ p := new(Mapping_Type)
+ *p = x
+ return p
+}
+
+func (x Mapping_Type) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Mapping_Type) Descriptor() protoreflect.EnumDescriptor {
+ return file_symbols_map_proto_enumTypes[0].Descriptor()
+}
+
+func (Mapping_Type) Type() protoreflect.EnumType {
+ return &file_symbols_map_proto_enumTypes[0]
+}
+
+func (x Mapping_Type) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Do not use.
+func (x *Mapping_Type) UnmarshalJSON(b []byte) error {
+ num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
+ if err != nil {
+ return err
+ }
+ *x = Mapping_Type(num)
+ return nil
+}
+
+// Deprecated: Use Mapping_Type.Descriptor instead.
+func (Mapping_Type) EnumDescriptor() ([]byte, []int) {
+ return file_symbols_map_proto_rawDescGZIP(), []int{0, 0}
+}
+
+type Mapping struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // identifier is a unique identifier of a location, generally the hash of the file. For an
+ // elf file it is the elf build ID, for an R8 dictionary it is the hash from the comments in the
+ // top of the file. It may be empty if no hash could be extracted from the file.
+ Identifier *string `protobuf:"bytes,1,opt,name=identifier" json:"identifier,omitempty"`
+ // location is the path to the file with the given identifier. The location should be valid
+ // both on the local disk and in the distributed symbols.zip or proguard_dict.zip files.
+ Location *string `protobuf:"bytes,2,opt,name=location" json:"location,omitempty"`
+ // type is the type of the mapping, either ELF or R8.
+ Type *Mapping_Type `protobuf:"varint,3,opt,name=type,enum=symbols_map.Mapping_Type" json:"type,omitempty"`
+}
+
+func (x *Mapping) Reset() {
+ *x = Mapping{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_symbols_map_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Mapping) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Mapping) ProtoMessage() {}
+
+func (x *Mapping) ProtoReflect() protoreflect.Message {
+ mi := &file_symbols_map_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Mapping.ProtoReflect.Descriptor instead.
+func (*Mapping) Descriptor() ([]byte, []int) {
+ return file_symbols_map_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Mapping) GetIdentifier() string {
+ if x != nil && x.Identifier != nil {
+ return *x.Identifier
+ }
+ return ""
+}
+
+func (x *Mapping) GetLocation() string {
+ if x != nil && x.Location != nil {
+ return *x.Location
+ }
+ return ""
+}
+
+func (x *Mapping) GetType() Mapping_Type {
+ if x != nil && x.Type != nil {
+ return *x.Type
+ }
+ return Mapping_ELF
+}
+
+type Mappings struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Mappings []*Mapping `protobuf:"bytes,4,rep,name=mappings" json:"mappings,omitempty"`
+}
+
+func (x *Mappings) Reset() {
+ *x = Mappings{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_symbols_map_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Mappings) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Mappings) ProtoMessage() {}
+
+func (x *Mappings) ProtoReflect() protoreflect.Message {
+ mi := &file_symbols_map_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Mappings.ProtoReflect.Descriptor instead.
+func (*Mappings) Descriptor() ([]byte, []int) {
+ return file_symbols_map_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *Mappings) GetMappings() []*Mapping {
+ if x != nil {
+ return x.Mappings
+ }
+ return nil
+}
+
+var File_symbols_map_proto protoreflect.FileDescriptor
+
+var file_symbols_map_proto_rawDesc = []byte{
+ 0x0a, 0x11, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x5f, 0x6d, 0x61, 0x70,
+ 0x22, 0x8d, 0x01, 0x0a, 0x07, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x1e, 0x0a, 0x0a,
+ 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08,
+ 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
+ 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
+ 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73,
+ 0x5f, 0x6d, 0x61, 0x70, 0x2e, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x54, 0x79, 0x70,
+ 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x17, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12,
+ 0x07, 0x0a, 0x03, 0x45, 0x4c, 0x46, 0x10, 0x00, 0x12, 0x06, 0x0a, 0x02, 0x52, 0x38, 0x10, 0x01,
+ 0x22, 0x3c, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x30, 0x0a, 0x08,
+ 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14,
+ 0x2e, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x2e, 0x4d, 0x61, 0x70,
+ 0x70, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x42, 0x31,
+ 0x5a, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f,
+ 0x63, 0x6d, 0x64, 0x2f, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x2f,
+ 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f,
+}
+
+var (
+ file_symbols_map_proto_rawDescOnce sync.Once
+ file_symbols_map_proto_rawDescData = file_symbols_map_proto_rawDesc
+)
+
+func file_symbols_map_proto_rawDescGZIP() []byte {
+ file_symbols_map_proto_rawDescOnce.Do(func() {
+ file_symbols_map_proto_rawDescData = protoimpl.X.CompressGZIP(file_symbols_map_proto_rawDescData)
+ })
+ return file_symbols_map_proto_rawDescData
+}
+
+var file_symbols_map_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_symbols_map_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_symbols_map_proto_goTypes = []interface{}{
+ (Mapping_Type)(0), // 0: symbols_map.Mapping.Type
+ (*Mapping)(nil), // 1: symbols_map.Mapping
+ (*Mappings)(nil), // 2: symbols_map.Mappings
+}
+var file_symbols_map_proto_depIdxs = []int32{
+ 0, // 0: symbols_map.Mapping.type:type_name -> symbols_map.Mapping.Type
+ 1, // 1: symbols_map.Mappings.mappings:type_name -> symbols_map.Mapping
+ 2, // [2:2] is the sub-list for method output_type
+ 2, // [2:2] is the sub-list for method input_type
+ 2, // [2:2] is the sub-list for extension type_name
+ 2, // [2:2] is the sub-list for extension extendee
+ 0, // [0:2] is the sub-list for field type_name
+}
+
+func init() { file_symbols_map_proto_init() }
+func file_symbols_map_proto_init() {
+ if File_symbols_map_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_symbols_map_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Mapping); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_symbols_map_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Mappings); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_symbols_map_proto_rawDesc,
+ NumEnums: 1,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_symbols_map_proto_goTypes,
+ DependencyIndexes: file_symbols_map_proto_depIdxs,
+ EnumInfos: file_symbols_map_proto_enumTypes,
+ MessageInfos: file_symbols_map_proto_msgTypes,
+ }.Build()
+ File_symbols_map_proto = out.File
+ file_symbols_map_proto_rawDesc = nil
+ file_symbols_map_proto_goTypes = nil
+ file_symbols_map_proto_depIdxs = nil
+}
diff --git a/cmd/symbols_map/symbols_map_proto/symbols_map.proto b/cmd/symbols_map/symbols_map_proto/symbols_map.proto
new file mode 100644
index 0000000..693fe3e
--- /dev/null
+++ b/cmd/symbols_map/symbols_map_proto/symbols_map.proto
@@ -0,0 +1,44 @@
+// Copyright 2022 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto2";
+
+package symbols_map;
+option go_package = "android/soong/cmd/symbols_map/symbols_map_proto";
+
+message Mapping {
+ // identifier is a unique identifier of a location, generally the hash of the file. For an
+ // elf file it is the elf build ID, for an R8 dictionary it is the hash from the comments in the
+ // top of the file. It may be empty if no hash could be extracted from the file.
+ optional string identifier = 1;
+
+ // location is the path to the file with the given identifier. The location should be valid
+ // both on the local disk and in the distributed symbols.zip or proguard_dict.zip files.
+ optional string location = 2;
+
+ // Type is the valid types of a mapping.
+ enum Type {
+ // ELF denotes a mapping from an elf build ID to an unstripped elf file.
+ ELF = 0;
+ // R8 denotes a mapping from an R8 dictionary hash to an R8 dictionary.
+ R8 = 1;
+ }
+
+ // type is the type of the mapping, either ELF or R8.
+ optional Type type = 3;
+}
+
+message Mappings {
+ repeated Mapping mappings = 4;
+}
\ No newline at end of file
diff --git a/cmd/symbols_map/symbols_map_test.go b/cmd/symbols_map/symbols_map_test.go
new file mode 100644
index 0000000..754b7ef
--- /dev/null
+++ b/cmd/symbols_map/symbols_map_test.go
@@ -0,0 +1,217 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "android/soong/cmd/symbols_map/symbols_map_proto"
+
+ "google.golang.org/protobuf/encoding/prototext"
+ "google.golang.org/protobuf/proto"
+)
+
+func Test_mergeProtos(t *testing.T) {
+ type testFile struct {
+ filename string
+ contents *symbols_map_proto.Mapping
+ missing bool
+ }
+
+ tests := []struct {
+ name string
+ inputs []testFile
+ stripPrefix string
+ writeIfChanged bool
+ ignoreMissingFiles bool
+
+ error string
+ output *symbols_map_proto.Mappings
+ }{
+ {
+ name: "empty",
+ output: &symbols_map_proto.Mappings{},
+ },
+ {
+ name: "merge",
+ inputs: []testFile{
+ {
+ filename: "foo",
+ contents: &symbols_map_proto.Mapping{
+ Identifier: proto.String("foo"),
+ Location: proto.String("symbols/foo"),
+ Type: symbols_map_proto.Mapping_ELF.Enum(),
+ },
+ },
+ {
+ filename: "bar",
+ contents: &symbols_map_proto.Mapping{
+ Identifier: proto.String("bar"),
+ Location: proto.String("symbols/bar"),
+ Type: symbols_map_proto.Mapping_R8.Enum(),
+ },
+ },
+ },
+ output: &symbols_map_proto.Mappings{
+ Mappings: []*symbols_map_proto.Mapping{
+ {
+ Identifier: proto.String("foo"),
+ Location: proto.String("symbols/foo"),
+ Type: symbols_map_proto.Mapping_ELF.Enum(),
+ },
+ {
+ Identifier: proto.String("bar"),
+ Location: proto.String("symbols/bar"),
+ Type: symbols_map_proto.Mapping_R8.Enum(),
+ },
+ },
+ },
+ },
+ {
+ name: "strip prefix",
+ inputs: []testFile{
+ {
+ filename: "foo",
+ contents: &symbols_map_proto.Mapping{
+ Identifier: proto.String("foo"),
+ Location: proto.String("symbols/foo"),
+ Type: symbols_map_proto.Mapping_ELF.Enum(),
+ },
+ },
+ {
+ filename: "bar",
+ contents: &symbols_map_proto.Mapping{
+ Identifier: proto.String("bar"),
+ Location: proto.String("symbols/bar"),
+ Type: symbols_map_proto.Mapping_R8.Enum(),
+ },
+ },
+ },
+ stripPrefix: "symbols/",
+ output: &symbols_map_proto.Mappings{
+ Mappings: []*symbols_map_proto.Mapping{
+ {
+ Identifier: proto.String("foo"),
+ Location: proto.String("foo"),
+ Type: symbols_map_proto.Mapping_ELF.Enum(),
+ },
+ {
+ Identifier: proto.String("bar"),
+ Location: proto.String("bar"),
+ Type: symbols_map_proto.Mapping_R8.Enum(),
+ },
+ },
+ },
+ },
+ {
+ name: "missing",
+ inputs: []testFile{
+ {
+ filename: "foo",
+ contents: &symbols_map_proto.Mapping{
+ Identifier: proto.String("foo"),
+ Location: proto.String("symbols/foo"),
+ Type: symbols_map_proto.Mapping_ELF.Enum(),
+ },
+ },
+ {
+ filename: "bar",
+ missing: true,
+ },
+ },
+ error: "no such file or directory",
+ },
+ {
+ name: "ignore missing",
+ inputs: []testFile{
+ {
+ filename: "foo",
+ contents: &symbols_map_proto.Mapping{
+ Identifier: proto.String("foo"),
+ Location: proto.String("symbols/foo"),
+ Type: symbols_map_proto.Mapping_ELF.Enum(),
+ },
+ },
+ {
+ filename: "bar",
+ missing: true,
+ },
+ },
+ ignoreMissingFiles: true,
+ output: &symbols_map_proto.Mappings{
+ Mappings: []*symbols_map_proto.Mapping{
+ {
+ Identifier: proto.String("foo"),
+ Location: proto.String("symbols/foo"),
+ Type: symbols_map_proto.Mapping_ELF.Enum(),
+ },
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ dir, err := os.MkdirTemp("", "test_mergeProtos")
+ if err != nil {
+ t.Fatalf("failed to create temporary directory: %s", err)
+ }
+ defer os.RemoveAll(dir)
+
+ var inputs []string
+ for _, in := range tt.inputs {
+ path := filepath.Join(dir, in.filename)
+ inputs = append(inputs, path)
+ if !in.missing {
+ err := writeTextProto(path, in.contents, false)
+ if err != nil {
+ t.Fatalf("failed to create input file %s: %s", path, err)
+ }
+ }
+ }
+ output := filepath.Join(dir, "out")
+
+ err = mergeProtos(output, inputs, tt.stripPrefix, tt.writeIfChanged, tt.ignoreMissingFiles)
+ if err != nil {
+ if tt.error != "" {
+ if !strings.Contains(err.Error(), tt.error) {
+ t.Fatalf("expected error %q, got %s", tt.error, err.Error())
+ }
+ } else {
+ t.Fatalf("unexpected error %q", err)
+ }
+ } else if tt.error != "" {
+ t.Fatalf("missing error %q", tt.error)
+ } else {
+ data, err := ioutil.ReadFile(output)
+ if err != nil {
+ t.Fatalf("failed to read output file %s: %s", output, err)
+ }
+ var got symbols_map_proto.Mappings
+ err = prototext.Unmarshal(data, &got)
+ if err != nil {
+ t.Fatalf("failed to unmarshal textproto %s: %s", output, err)
+ }
+
+ if !proto.Equal(tt.output, &got) {
+ t.Fatalf("expected output %q, got %q", tt.output.String(), got.String())
+ }
+ }
+ })
+ }
+}
diff --git a/java/app.go b/java/app.go
index 5b1daa4..21ee34e 100755
--- a/java/app.go
+++ b/java/app.go
@@ -586,10 +586,16 @@
}
a.onDeviceDir = android.InstallPathToOnDevicePath(ctx, a.installDir)
- 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 = android.OptionalPathForPath(noticeFile)
+ noticeFile := android.PathForModuleOut(ctx, "NOTICE.html.gz")
+ android.BuildNoticeHtmlOutputFromLicenseMetadata(ctx, noticeFile)
+ noticeAssetPath := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
+ builder := android.NewRuleBuilder(pctx, ctx)
+ builder.Command().Text("cp").
+ Input(noticeFile).
+ Output(noticeAssetPath)
+ builder.Build("notice_dir", "Building notice dir")
+ a.aapt.noticeFile = android.OptionalPathForPath(noticeAssetPath)
}
a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
diff --git a/java/base.go b/java/base.go
index 2f425cd..8b258d6 100644
--- a/java/base.go
+++ b/java/base.go
@@ -1052,6 +1052,7 @@
j.expandIDEInfoCompiledSrcs = append(j.expandIDEInfoCompiledSrcs, uniqueSrcFiles.Strings()...)
var kotlinJars android.Paths
+ var kotlinHeaderJars android.Paths
if srcFiles.HasExt(".kt") {
// user defined kotlin flags.
@@ -1109,18 +1110,22 @@
}
kotlinJar := android.PathForModuleOut(ctx, "kotlin", jarName)
- kotlinCompile(ctx, kotlinJar, kotlinSrcFiles, kotlinCommonSrcFiles, srcJars, flags)
+ kotlinHeaderJar := android.PathForModuleOut(ctx, "kotlin_headers", jarName)
+ kotlinCompile(ctx, kotlinJar, kotlinHeaderJar, kotlinSrcFiles, kotlinCommonSrcFiles, srcJars, flags)
if ctx.Failed() {
return
}
// Make javac rule depend on the kotlinc rule
- flags.classpath = append(flags.classpath, kotlinJar)
+ flags.classpath = append(classpath{kotlinHeaderJar}, flags.classpath...)
kotlinJars = append(kotlinJars, kotlinJar)
+ kotlinHeaderJars = append(kotlinHeaderJars, kotlinHeaderJar)
+
// Jar kotlin classes into the final jar after javac
if BoolDefault(j.properties.Static_kotlin_stdlib, true) {
kotlinJars = append(kotlinJars, deps.kotlinStdlib...)
+ kotlinHeaderJars = append(kotlinHeaderJars, deps.kotlinStdlib...)
} else {
flags.dexClasspath = append(flags.dexClasspath, deps.kotlinStdlib...)
}
@@ -1144,7 +1149,7 @@
// with sharding enabled. See: b/77284273.
}
headerJarFileWithoutDepsOrJarjar, j.headerJarFile =
- j.compileJavaHeader(ctx, uniqueSrcFiles, srcJars, deps, flags, jarName, kotlinJars)
+ j.compileJavaHeader(ctx, uniqueSrcFiles, srcJars, deps, flags, jarName, kotlinHeaderJars)
if ctx.Failed() {
return
}
diff --git a/java/config/config.go b/java/config/config.go
index 05dfde6..262c531 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -50,11 +50,6 @@
"core-icu4j",
"core-oj",
"core-libart",
- // TODO: Could this be all updatable bootclasspath jars?
- "updatable-media",
- "framework-mediaprovider",
- "framework-sdkextensions",
- "android.net.ipsec.ike",
}
)
diff --git a/java/config/kotlin.go b/java/config/kotlin.go
index a83f87f..fc63f4d 100644
--- a/java/config/kotlin.go
+++ b/java/config/kotlin.go
@@ -34,6 +34,7 @@
pctx.SourcePathVariable("KotlinKaptJar", "external/kotlinc/lib/kotlin-annotation-processing.jar")
pctx.SourcePathVariable("KotlinAnnotationJar", "external/kotlinc/lib/annotations-13.0.jar")
pctx.SourcePathVariable("KotlinStdlibJar", KotlinStdlibJar)
+ pctx.SourcePathVariable("KotlinAbiGenPluginJar", "external/kotlinc/lib/jvm-abi-gen.jar")
// These flags silence "Illegal reflective access" warnings when running kapt in OpenJDK9+
pctx.StaticVariable("KaptSuppressJDK9Warnings", strings.Join([]string{
diff --git a/java/droidstubs.go b/java/droidstubs.go
index e7aeeb8..3b1f7c0 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -433,6 +433,10 @@
}
}
+func metalavaUseRbe(ctx android.ModuleContext) bool {
+ return ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_METALAVA")
+}
+
func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, javaVersion javaVersion, srcs android.Paths,
srcJarList android.Path, bootclasspath, classpath classpath, homeDir android.WritablePath) *android.RuleBuilderCommand {
rule.Command().Text("rm -rf").Flag(homeDir.String())
@@ -441,7 +445,7 @@
cmd := rule.Command()
cmd.FlagWithArg("ANDROID_PREFS_ROOT=", homeDir.String())
- if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_METALAVA") {
+ if metalavaUseRbe(ctx) {
rule.Remoteable(android.RemoteRuleSupports{RBE: true})
execStrategy := ctx.Config().GetenvWithDefault("RBE_METALAVA_EXEC_STRATEGY", remoteexec.LocalExecStrategy)
labels := map[string]string{"type": "tool", "name": "metalava"}
@@ -665,7 +669,9 @@
}
// TODO(b/183630617): rewrapper doesn't support restat rules
- // rule.Restat()
+ if !metalavaUseRbe(ctx) {
+ rule.Restat()
+ }
zipSyncCleanupCmd(rule, srcJarDir)
diff --git a/java/java.go b/java/java.go
index ecbbc32..713fe94 100644
--- a/java/java.go
+++ b/java/java.go
@@ -2060,15 +2060,22 @@
protoSrcPartition: android.ProtoSrcLabelPartition,
})
+ var javacopts []string
+ if m.properties.Javacflags != nil {
+ javacopts = append(javacopts, m.properties.Javacflags...)
+ }
+ epEnabled := m.properties.Errorprone.Enabled
+ //TODO(b/227504307) add configuration that depends on RUN_ERROR_PRONE environment variable
+ if Bool(epEnabled) {
+ javacopts = append(javacopts, m.properties.Errorprone.Javacflags...)
+ }
+
commonAttrs := &javaCommonAttributes{
Srcs: srcPartitions[javaSrcPartition],
Plugins: bazel.MakeLabelListAttribute(
android.BazelLabelForModuleDeps(ctx, m.properties.Plugins),
),
- }
-
- if m.properties.Javacflags != nil {
- commonAttrs.Javacopts = bazel.MakeStringListAttribute(m.properties.Javacflags)
+ Javacopts: bazel.MakeStringListAttribute(javacopts),
}
depLabels := &javaDependencyLabels{}
diff --git a/java/kotlin.go b/java/kotlin.go
index ce79bae..eff5bb5 100644
--- a/java/kotlin.go
+++ b/java/kotlin.go
@@ -28,17 +28,20 @@
var kotlinc = pctx.AndroidRemoteStaticRule("kotlinc", android.RemoteRuleSupports{Goma: true},
blueprint.RuleParams{
- Command: `rm -rf "$classesDir" "$srcJarDir" "$kotlinBuildFile" "$emptyDir" && ` +
- `mkdir -p "$classesDir" "$srcJarDir" "$emptyDir" && ` +
+ Command: `rm -rf "$classesDir" "$headerClassesDir" "$srcJarDir" "$kotlinBuildFile" "$emptyDir" && ` +
+ `mkdir -p "$classesDir" "$headerClassesDir" "$srcJarDir" "$emptyDir" && ` +
`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
`${config.GenKotlinBuildFileCmd} --classpath "$classpath" --name "$name"` +
` --out_dir "$classesDir" --srcs "$out.rsp" --srcs "$srcJarDir/list"` +
` $commonSrcFilesArg --out "$kotlinBuildFile" && ` +
`${config.KotlincCmd} ${config.KotlincGlobalFlags} ` +
- `${config.KotlincSuppressJDK9Warnings} ${config.JavacHeapFlags} ` +
- `$kotlincFlags -jvm-target $kotlinJvmTarget -Xbuild-file=$kotlinBuildFile ` +
- `-kotlin-home $emptyDir && ` +
- `${config.SoongZipCmd} -jar -o $out -C $classesDir -D $classesDir && ` +
+ ` ${config.KotlincSuppressJDK9Warnings} ${config.JavacHeapFlags} ` +
+ ` $kotlincFlags -jvm-target $kotlinJvmTarget -Xbuild-file=$kotlinBuildFile ` +
+ ` -kotlin-home $emptyDir ` +
+ ` -Xplugin=${config.KotlinAbiGenPluginJar} ` +
+ ` -P plugin:org.jetbrains.kotlin.jvm.abi:outputDir=$headerClassesDir && ` +
+ `${config.SoongZipCmd} -jar -o $out -C $classesDir -D $classesDir -write_if_changed && ` +
+ `${config.SoongZipCmd} -jar -o $headerJar -C $headerClassesDir -D $headerClassesDir -write_if_changed && ` +
`rm -rf "$srcJarDir"`,
CommandDeps: []string{
"${config.KotlincCmd}",
@@ -49,15 +52,17 @@
"${config.KotlinStdlibJar}",
"${config.KotlinTrove4jJar}",
"${config.KotlinAnnotationJar}",
+ "${config.KotlinAbiGenPluginJar}",
"${config.GenKotlinBuildFileCmd}",
"${config.SoongZipCmd}",
"${config.ZipSyncCmd}",
},
Rspfile: "$out.rsp",
RspfileContent: `$in`,
+ Restat: true,
},
"kotlincFlags", "classpath", "srcJars", "commonSrcFilesArg", "srcJarDir", "classesDir",
- "kotlinJvmTarget", "kotlinBuildFile", "emptyDir", "name")
+ "headerClassesDir", "headerJar", "kotlinJvmTarget", "kotlinBuildFile", "emptyDir", "name")
func kotlinCommonSrcsList(ctx android.ModuleContext, commonSrcFiles android.Paths) android.OptionalPath {
if len(commonSrcFiles) > 0 {
@@ -76,7 +81,7 @@
}
// kotlinCompile takes .java and .kt sources and srcJars, and compiles the .kt sources into a classes jar in outputFile.
-func kotlinCompile(ctx android.ModuleContext, outputFile android.WritablePath,
+func kotlinCompile(ctx android.ModuleContext, outputFile, headerOutputFile android.WritablePath,
srcFiles, commonSrcFiles, srcJars android.Paths,
flags javaBuilderFlags) {
@@ -97,17 +102,20 @@
}
ctx.Build(pctx, android.BuildParams{
- Rule: kotlinc,
- Description: "kotlinc",
- Output: outputFile,
- Inputs: srcFiles,
- Implicits: deps,
+ Rule: kotlinc,
+ Description: "kotlinc",
+ Output: outputFile,
+ ImplicitOutput: headerOutputFile,
+ Inputs: srcFiles,
+ Implicits: deps,
Args: map[string]string{
"classpath": flags.kotlincClasspath.FormJavaClassPath(""),
"kotlincFlags": flags.kotlincFlags,
"commonSrcFilesArg": commonSrcFilesArg,
"srcJars": strings.Join(srcJars.Strings(), " "),
"classesDir": android.PathForModuleOut(ctx, "kotlinc", "classes").String(),
+ "headerClassesDir": android.PathForModuleOut(ctx, "kotlinc", "header_classes").String(),
+ "headerJar": headerOutputFile.String(),
"srcJarDir": android.PathForModuleOut(ctx, "kotlinc", "srcJars").String(),
"kotlinBuildFile": android.PathForModuleOut(ctx, "kotlinc-build.xml").String(),
"emptyDir": android.PathForModuleOut(ctx, "kotlinc", "empty").String(),
diff --git a/java/kotlin_test.go b/java/kotlin_test.go
index d51bc04..f9ff982 100644
--- a/java/kotlin_test.go
+++ b/java/kotlin_test.go
@@ -45,6 +45,10 @@
fooKotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
fooJavac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
fooJar := ctx.ModuleForTests("foo", "android_common").Output("combined/foo.jar")
+ fooHeaderJar := ctx.ModuleForTests("foo", "android_common").Output("turbine-combined/foo.jar")
+
+ fooKotlincClasses := fooKotlinc.Output
+ fooKotlincHeaderClasses := fooKotlinc.ImplicitOutput
if len(fooKotlinc.Inputs) != 2 || fooKotlinc.Inputs[0].String() != "a.java" ||
fooKotlinc.Inputs[1].String() != "b.kt" {
@@ -55,17 +59,21 @@
t.Errorf(`foo inputs %v != ["a.java"]`, fooJavac.Inputs)
}
- if !strings.Contains(fooJavac.Args["classpath"], fooKotlinc.Output.String()) {
+ if !strings.Contains(fooJavac.Args["classpath"], fooKotlincHeaderClasses.String()) {
t.Errorf("foo classpath %v does not contain %q",
- fooJavac.Args["classpath"], fooKotlinc.Output.String())
+ fooJavac.Args["classpath"], fooKotlincHeaderClasses.String())
}
- if !inList(fooKotlinc.Output.String(), fooJar.Inputs.Strings()) {
+ if !inList(fooKotlincClasses.String(), fooJar.Inputs.Strings()) {
t.Errorf("foo jar inputs %v does not contain %q",
- fooJar.Inputs.Strings(), fooKotlinc.Output.String())
+ fooJar.Inputs.Strings(), fooKotlincClasses.String())
}
- fooHeaderJar := ctx.ModuleForTests("foo", "android_common").Output("turbine-combined/foo.jar")
+ if !inList(fooKotlincHeaderClasses.String(), fooHeaderJar.Inputs.Strings()) {
+ t.Errorf("foo header jar inputs %v does not contain %q",
+ fooHeaderJar.Inputs.Strings(), fooKotlincHeaderClasses.String())
+ }
+
bazHeaderJar := ctx.ModuleForTests("baz", "android_common").Output("turbine-combined/baz.jar")
barKotlinc := ctx.ModuleForTests("bar", "android_common").Rule("kotlinc")
diff --git a/mk2rbc/expr.go b/mk2rbc/expr.go
index dc16d1d..54bb6d1 100644
--- a/mk2rbc/expr.go
+++ b/mk2rbc/expr.go
@@ -221,11 +221,9 @@
}
func (xi *interpolateExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
- argsCopy := make([]starlarkExpr, len(xi.args))
- for i, arg := range xi.args {
- argsCopy[i] = arg.transform(transformer)
+ for i := range xi.args {
+ xi.args[i] = xi.args[i].transform(transformer)
}
- xi.args = argsCopy
if replacement := transformer(xi); replacement != nil {
return replacement
} else {
@@ -591,11 +589,9 @@
if cx.object != nil {
cx.object = cx.object.transform(transformer)
}
- argsCopy := make([]starlarkExpr, len(cx.args))
- for i, arg := range cx.args {
- argsCopy[i] = arg.transform(transformer)
+ for i := range cx.args {
+ cx.args[i] = cx.args[i].transform(transformer)
}
- cx.args = argsCopy
if replacement := transformer(cx); replacement != nil {
return replacement
} else {
@@ -769,3 +765,35 @@
x, ok := expr.(*stringLiteralExpr)
return ok && x.literal == ""
}
+
+func negateExpr(expr starlarkExpr) starlarkExpr {
+ switch typedExpr := expr.(type) {
+ case *notExpr:
+ return typedExpr.expr
+ case *inExpr:
+ typedExpr.isNot = !typedExpr.isNot
+ return typedExpr
+ case *eqExpr:
+ typedExpr.isEq = !typedExpr.isEq
+ return typedExpr
+ case *binaryOpExpr:
+ switch typedExpr.op {
+ case ">":
+ typedExpr.op = "<="
+ return typedExpr
+ case "<":
+ typedExpr.op = ">="
+ return typedExpr
+ case ">=":
+ typedExpr.op = "<"
+ return typedExpr
+ case "<=":
+ typedExpr.op = ">"
+ return typedExpr
+ default:
+ return ¬Expr{expr: expr}
+ }
+ default:
+ return ¬Expr{expr: expr}
+ }
+}
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
index 950a1e5..8807437 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -86,7 +86,7 @@
"filter": &simpleCallParser{name: baseName + ".filter", returnType: starlarkTypeList},
"filter-out": &simpleCallParser{name: baseName + ".filter_out", returnType: starlarkTypeList},
"firstword": &firstOrLastwordCallParser{isLastWord: false},
- "foreach": &foreachCallPaser{},
+ "foreach": &foreachCallParser{},
"if": &ifCallParser{},
"info": &makeControlFuncParser{name: baseName + ".mkinfo"},
"is-board-platform": &simpleCallParser{name: baseName + ".board_platform_is", returnType: starlarkTypeBool, addGlobals: true},
@@ -117,6 +117,17 @@
"wildcard": &simpleCallParser{name: baseName + ".expand_wildcard", returnType: starlarkTypeList},
}
+// The same as knownFunctions, but returns a []starlarkNode instead of a starlarkExpr
+var knownNodeFunctions = map[string]interface {
+ parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode
+}{
+ "eval": &evalNodeParser{},
+ "if": &ifCallNodeParser{},
+ "inherit-product": &inheritProductCallParser{loadAlways: true},
+ "inherit-product-if-exists": &inheritProductCallParser{loadAlways: false},
+ "foreach": &foreachCallNodeParser{},
+}
+
// These are functions that we don't implement conversions for, but
// we allow seeing their definitions in the product config files.
var ignoredDefines = map[string]bool{
@@ -846,15 +857,19 @@
return res
}
-func (ctx *parseContext) handleInheritModule(v mkparser.Node, args *mkparser.MakeString, loadAlways bool) []starlarkNode {
+type inheritProductCallParser struct {
+ loadAlways bool
+}
+
+func (p *inheritProductCallParser) parse(ctx *parseContext, v mkparser.Node, args *mkparser.MakeString) []starlarkNode {
args.TrimLeftSpaces()
args.TrimRightSpaces()
pathExpr := ctx.parseMakeString(v, args)
if _, ok := pathExpr.(*badExpr); ok {
return []starlarkNode{ctx.newBadNode(v, "Unable to parse argument to inherit")}
}
- return ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) starlarkNode {
- return &inheritNode{im, loadAlways}
+ return ctx.handleSubConfig(v, pathExpr, p.loadAlways, func(im inheritedModule) starlarkNode {
+ return &inheritNode{im, p.loadAlways}
})
}
@@ -873,19 +888,12 @@
// $(error xxx)
// $(call other-custom-functions,...)
- // inherit-product(-if-exists) gets converted to a series of statements,
- // not just a single expression like parseReference returns. So handle it
- // separately at the beginning here.
- if strings.HasPrefix(v.Name.Dump(), "call inherit-product,") {
- args := v.Name.Clone()
- args.ReplaceLiteral("call inherit-product,", "")
- return ctx.handleInheritModule(v, args, true)
+ if name, args, ok := ctx.maybeParseFunctionCall(v, v.Name); ok {
+ if kf, ok := knownNodeFunctions[name]; ok {
+ return kf.parse(ctx, v, args)
+ }
}
- if strings.HasPrefix(v.Name.Dump(), "call inherit-product-if-exists,") {
- args := v.Name.Clone()
- args.ReplaceLiteral("call inherit-product-if-exists,", "")
- return ctx.handleInheritModule(v, args, false)
- }
+
return []starlarkNode{&exprNode{expr: ctx.parseReference(v, v.Name)}}
}
@@ -1030,49 +1038,19 @@
otherOperand = xLeft
}
- not := func(expr starlarkExpr) starlarkExpr {
- switch typedExpr := expr.(type) {
- case *inExpr:
- typedExpr.isNot = !typedExpr.isNot
- return typedExpr
- case *eqExpr:
- typedExpr.isEq = !typedExpr.isEq
- return typedExpr
- case *binaryOpExpr:
- switch typedExpr.op {
- case ">":
- typedExpr.op = "<="
- return typedExpr
- case "<":
- typedExpr.op = ">="
- return typedExpr
- case ">=":
- typedExpr.op = "<"
- return typedExpr
- case "<=":
- typedExpr.op = ">"
- return typedExpr
- default:
- return ¬Expr{expr: expr}
- }
- default:
- return ¬Expr{expr: expr}
- }
- }
-
// If we've identified one of the operands as being a string literal, check
// for some special cases we can do to simplify the resulting expression.
if otherOperand != nil {
if stringOperand == "" {
if isEq {
- return not(otherOperand)
+ return negateExpr(otherOperand)
} else {
return otherOperand
}
}
if stringOperand == "true" && otherOperand.typ() == starlarkTypeBool {
if !isEq {
- return not(otherOperand)
+ return negateExpr(otherOperand)
} else {
return otherOperand
}
@@ -1228,6 +1206,37 @@
right: xValue, isEq: !negate}
}
+func (ctx *parseContext) maybeParseFunctionCall(node mkparser.Node, ref *mkparser.MakeString) (name string, args *mkparser.MakeString, ok bool) {
+ ref.TrimLeftSpaces()
+ ref.TrimRightSpaces()
+
+ words := ref.SplitN(" ", 2)
+ if !words[0].Const() {
+ return "", nil, false
+ }
+
+ name = words[0].Dump()
+ args = mkparser.SimpleMakeString("", words[0].Pos())
+ if len(words) >= 2 {
+ args = words[1]
+ }
+ args.TrimLeftSpaces()
+ if name == "call" {
+ words = args.SplitN(",", 2)
+ if words[0].Empty() || !words[0].Const() {
+ return "", nil, false
+ }
+ name = words[0].Dump()
+ if len(words) < 2 {
+ args = &mkparser.MakeString{}
+ } else {
+ args = words[1]
+ }
+ }
+ ok = true
+ return
+}
+
// parses $(...), returning an expression
func (ctx *parseContext) parseReference(node mkparser.Node, ref *mkparser.MakeString) starlarkExpr {
ref.TrimLeftSpaces()
@@ -1242,7 +1251,7 @@
// If it is a single word, it can be a simple variable
// reference or a function call
- if len(words) == 1 && !isMakeControlFunc(refDump) && refDump != "shell" {
+ if len(words) == 1 && !isMakeControlFunc(refDump) && refDump != "shell" && refDump != "eval" {
if strings.HasPrefix(refDump, soongNsPrefix) {
// TODO (asmundak): if we find many, maybe handle them.
return ctx.newBadExpr(node, "SOONG_CONFIG_ variables cannot be referenced, use soong_config_get instead: %s", refDump)
@@ -1281,28 +1290,14 @@
return ctx.newBadExpr(node, "unknown variable %s", refDump)
}
- expr := &callExpr{name: words[0].Dump(), returnType: starlarkTypeUnknown}
- args := mkparser.SimpleMakeString("", words[0].Pos())
- if len(words) >= 2 {
- args = words[1]
- }
- args.TrimLeftSpaces()
- if expr.name == "call" {
- words = args.SplitN(",", 2)
- if words[0].Empty() || !words[0].Const() {
- return ctx.newBadExpr(node, "cannot handle %s", refDump)
- }
- expr.name = words[0].Dump()
- if len(words) < 2 {
- args = &mkparser.MakeString{}
+ if name, args, ok := ctx.maybeParseFunctionCall(node, ref); ok {
+ if kf, found := knownFunctions[name]; found {
+ return kf.parse(ctx, node, args)
} else {
- args = words[1]
+ return ctx.newBadExpr(node, "cannot handle invoking %s", name)
}
- }
- if kf, found := knownFunctions[expr.name]; found {
- return kf.parse(ctx, node, args)
} else {
- return ctx.newBadExpr(node, "cannot handle invoking %s", expr.name)
+ return ctx.newBadExpr(node, "cannot handle %s", refDump)
}
}
@@ -1486,9 +1481,46 @@
}
}
-type foreachCallPaser struct{}
+type ifCallNodeParser struct{}
-func (p *foreachCallPaser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+func (p *ifCallNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
+ words := args.Split(",")
+ if len(words) != 2 && len(words) != 3 {
+ return []starlarkNode{ctx.newBadNode(node, "if function should have 2 or 3 arguments, found "+strconv.Itoa(len(words)))}
+ }
+
+ ifn := &ifNode{expr: ctx.parseMakeString(node, words[0])}
+ cases := []*switchCase{
+ {
+ gate: ifn,
+ nodes: ctx.parseNodeMakeString(node, words[1]),
+ },
+ }
+ if len(words) == 3 {
+ cases = append(cases, &switchCase{
+ gate: &elseNode{},
+ nodes: ctx.parseNodeMakeString(node, words[2]),
+ })
+ }
+ if len(cases) == 2 {
+ if len(cases[1].nodes) == 0 {
+ // Remove else branch if it has no contents
+ cases = cases[:1]
+ } else if len(cases[0].nodes) == 0 {
+ // If the if branch has no contents but the else does,
+ // move them to the if and negate its condition
+ ifn.expr = negateExpr(ifn.expr)
+ cases[0].nodes = cases[1].nodes
+ cases = cases[:1]
+ }
+ }
+
+ return []starlarkNode{&switchNode{ssCases: cases}}
+}
+
+type foreachCallParser struct{}
+
+func (p *foreachCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
words := args.Split(",")
if len(words) != 3 {
return ctx.newBadExpr(node, "foreach function should have 3 arguments, found "+strconv.Itoa(len(words)))
@@ -1520,6 +1552,71 @@
}
}
+func transformNode(node starlarkNode, transformer func(expr starlarkExpr) starlarkExpr) {
+ switch a := node.(type) {
+ case *ifNode:
+ a.expr = a.expr.transform(transformer)
+ case *switchCase:
+ transformNode(a.gate, transformer)
+ for _, n := range a.nodes {
+ transformNode(n, transformer)
+ }
+ case *switchNode:
+ for _, n := range a.ssCases {
+ transformNode(n, transformer)
+ }
+ case *exprNode:
+ a.expr = a.expr.transform(transformer)
+ case *assignmentNode:
+ a.value = a.value.transform(transformer)
+ case *foreachNode:
+ a.list = a.list.transform(transformer)
+ for _, n := range a.actions {
+ transformNode(n, transformer)
+ }
+ }
+}
+
+type foreachCallNodeParser struct{}
+
+func (p *foreachCallNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
+ words := args.Split(",")
+ if len(words) != 3 {
+ return []starlarkNode{ctx.newBadNode(node, "foreach function should have 3 arguments, found "+strconv.Itoa(len(words)))}
+ }
+ if !words[0].Const() || words[0].Empty() || !identifierFullMatchRegex.MatchString(words[0].Strings[0]) {
+ return []starlarkNode{ctx.newBadNode(node, "first argument to foreach function must be a simple string identifier")}
+ }
+
+ loopVarName := words[0].Strings[0]
+
+ list := ctx.parseMakeString(node, words[1])
+ if list.typ() != starlarkTypeList {
+ list = &callExpr{
+ name: baseName + ".words",
+ returnType: starlarkTypeList,
+ args: []starlarkExpr{list},
+ }
+ }
+
+ actions := ctx.parseNodeMakeString(node, words[2])
+ // TODO(colefaust): Replace transforming code with something more elegant
+ for _, action := range actions {
+ transformNode(action, func(expr starlarkExpr) starlarkExpr {
+ if varRefExpr, ok := expr.(*variableRefExpr); ok && varRefExpr.ref.name() == loopVarName {
+ return &identifierExpr{loopVarName}
+ }
+ return nil
+ })
+ }
+
+ return []starlarkNode{&foreachNode{
+ varName: loopVarName,
+ list: list,
+ actions: actions,
+ }}
+}
+
type wordCallParser struct{}
func (p *wordCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
@@ -1630,6 +1727,31 @@
}
}
+type evalNodeParser struct{}
+
+func (p *evalNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
+ parser := mkparser.NewParser("Eval expression", strings.NewReader(args.Dump()))
+ nodes, errs := parser.Parse()
+ if errs != nil {
+ return []starlarkNode{ctx.newBadNode(node, "Unable to parse eval statement")}
+ }
+
+ if len(nodes) == 0 {
+ return []starlarkNode{}
+ } else if len(nodes) == 1 {
+ switch n := nodes[0].(type) {
+ case *mkparser.Assignment:
+ if n.Name.Const() {
+ return ctx.handleAssignment(n)
+ }
+ case *mkparser.Comment:
+ return []starlarkNode{&commentNode{strings.TrimSpace("#" + n.Comment)}}
+ }
+ }
+
+ return []starlarkNode{ctx.newBadNode(node, "Eval expression too complex; only assignments and comments are supported")}
+}
+
func (ctx *parseContext) parseMakeString(node mkparser.Node, mk *mkparser.MakeString) starlarkExpr {
if mk.Const() {
return &stringLiteralExpr{mk.Dump()}
@@ -1654,6 +1776,16 @@
return NewInterpolateExpr(parts)
}
+func (ctx *parseContext) parseNodeMakeString(node mkparser.Node, mk *mkparser.MakeString) []starlarkNode {
+ // Discard any constant values in the make string, as they would be top level
+ // string literals and do nothing.
+ result := make([]starlarkNode, 0, len(mk.Variables))
+ for i := range mk.Variables {
+ result = append(result, ctx.handleVariable(&mk.Variables[i])...)
+ }
+ return result
+}
+
// Handles the statements whose treatment is the same in all contexts: comment,
// assignment, variable (which is a macro call in reality) and all constructs that
// do not handle in any context ('define directive and any unrecognized stuff).
@@ -1698,6 +1830,7 @@
if result == nil {
result = []starlarkNode{}
}
+
return result
}
diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go
index 2b447e3..3698813 100644
--- a/mk2rbc/mk2rbc_test.go
+++ b/mk2rbc/mk2rbc_test.go
@@ -1313,6 +1313,11 @@
FOREACH_WITH_IF := $(foreach module,\
$(BOOT_KERNEL_MODULES_LIST),\
$(if $(filter $(module),foo.ko),,$(error module "$(module)" has an error!)))
+
+# Same as above, but not assigning it to a variable allows it to be converted to statements
+$(foreach module,\
+ $(BOOT_KERNEL_MODULES_LIST),\
+ $(if $(filter $(module),foo.ko),,$(error module "$(module)" has an error!)))
`,
expected: `load("//build/make/core:product_config.rbc", "rblf")
@@ -1324,6 +1329,10 @@
g["BOOT_KERNEL_MODULES_LIST"] += ["bar.ko"]
g["BOOT_KERNEL_MODULES_FILTER_2"] = ["%%/%s" % m for m in g["BOOT_KERNEL_MODULES_LIST"]]
g["FOREACH_WITH_IF"] = [("" if rblf.filter(module, "foo.ko") else rblf.mkerror("product.mk", "module \"%s\" has an error!" % module)) for module in g["BOOT_KERNEL_MODULES_LIST"]]
+ # Same as above, but not assigning it to a variable allows it to be converted to statements
+ for module in g["BOOT_KERNEL_MODULES_LIST"]:
+ if not rblf.filter(module, "foo.ko"):
+ rblf.mkerror("product.mk", "module \"%s\" has an error!" % module)
`,
},
{
@@ -1474,6 +1483,34 @@
`,
},
+ {
+ desc: "Evals",
+ mkname: "product.mk",
+ in: `
+$(eval)
+$(eval MY_VAR := foo)
+$(eval # This is a test of eval functions)
+$(eval $(TOO_COMPLICATED) := bar)
+$(foreach x,$(MY_LIST_VAR), \
+ $(eval PRODUCT_COPY_FILES += foo/bar/$(x):$(TARGET_COPY_OUT_VENDOR)/etc/$(x)) \
+ $(if $(MY_OTHER_VAR),$(eval PRODUCT_COPY_FILES += $(MY_OTHER_VAR):foo/bar/$(x))) \
+)
+
+`,
+ expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+ cfg = rblf.cfg(handle)
+ g["MY_VAR"] = "foo"
+ # This is a test of eval functions
+ rblf.mk2rbc_error("product.mk:5", "Eval expression too complex; only assignments and comments are supported")
+ for x in rblf.words(g.get("MY_LIST_VAR", "")):
+ rblf.setdefault(handle, "PRODUCT_COPY_FILES")
+ cfg["PRODUCT_COPY_FILES"] += ("foo/bar/%s:%s/etc/%s" % (x, g.get("TARGET_COPY_OUT_VENDOR", ""), x)).split()
+ if g.get("MY_OTHER_VAR", ""):
+ cfg["PRODUCT_COPY_FILES"] += ("%s:foo/bar/%s" % (g.get("MY_OTHER_VAR", ""), x)).split()
+`,
+ },
}
var known_variables = []struct {
diff --git a/mk2rbc/node.go b/mk2rbc/node.go
index 9d5af91..c0c4c98 100644
--- a/mk2rbc/node.go
+++ b/mk2rbc/node.go
@@ -294,3 +294,28 @@
ssCase.emit(gctx)
}
}
+
+type foreachNode struct {
+ varName string
+ list starlarkExpr
+ actions []starlarkNode
+}
+
+func (f *foreachNode) emit(gctx *generationContext) {
+ gctx.newLine()
+ gctx.writef("for %s in ", f.varName)
+ f.list.emit(gctx)
+ gctx.write(":")
+ gctx.indentLevel++
+ hasStatements := false
+ for _, a := range f.actions {
+ if _, ok := a.(*commentNode); !ok {
+ hasStatements = true
+ }
+ a.emit(gctx)
+ }
+ if !hasStatements {
+ gctx.emitPass()
+ }
+ gctx.indentLevel--
+}
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index 30700dd..802e1da 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -28,6 +28,7 @@
"prebuilts/rust",
"system/core/debuggerd/rust",
"system/core/libstats/pull_rust",
+ "system/core/trusty/libtrusty-rs",
"system/extras/profcollectd",
"system/extras/simpleperf",
"system/hardware/interfaces/keystore2",
diff --git a/scripts/hiddenapi/analyze_bcpf.py b/scripts/hiddenapi/analyze_bcpf.py
index 1ad8d07..595343b 100644
--- a/scripts/hiddenapi/analyze_bcpf.py
+++ b/scripts/hiddenapi/analyze_bcpf.py
@@ -319,12 +319,48 @@
@dataclasses.dataclass()
+class PackagePropertyReason:
+ """Provides the reasons why a package was added to a specific property.
+
+ A split package is one that contains classes from the bootclasspath_fragment
+ and other bootclasspath modules. So, for a split package this contains the
+ corresponding lists of classes.
+
+ A single package is one that contains classes sub-packages from the
+ For a split package this contains a list of classes in that package that are
+ provided by the bootclasspath_fragment and a list of classes
+ """
+
+ # The list of classes/sub-packages that is provided by the
+ # bootclasspath_fragment.
+ bcpf: typing.List[str]
+
+ # The list of classes/sub-packages that is provided by other modules on the
+ # bootclasspath.
+ other: typing.List[str]
+
+
+@dataclasses.dataclass()
class Result:
"""Encapsulates the result of the analysis."""
# The diffs in the flags.
diffs: typing.Optional[FlagDiffs] = None
+ # A map from package name to the reason why it belongs in the
+ # split_packages property.
+ split_packages: typing.Dict[str, PackagePropertyReason] = dataclasses.field(
+ default_factory=dict)
+
+ # A map from package name to the reason why it belongs in the
+ # single_packages property.
+ single_packages: typing.Dict[str,
+ PackagePropertyReason] = dataclasses.field(
+ default_factory=dict)
+
+ # The list of packages to add to the package_prefixes property.
+ package_prefixes: typing.List[str] = dataclasses.field(default_factory=list)
+
# The bootclasspath_fragment hidden API properties changes.
property_changes: typing.List[HiddenApiPropertyChange] = dataclasses.field(
default_factory=list)
@@ -394,13 +430,17 @@
def reformat_report_test(text):
return re.sub(r"(.)\n([^\s])", r"\1 \2", text)
- def report(self, text, **kwargs):
+ def report(self, text="", **kwargs):
# Concatenate lines that are not separated by a blank line together to
# eliminate formatting applied to the supplied text to adhere to python
# line length limitations.
text = self.reformat_report_test(text)
logging.info("%s", text, **kwargs)
+ def report_dedent(self, text, **kwargs):
+ text = textwrap.dedent(text)
+ self.report(text, **kwargs)
+
def run_command(self, cmd, *args, **kwargs):
cmd_line = " ".join(cmd)
logging.debug("Running %s", cmd_line)
@@ -442,9 +482,7 @@
def load_module_info(self):
module_info_file = os.path.join(self.product_out_dir,
"module-info.json")
- self.report(f"""
-Making sure that {module_info_file} is up to date.
-""")
+ self.report(f"\nMaking sure that {module_info_file} is up to date.\n")
output = self.build_file_read_output(module_info_file)
lines = output.lines()
for line in lines:
@@ -496,61 +534,62 @@
optimizations that can be applied.
"""
self.report(f"Analyzing bootclasspath_fragment module {self.bcpf}")
- self.report(f"""
-Run this tool to help initialize a bootclasspath_fragment module. Before you
-start make sure that:
+ self.report_dedent(f"""
+ Run this tool to help initialize a bootclasspath_fragment module.
+ Before you start make sure that:
-1. The current checkout is up to date.
+ 1. The current checkout is up to date.
-2. The environment has been initialized using lunch, e.g.
- lunch aosp_arm64-userdebug
+ 2. The environment has been initialized using lunch, e.g.
+ lunch aosp_arm64-userdebug
-3. You have added a bootclasspath_fragment module to the appropriate Android.bp
-file. Something like this:
+ 3. You have added a bootclasspath_fragment module to the appropriate
+ Android.bp file. Something like this:
- bootclasspath_fragment {{
- name: "{self.bcpf}",
- contents: [
- "...",
- ],
+ bootclasspath_fragment {{
+ name: "{self.bcpf}",
+ contents: [
+ "...",
+ ],
+
+ // The bootclasspath_fragments that provide APIs on which this
+ // depends.
+ fragments: [
+ {{
+ apex: "com.android.art",
+ module: "art-bootclasspath-fragment",
+ }},
+ ],
+ }}
+
+ 4. You have added it to the platform_bootclasspath module in
+ frameworks/base/boot/Android.bp. Something like this:
- // The bootclasspath_fragments that provide APIs on which this depends.
- fragments: [
- {{
- apex: "com.android.art",
- module: "art-bootclasspath-fragment",
- }},
- ],
- }}
+ platform_bootclasspath {{
+ name: "platform-bootclasspath",
+ fragments: [
+ ...
+ {{
+ apex: "{self.apex}",
+ module: "{self.bcpf}",
+ }},
+ ],
+ }}
-4. You have added it to the platform_bootclasspath module in
-frameworks/base/boot/Android.bp. Something like this:
+ 5. You have added an sdk module. Something like this:
- platform_bootclasspath {{
- name: "platform-bootclasspath",
- fragments: [
- ...
- {{
- apex: "{self.apex}",
- module: "{self.bcpf}",
- }},
- ],
- }}
-
-5. You have added an sdk module. Something like this:
-
- sdk {{
- name: "{self.sdk}",
- bootclasspath_fragments: ["{self.bcpf}"],
- }}
-""")
+ sdk {{
+ name: "{self.sdk}",
+ bootclasspath_fragments: ["{self.bcpf}"],
+ }}
+ """)
# Make sure that the module-info.json file is up to date.
self.load_module_info()
- self.report("""
-Cleaning potentially stale files.
-""")
+ self.report_dedent("""
+ Cleaning potentially stale files.
+ """)
# Remove the out/soong/hiddenapi files.
shutil.rmtree(f"{self.out_dir}/soong/hiddenapi", ignore_errors=True)
@@ -605,26 +644,26 @@
if result.file_changes:
if self.fix:
- file_change_message = """
-The following files were modified by this script:"""
+ file_change_message = textwrap.dedent("""
+ The following files were modified by this script:
+ """)
else:
- file_change_message = """
-The following modifications need to be made:"""
+ file_change_message = textwrap.dedent("""
+ The following modifications need to be made:
+ """)
- self.report(f"""
-{file_change_message}""")
+ self.report(file_change_message)
result.file_changes.sort()
for file_change in result.file_changes:
- self.report(f"""
- {file_change.path}
- {file_change.description}
-""".lstrip("\n"))
+ self.report(f" {file_change.path}")
+ self.report(f" {file_change.description}")
+ self.report()
if not self.fix:
- self.report("""
-Run the command again with the --fix option to automatically make the above
-changes.
-""".lstrip())
+ self.report_dedent("""
+ Run the command again with the --fix option to automatically
+ make the above changes.
+ """.lstrip("\n"))
def new_file_change(self, file, description):
return FileChange(
@@ -635,11 +674,10 @@
if not (module_line.startswith("< ") and
monolithic_line.startswith("> ") and not separator_line):
# Something went wrong.
- self.report(f"""Invalid build output detected:
- module_line: "{module_line}"
- monolithic_line: "{monolithic_line}"
- separator_line: "{separator_line}"
-""")
+ self.report("Invalid build output detected:")
+ self.report(f" module_line: '{module_line}'")
+ self.report(f" monolithic_line: '{monolithic_line}'")
+ self.report(f" separator_line: '{separator_line}'")
sys.exit(1)
if significant:
@@ -698,10 +736,9 @@
if module_signature != monolithic_signature:
# Something went wrong.
- self.report(f"""Inconsistent signatures detected:
- module_signature: "{module_signature}"
- monolithic_signature: "{monolithic_signature}"
-""")
+ self.report("Inconsistent signatures detected:")
+ self.report(f" module_signature: '{module_signature}'")
+ self.report(f" monolithic_signature: '{monolithic_signature}'")
sys.exit(1)
diffs[module_signature] = (module_flags, monolithic_flags)
@@ -749,85 +786,91 @@
return diffs
def build_monolithic_stubs_flags(self):
- self.report(f"""
-Attempting to build {_STUB_FLAGS_FILE} to verify that the
-bootclasspath_fragment has the correct API stubs available...
-""")
+ self.report_dedent(f"""
+ Attempting to build {_STUB_FLAGS_FILE} to verify that the
+ bootclasspath_fragment has the correct API stubs available...
+ """)
# Build the hiddenapi-stubs-flags.txt file.
diffs = self.build_hiddenapi_flags(_STUB_FLAGS_FILE)
if diffs:
- self.report(f"""
-There is a discrepancy between the stub API derived flags created by the
-bootclasspath_fragment and the platform_bootclasspath. See preceding error
-messages to see which flags are inconsistent. The inconsistencies can occur for
-a couple of reasons:
+ self.report_dedent(f"""
+ There is a discrepancy between the stub API derived flags
+ created by the bootclasspath_fragment and the
+ platform_bootclasspath. See preceding error messages to see
+ which flags are inconsistent. The inconsistencies can occur for
+ a couple of reasons:
-If you are building against prebuilts of the Android SDK, e.g. by using
-TARGET_BUILD_APPS then the prebuilt versions of the APIs this
-bootclasspath_fragment depends upon are out of date and need updating. See
-go/update-prebuilts for help.
+ If you are building against prebuilts of the Android SDK, e.g.
+ by using TARGET_BUILD_APPS then the prebuilt versions of the
+ APIs this bootclasspath_fragment depends upon are out of date
+ and need updating. See go/update-prebuilts for help.
-Otherwise, this is happening because there are some stub APIs that are either
-provided by or used by the contents of the bootclasspath_fragment but which are
-not available to it. There are 4 ways to handle this:
+ Otherwise, this is happening because there are some stub APIs
+ that are either provided by or used by the contents of the
+ bootclasspath_fragment but which are not available to it. There
+ are 4 ways to handle this:
-1. A java_sdk_library in the contents property will automatically make its stub
- APIs available to the bootclasspath_fragment so nothing needs to be done.
+ 1. A java_sdk_library in the contents property will
+ automatically make its stub APIs available to the
+ bootclasspath_fragment so nothing needs to be done.
-2. If the API provided by the bootclasspath_fragment is created by an api_only
- java_sdk_library (or a java_library that compiles files generated by a
- separate droidstubs module then it cannot be added to the contents and
- instead must be added to the api.stubs property, e.g.
+ 2. If the API provided by the bootclasspath_fragment is created
+ by an api_only java_sdk_library (or a java_library that compiles
+ files generated by a separate droidstubs module then it cannot
+ be added to the contents and instead must be added to the
+ api.stubs property, e.g.
- bootclasspath_fragment {{
- name: "{self.bcpf}",
- ...
- api: {{
- stubs: ["$MODULE-api-only"],"
- }},
- }}
+ bootclasspath_fragment {{
+ name: "{self.bcpf}",
+ ...
+ api: {{
+ stubs: ["$MODULE-api-only"],"
+ }},
+ }}
-3. If the contents use APIs provided by another bootclasspath_fragment then
- it needs to be added to the fragments property, e.g.
+ 3. If the contents use APIs provided by another
+ bootclasspath_fragment then it needs to be added to the
+ fragments property, e.g.
+
+ bootclasspath_fragment {{
+ name: "{self.bcpf}",
+ ...
+ // The bootclasspath_fragments that provide APIs on which this depends.
+ fragments: [
+ ...
+ {{
+ apex: "com.android.other",
+ module: "com.android.other-bootclasspath-fragment",
+ }},
+ ],
+ }}
+
+ 4. If the contents use APIs from a module that is not part of
+ another bootclasspath_fragment then it must be added to the
+ additional_stubs property, e.g.
- bootclasspath_fragment {{
- name: "{self.bcpf}",
- ...
- // The bootclasspath_fragments that provide APIs on which this depends.
- fragments: [
- ...
- {{
- apex: "com.android.other",
- module: "com.android.other-bootclasspath-fragment",
- }},
- ],
- }}
+ bootclasspath_fragment {{
+ name: "{self.bcpf}",
+ ...
+ additional_stubs: ["android-non-updatable"],
+ }}
-4. If the contents use APIs from a module that is not part of another
- bootclasspath_fragment then it must be added to the additional_stubs
- property, e.g.
+ Like the api.stubs property these are typically
+ java_sdk_library modules but can be java_library too.
- bootclasspath_fragment {{
- name: "{self.bcpf}",
- ...
- additional_stubs: ["android-non-updatable"],
- }}
-
- Like the api.stubs property these are typically java_sdk_library modules but
- can be java_library too.
-
- Note: The "android-non-updatable" is treated as if it was a java_sdk_library
- which it is not at the moment but will be in future.
-""")
+ Note: The "android-non-updatable" is treated as if it was a
+ java_sdk_library which it is not at the moment but will be in
+ future.
+ """)
return diffs
def build_monolithic_flags(self, result):
- self.report(f"""
-Attempting to build {_FLAGS_FILE} to verify that the
-bootclasspath_fragment has the correct hidden API flags...
-""")
+ self.report_dedent(f"""
+ Attempting to build {_FLAGS_FILE} to verify that the
+ bootclasspath_fragment has the correct hidden API flags...
+ """)
# Build the hiddenapi-flags.csv file and extract any differences in
# the flags between this bootclasspath_fragment and the monolithic
@@ -838,32 +881,34 @@
self.load_all_flags()
if result.diffs:
- self.report(f"""
-There is a discrepancy between the hidden API flags created by the
-bootclasspath_fragment and the platform_bootclasspath. See preceding error
-messages to see which flags are inconsistent. The inconsistencies can occur for
-a couple of reasons:
+ self.report_dedent(f"""
+ There is a discrepancy between the hidden API flags created by
+ the bootclasspath_fragment and the platform_bootclasspath. See
+ preceding error messages to see which flags are inconsistent.
+ The inconsistencies can occur for a couple of reasons:
-If you are building against prebuilts of this bootclasspath_fragment then the
-prebuilt version of the sdk snapshot (specifically the hidden API flag files)
-are inconsistent with the prebuilt version of the apex {self.apex}. Please
-ensure that they are both updated from the same build.
+ If you are building against prebuilts of this
+ bootclasspath_fragment then the prebuilt version of the sdk
+ snapshot (specifically the hidden API flag files) are
+ inconsistent with the prebuilt version of the apex {self.apex}.
+ Please ensure that they are both updated from the same build.
-1. There are custom hidden API flags specified in the one of the files in
- frameworks/base/boot/hiddenapi which apply to the bootclasspath_fragment but
- which are not supplied to the bootclasspath_fragment module.
+ 1. There are custom hidden API flags specified in the one of the
+ files in frameworks/base/boot/hiddenapi which apply to the
+ bootclasspath_fragment but which are not supplied to the
+ bootclasspath_fragment module.
-2. The bootclasspath_fragment specifies invalid "package_prefixes" or
- "split_packages" properties that match packages and classes that it does not
- provide.
-
-""")
+ 2. The bootclasspath_fragment specifies invalid
+ "split_packages", "single_packages" and/of "package_prefixes"
+ properties that match packages and classes that it does not
+ provide.
+ """)
# Check to see if there are any hiddenapi related properties that
# need to be added to the
- self.report("""
-Checking custom hidden API flags....
-""")
+ self.report_dedent("""
+ Checking custom hidden API flags....
+ """)
self.check_frameworks_base_boot_hidden_api_files(result)
def report_hidden_api_flag_file_changes(self, result, property_name,
@@ -1044,13 +1089,16 @@
""").strip("\n")
def analyze_hiddenapi_package_properties(self, result):
- split_packages, single_packages, package_prefixes = \
- self.compute_hiddenapi_package_properties()
+ self.compute_hiddenapi_package_properties(result)
+
+ def indent_lines(lines):
+ return "\n".join([f" {cls}" for cls in lines])
# TODO(b/202154151): Find those classes in split packages that are not
# part of an API, i.e. are an internal implementation class, and so
# can, and should, be safely moved out of the split packages.
+ split_packages = result.split_packages.keys()
result.property_changes.append(
HiddenApiPropertyChange(
property_name="split_packages",
@@ -1060,26 +1108,40 @@
))
if split_packages:
- self.report(f"""
-bootclasspath_fragment {self.bcpf} contains classes in packages that also
-contain classes provided by other sources, those packages are called split
-packages. Split packages should be avoided where possible but are often
-unavoidable when modularizing existing code.
+ self.report_dedent(f"""
+ bootclasspath_fragment {self.bcpf} contains classes in packages
+ that also contain classes provided by other bootclasspath
+ modules. Those packages are called split packages. Split
+ packages should be avoided where possible but are often
+ unavoidable when modularizing existing code.
-The hidden api processing needs to know which packages are split (and conversely
-which are not) so that it can optimize the hidden API flags to remove
-unnecessary implementation details.
+ The hidden api processing needs to know which packages are split
+ (and conversely which are not) so that it can optimize the
+ hidden API flags to remove unnecessary implementation details.
+
+ By default (for backwards compatibility) the
+ bootclasspath_fragment assumes that all packages are split
+ unless one of the package_prefixes or split_packages properties
+ are specified. While that is safe it is not optimal and can lead
+ to unnecessary implementation details leaking into the hidden
+ API flags. Adding an empty split_packages property allows the
+ flags to be optimized and remove any unnecessary implementation
+ details.
+ """)
+
+ for package in split_packages:
+ reason = result.split_packages[package]
+ self.report(f"""
+ Package {package} is split because while this bootclasspath_fragment
+ provides the following classes:
+{indent_lines(reason.bcpf)}
+
+ Other module(s) on the bootclasspath provides the following classes in
+ that package:
+{indent_lines(reason.other)}
""")
- self.report("""
-By default (for backwards compatibility) the bootclasspath_fragment assumes that
-all packages are split unless one of the package_prefixes or split_packages
-properties are specified. While that is safe it is not optimal and can lead to
-unnecessary implementation details leaking into the hidden API flags. Adding an
-empty split_packages property allows the flags to be optimized and remove any
-unnecessary implementation details.
-""")
-
+ single_packages = result.single_packages.keys()
if single_packages:
result.property_changes.append(
HiddenApiPropertyChange(
@@ -1091,10 +1153,34 @@
contain classes from other bootclasspath modules. Packages
should only be listed here when necessary for legacy
purposes, new packages should match a package prefix.
- """),
+ """),
action=PropertyChangeAction.REPLACE,
))
+ self.report_dedent(f"""
+ bootclasspath_fragment {self.bcpf} contains classes from
+ packages that has sub-packages which contain classes provided by
+ other bootclasspath modules. Those packages are called single
+ packages. Single packages should be avoided where possible but
+ are often unavoidable when modularizing existing code.
+
+ Because some sub-packages contains classes from other
+ bootclasspath modules it is not possible to use the package as a
+ package prefix as that treats the package and all its
+ sub-packages as being provided by this module.
+ """)
+ for package in single_packages:
+ reason = result.single_packages[package]
+ self.report(f"""
+ Package {package} is not a package prefix because while this
+ bootclasspath_fragment provides the following sub-packages:
+{indent_lines(reason.bcpf)}
+
+ Other module(s) on the bootclasspath provide the following sub-packages:
+{indent_lines(reason.other)}
+""")
+
+ package_prefixes = result.package_prefixes
if package_prefixes:
result.property_changes.append(
HiddenApiPropertyChange(
@@ -1111,38 +1197,40 @@
signature_patterns_files = signature_patterns_files.removeprefix(
self.top_dir)
- self.report(f"""
-The purpose of the hiddenapi split_packages and package_prefixes properties is
-to allow the removal of implementation details from the hidden API flags to
-reduce the coupling between sdk snapshots and the APEX runtime. It cannot
-eliminate that coupling completely though. Doing so may require changes to the
-code.
+ self.report_dedent(f"""
+ The purpose of the hiddenapi split_packages and package_prefixes
+ properties is to allow the removal of implementation details
+ from the hidden API flags to reduce the coupling between sdk
+ snapshots and the APEX runtime. It cannot eliminate that
+ coupling completely though. Doing so may require changes to the
+ code.
-This tool provides support for managing those properties but it cannot decide
-whether the set of package prefixes suggested is appropriate that needs the
-input of the developer.
+ This tool provides support for managing those properties but it
+ cannot decide whether the set of package prefixes suggested is
+ appropriate that needs the input of the developer.
-Please run the following command:
- m {signature_patterns_files}
+ Please run the following command:
+ m {signature_patterns_files}
-And then check the '{signature_patterns_files}' for any mention of
-implementation classes and packages (i.e. those classes/packages that do not
-contain any part of an API surface, including the hidden API). If they are
-found then the code should ideally be moved to a package unique to this module
-that is contained within a package that is part of an API surface.
+ And then check the '{signature_patterns_files}' for any mention
+ of implementation classes and packages (i.e. those
+ classes/packages that do not contain any part of an API surface,
+ including the hidden API). If they are found then the code
+ should ideally be moved to a package unique to this module that
+ is contained within a package that is part of an API surface.
-The format of the file is a list of patterns:
+ The format of the file is a list of patterns:
-* Patterns for split packages will list every class in that package.
+ * Patterns for split packages will list every class in that package.
-* Patterns for package prefixes will end with .../**.
+ * Patterns for package prefixes will end with .../**.
-* Patterns for packages which are not split but cannot use a package prefix
-because there are sub-packages which are provided by another module will end
-with .../*.
-""")
+ * Patterns for packages which are not split but cannot use a
+ package prefix because there are sub-packages which are provided
+ by another module will end with .../*.
+ """)
- def compute_hiddenapi_package_properties(self):
+ def compute_hiddenapi_package_properties(self, result):
trie = signature_trie()
# Populate the trie with the classes that are provided by the
# bootclasspath_fragment tagging them to make it clear where they
@@ -1151,6 +1239,7 @@
for class_name in sorted_classes:
trie.add(class_name + _FAKE_MEMBER, ClassProvider.BCPF)
+ # Now the same for monolithic classes.
monolithic_classes = set()
abs_flags_file = os.path.join(self.top_dir, _FLAGS_FILE)
with open(abs_flags_file, "r", encoding="utf8") as f:
@@ -1165,15 +1254,54 @@
only_if_matches=True)
monolithic_classes.add(class_name)
- split_packages = []
- single_packages = []
- package_prefixes = []
- self.recurse_hiddenapi_packages_trie(trie, split_packages,
- single_packages, package_prefixes)
- return split_packages, single_packages, package_prefixes
+ self.recurse_hiddenapi_packages_trie(trie, result)
- def recurse_hiddenapi_packages_trie(self, node, split_packages,
- single_packages, package_prefixes):
+ @staticmethod
+ def selector_to_java_reference(node):
+ return node.selector.replace("/", ".")
+
+ @staticmethod
+ def determine_reason_for_single_package(node):
+ bcpf_packages = []
+ other_packages = []
+
+ def recurse(n):
+ if n.type != "package":
+ return
+
+ providers = n.get_matching_rows("*")
+ package_ref = BcpfAnalyzer.selector_to_java_reference(n)
+ if ClassProvider.BCPF in providers:
+ bcpf_packages.append(package_ref)
+ else:
+ other_packages.append(package_ref)
+
+ children = n.child_nodes()
+ if children:
+ for child in children:
+ recurse(child)
+
+ recurse(node)
+ return PackagePropertyReason(bcpf=bcpf_packages, other=other_packages)
+
+ @staticmethod
+ def determine_reason_for_split_package(node):
+ bcpf_classes = []
+ other_classes = []
+ for child in node.child_nodes():
+ if child.type != "class":
+ continue
+
+ providers = child.values(lambda _: True)
+ class_ref = BcpfAnalyzer.selector_to_java_reference(child)
+ if ClassProvider.BCPF in providers:
+ bcpf_classes.append(class_ref)
+ else:
+ other_classes.append(class_ref)
+
+ return PackagePropertyReason(bcpf=bcpf_classes, other=other_classes)
+
+ def recurse_hiddenapi_packages_trie(self, node, result):
nodes = node.child_nodes()
if nodes:
for child in nodes:
@@ -1181,7 +1309,7 @@
if child.type != "package":
continue
- package = child.selector.replace("/", ".")
+ package = self.selector_to_java_reference(child)
providers = set(child.get_matching_rows("**"))
if not providers:
@@ -1192,7 +1320,7 @@
# The package and all its sub packages only contain
# classes provided by the bootclasspath_fragment.
logging.debug("Package '%s.**' is not split", package)
- package_prefixes.append(package)
+ result.package_prefixes.append(package)
# There is no point traversing into the sub packages.
continue
elif providers == {ClassProvider.OTHER}:
@@ -1217,8 +1345,17 @@
elif providers == {ClassProvider.BCPF}:
# The package only contains classes provided by the
# bootclasspath_fragment.
- logging.debug("Package '%s.*' is not split", package)
- single_packages.append(package)
+ logging.debug(
+ "Package '%s.*' is not split but does have "
+ "sub-packages from other modules", package)
+
+ # Partition the sub-packages into those that are provided by
+ # this bootclasspath_fragment and those provided by other
+ # modules. They can be used to explain the reason for the
+ # single package to developers.
+ reason = self.determine_reason_for_single_package(child)
+ result.single_packages[package] = reason
+
elif providers == {ClassProvider.OTHER}:
# The package contains no classes provided by the
# bootclasspath_fragment. Child nodes make contain such
@@ -1229,11 +1366,15 @@
# The package contains classes provided by both the
# bootclasspath_fragment and some other source.
logging.debug("Package '%s.*' is split", package)
- split_packages.append(package)
- self.recurse_hiddenapi_packages_trie(child, split_packages,
- single_packages,
- package_prefixes)
+ # Partition the classes in this split package into those
+ # that come from this bootclasspath_fragment and those that
+ # come from other modules. That can be used to explain the
+ # reason for the split package to developers.
+ reason = self.determine_reason_for_split_package(child)
+ result.split_packages[package] = reason
+
+ self.recurse_hiddenapi_packages_trie(child, result)
def newline_stripping_iter(iterator):
diff --git a/scripts/hiddenapi/analyze_bcpf_test.py b/scripts/hiddenapi/analyze_bcpf_test.py
index 650dd54..a32ffd0 100644
--- a/scripts/hiddenapi/analyze_bcpf_test.py
+++ b/scripts/hiddenapi/analyze_bcpf_test.py
@@ -377,6 +377,7 @@
La/b/c/D;->m()V
La/b/c/E;->m()V
La/b/c/d/E;->m()V
+La/b/c/d/e/F;->m()V
Lb/c/D;->m()V
Lb/c/E;->m()V
Lb/c/d/E;->m()V
@@ -385,11 +386,21 @@
analyzer = self.create_analyzer_for_test(fs)
analyzer.load_all_flags()
- split_packages, single_packages, package_prefixes = \
- analyzer.compute_hiddenapi_package_properties()
- self.assertEqual(["a.b"], split_packages)
- self.assertEqual(["a.b.c"], single_packages)
- self.assertEqual(["b"], package_prefixes)
+ result = ab.Result()
+ analyzer.compute_hiddenapi_package_properties(result)
+ self.assertEqual(["a.b"], list(result.split_packages.keys()))
+
+ reason = result.split_packages["a.b"]
+ self.assertEqual(["a.b.C"], reason.bcpf)
+ self.assertEqual(["a.b.D", "a.b.E"], reason.other)
+
+ self.assertEqual(["a.b.c"], list(result.single_packages.keys()))
+
+ reason = result.single_packages["a.b.c"]
+ self.assertEqual(["a.b.c"], reason.bcpf)
+ self.assertEqual(["a.b.c.d", "a.b.c.d.e"], reason.other)
+
+ self.assertEqual(["b"], result.package_prefixes)
class TestHiddenApiPropertyChange(unittest.TestCase):
diff --git a/scripts/hiddenapi/signature_trie.py b/scripts/hiddenapi/signature_trie.py
index e813a97..3650fa1 100644
--- a/scripts/hiddenapi/signature_trie.py
+++ b/scripts/hiddenapi/signature_trie.py
@@ -45,7 +45,9 @@
:return: A list of iterables of all the values associated with
this node and its children.
"""
- raise NotImplementedError("Please Implement this method")
+ values = []
+ self.append_values(values, selector)
+ return values
def append_values(self, values, selector):
"""Append the values associated with this node and its children.
@@ -313,12 +315,8 @@
node = node.nodes[element]
else:
return []
- return chain.from_iterable(node.values(selector))
- def values(self, selector):
- values = []
- self.append_values(values, selector)
- return values
+ return node.values(selector)
def append_values(self, values, selector):
for key, node in self.nodes.items():
@@ -336,11 +334,8 @@
# The value associated with this leaf.
value: typing.Any
- def values(self, selector):
- return [[self.value]]
-
def append_values(self, values, selector):
- values.append([self.value])
+ values.append(self.value)
def child_nodes(self):
return []
diff --git a/scripts/hiddenapi/signature_trie_test.py b/scripts/hiddenapi/signature_trie_test.py
index 1295691..6d4e660 100755
--- a/scripts/hiddenapi/signature_trie_test.py
+++ b/scripts/hiddenapi/signature_trie_test.py
@@ -150,6 +150,27 @@
str(context.exception))
+class TestValues(unittest.TestCase):
+ def test_add_then_get(self):
+ trie = signature_trie()
+ trie.add("La/b/C;->l()", 1)
+ trie.add("La/b/C$D;->m()", "A")
+ trie.add("La/b/C$D;->n()", {})
+
+ package_a_node = next(iter(trie.child_nodes()))
+ self.assertEqual("package", package_a_node.type)
+ self.assertEqual("a", package_a_node.selector)
+
+ package_b_node = next(iter(package_a_node.child_nodes()))
+ self.assertEqual("package", package_b_node.type)
+ self.assertEqual("a/b", package_b_node.selector)
+
+ class_c_node = next(iter(package_b_node.child_nodes()))
+ self.assertEqual("class", class_c_node.type)
+ self.assertEqual("a/b/C", class_c_node.selector)
+
+ self.assertEqual([1, "A", {}], class_c_node.values(lambda _: True))
+
class TestGetMatchingRows(unittest.TestCase):
extractInput = """
Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;