Merge "Add aconfig storage API deps to codegened lib" into main
diff --git a/.gitignore b/.gitignore
index 5d2bc0d..781dd32 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,4 @@
 *.iml
 *.ipr
 *.iws
-
+/.vscode
diff --git a/android/Android.bp b/android/Android.bp
index 4c59592..f130d3a 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -138,6 +138,7 @@
         "selects_test.go",
         "singleton_module_test.go",
         "soong_config_modules_test.go",
+        "test_suites_test.go",
         "util_test.go",
         "variable_test.go",
         "visibility_test.go",
diff --git a/android/test_suites.go b/android/test_suites.go
index adcc15a..ff75f26 100644
--- a/android/test_suites.go
+++ b/android/test_suites.go
@@ -14,6 +14,11 @@
 
 package android
 
+import (
+	"path/filepath"
+	"strings"
+)
+
 func init() {
 	RegisterParallelSingletonType("testsuites", testSuiteFilesFactory)
 }
@@ -23,8 +28,8 @@
 }
 
 type testSuiteFiles struct {
-	robolectric WritablePath
-	ravenwood   WritablePath
+	robolectric []Path
+	ravenwood   []Path
 }
 
 type TestSuiteModule interface {
@@ -48,53 +53,107 @@
 	})
 
 	t.robolectric = robolectricTestSuite(ctx, files["robolectric-tests"])
-	ctx.Phony("robolectric-tests", t.robolectric)
+	ctx.Phony("robolectric-tests", t.robolectric...)
 
 	t.ravenwood = ravenwoodTestSuite(ctx, files["ravenwood-tests"])
-	ctx.Phony("ravenwood-tests", t.ravenwood)
+	ctx.Phony("ravenwood-tests", t.ravenwood...)
 }
 
 func (t *testSuiteFiles) MakeVars(ctx MakeVarsContext) {
-	ctx.DistForGoal("robolectric-tests", t.robolectric)
-	ctx.DistForGoal("ravenwood-tests", t.ravenwood)
+	ctx.DistForGoal("robolectric-tests", t.robolectric...)
+	ctx.DistForGoal("ravenwood-tests", t.ravenwood...)
 }
 
-func robolectricTestSuite(ctx SingletonContext, files map[string]InstallPaths) WritablePath {
+func robolectricTestSuite(ctx SingletonContext, files map[string]InstallPaths) []Path {
 	var installedPaths InstallPaths
 	for _, module := range SortedKeys(files) {
 		installedPaths = append(installedPaths, files[module]...)
 	}
-	testCasesDir := pathForInstall(ctx, ctx.Config().BuildOS, X86, "testcases")
 
-	outputFile := PathForOutput(ctx, "packaging", "robolectric-tests.zip")
+	outputFile := pathForPackaging(ctx, "robolectric-tests.zip")
 	rule := NewRuleBuilder(pctx, ctx)
 	rule.Command().BuiltTool("soong_zip").
 		FlagWithOutput("-o ", outputFile).
 		FlagWithArg("-P ", "host/testcases").
-		FlagWithArg("-C ", testCasesDir.String()).
+		FlagWithArg("-C ", pathForTestCases(ctx).String()).
 		FlagWithRspFileInputList("-r ", outputFile.ReplaceExtension(ctx, "rsp"), installedPaths.Paths()).
+		Flag("-sha256") // necessary to save cas_uploader's time
+
+	testList := buildTestList(ctx, "robolectric-tests_list", installedPaths)
+	testListZipOutputFile := pathForPackaging(ctx, "robolectric-tests_list.zip")
+
+	rule.Command().BuiltTool("soong_zip").
+		FlagWithOutput("-o ", testListZipOutputFile).
+		FlagWithArg("-C ", pathForPackaging(ctx).String()).
+		FlagWithInput("-f ", testList).
 		Flag("-sha256")
+
 	rule.Build("robolectric_tests_zip", "robolectric-tests.zip")
 
-	return outputFile
+	return []Path{outputFile, testListZipOutputFile}
 }
 
-func ravenwoodTestSuite(ctx SingletonContext, files map[string]InstallPaths) WritablePath {
+func ravenwoodTestSuite(ctx SingletonContext, files map[string]InstallPaths) []Path {
 	var installedPaths InstallPaths
 	for _, module := range SortedKeys(files) {
 		installedPaths = append(installedPaths, files[module]...)
 	}
-	testCasesDir := pathForInstall(ctx, ctx.Config().BuildOS, X86, "testcases")
 
-	outputFile := PathForOutput(ctx, "packaging", "ravenwood-tests.zip")
+	outputFile := pathForPackaging(ctx, "ravenwood-tests.zip")
 	rule := NewRuleBuilder(pctx, ctx)
 	rule.Command().BuiltTool("soong_zip").
 		FlagWithOutput("-o ", outputFile).
 		FlagWithArg("-P ", "host/testcases").
-		FlagWithArg("-C ", testCasesDir.String()).
+		FlagWithArg("-C ", pathForTestCases(ctx).String()).
 		FlagWithRspFileInputList("-r ", outputFile.ReplaceExtension(ctx, "rsp"), installedPaths.Paths()).
+		Flag("-sha256") // necessary to save cas_uploader's time
+
+	testList := buildTestList(ctx, "ravenwood-tests_list", installedPaths)
+	testListZipOutputFile := pathForPackaging(ctx, "ravenwood-tests_list.zip")
+
+	rule.Command().BuiltTool("soong_zip").
+		FlagWithOutput("-o ", testListZipOutputFile).
+		FlagWithArg("-C ", pathForPackaging(ctx).String()).
+		FlagWithInput("-f ", testList).
 		Flag("-sha256")
+
 	rule.Build("ravenwood_tests_zip", "ravenwood-tests.zip")
 
+	return []Path{outputFile, testListZipOutputFile}
+}
+
+func buildTestList(ctx SingletonContext, listFile string, installedPaths InstallPaths) Path {
+	buf := &strings.Builder{}
+	for _, p := range installedPaths {
+		if p.Ext() != ".config" {
+			continue
+		}
+		pc, err := toTestListPath(p.String(), pathForTestCases(ctx).String(), "host/testcases")
+		if err != nil {
+			ctx.Errorf("Failed to convert path: %s, %v", p.String(), err)
+			continue
+		}
+		buf.WriteString(pc)
+		buf.WriteString("\n")
+	}
+	outputFile := pathForPackaging(ctx, listFile)
+	WriteFileRuleVerbatim(ctx, outputFile, buf.String())
 	return outputFile
 }
+
+func toTestListPath(path, relativeRoot, prefix string) (string, error) {
+	dest, err := filepath.Rel(relativeRoot, path)
+	if err != nil {
+		return "", err
+	}
+	return filepath.Join(prefix, dest), nil
+}
+
+func pathForPackaging(ctx PathContext, pathComponents ...string) OutputPath {
+	pathComponents = append([]string{"packaging"}, pathComponents...)
+	return PathForOutput(ctx, pathComponents...)
+}
+
+func pathForTestCases(ctx PathContext) InstallPath {
+	return pathForInstall(ctx, ctx.Config().BuildOS, X86, "testcases")
+}
diff --git a/android/test_suites_test.go b/android/test_suites_test.go
new file mode 100644
index 0000000..db9a34d
--- /dev/null
+++ b/android/test_suites_test.go
@@ -0,0 +1,117 @@
+// Copyright 2024 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"path/filepath"
+	"testing"
+)
+
+func TestBuildTestList(t *testing.T) {
+	t.Parallel()
+	ctx := GroupFixturePreparers(
+		prepareForFakeTestSuite,
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.RegisterParallelSingletonType("testsuites", testSuiteFilesFactory)
+		}),
+	).RunTestWithBp(t, `
+		fake_module {
+			name: "module1",
+			outputs: [
+				"Test1/Test1.config",
+				"Test1/Test1.apk",
+			],
+			test_suites: ["ravenwood-tests"],
+		}
+		fake_module {
+			name: "module2",
+			outputs: [
+				"Test2/Test21/Test21.config",
+				"Test2/Test21/Test21.apk",
+			],
+			test_suites: ["ravenwood-tests", "robolectric-tests"],
+		}
+		fake_module {
+			name: "module_without_config",
+			outputs: [
+				"BadTest/BadTest.jar",
+			],
+			test_suites: ["robolectric-tests"],
+		}
+	`)
+
+	config := ctx.SingletonForTests("testsuites")
+	allOutputs := config.AllOutputs()
+
+	wantContents := map[string]string{
+		"robolectric-tests.zip":      "",
+		"robolectric-tests_list.zip": "",
+		"robolectric-tests_list": `host/testcases/Test2/Test21/Test21.config
+`,
+		"ravenwood-tests.zip":      "",
+		"ravenwood-tests_list.zip": "",
+		"ravenwood-tests_list": `host/testcases/Test1/Test1.config
+host/testcases/Test2/Test21/Test21.config
+`,
+	}
+	for _, output := range allOutputs {
+		want, ok := wantContents[filepath.Base(output)]
+		if !ok {
+			t.Errorf("unexpected output: %q", output)
+			continue
+		}
+
+		got := ""
+		if want != "" {
+			got = ContentFromFileRuleForTests(t, ctx.TestContext, config.MaybeOutput(output))
+		}
+
+		if want != got {
+			t.Errorf("want %q, got %q", want, got)
+		}
+	}
+}
+
+type fake_module struct {
+	ModuleBase
+	props struct {
+		Outputs     []string
+		Test_suites []string
+	}
+}
+
+func fakeTestSuiteFactory() Module {
+	module := &fake_module{}
+	base := module.base()
+	module.AddProperties(&base.nameProperties, &module.props)
+	InitAndroidModule(module)
+	return module
+}
+
+var prepareForFakeTestSuite = GroupFixturePreparers(
+	FixtureRegisterWithContext(func(ctx RegistrationContext) {
+		ctx.RegisterModuleType("fake_module", fakeTestSuiteFactory)
+	}),
+)
+
+func (f *fake_module) GenerateAndroidBuildActions(ctx ModuleContext) {
+	for _, output := range f.props.Outputs {
+		ctx.InstallFile(pathForTestCases(ctx), output, nil)
+	}
+}
+
+func (f *fake_module) TestSuites() []string {
+	return f.props.Test_suites
+}
diff --git a/rust/bindgen.go b/rust/bindgen.go
index 11ba74d..eaed1b9 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -101,6 +101,9 @@
 	//
 	// "my_bindgen [flags] wrapper_header.h -o [output_path] -- [clang flags]"
 	Custom_bindgen string
+
+	// flag to indicate if bindgen should handle `static inline` functions (default is false)
+	Handle_static_inline bool
 }
 
 type bindgenDecorator struct {
@@ -232,6 +235,9 @@
 
 	bindgenFlags := defaultBindgenFlags
 	bindgenFlags = append(bindgenFlags, esc(b.Properties.Bindgen_flags)...)
+	if b.Properties.Handle_static_inline {
+		bindgenFlags = append(bindgenFlags, "--experimental --wrap-static-fns")
+	}
 
 	// cat reads from stdin if its command line is empty,
 	// so we pass in /dev/null if there are no other flag files
diff --git a/rust/bindgen_test.go b/rust/bindgen_test.go
index 0c0a6da..11cfe4e 100644
--- a/rust/bindgen_test.go
+++ b/rust/bindgen_test.go
@@ -227,3 +227,22 @@
 	// TODO: The best we can do right now is check $flagfiles. Once bindgen.go switches to RuleBuilder,
 	// we may be able to check libbinder.RuleParams.Command to see if it contains $(cat /dev/null flag_file.txt)
 }
+
+
+func TestBindgenHandleStaticInlining(t *testing.T) {
+	ctx := testRust(t, `
+		rust_bindgen {
+			name: "libbindgen",
+			wrapper_src: "src/any.h",
+			crate_name: "bindgen",
+			stem: "libbindgen",
+			source_stem: "bindings",
+			handle_static_inline: true
+		}
+	`)
+	libbindgen := ctx.ModuleForTests("libbindgen", "android_arm64_armv8-a_source").Output("bindings.rs")
+	// Make sure the flag to support `static inline` functions is present
+	if !strings.Contains(libbindgen.Args["flags"], "--wrap-static-fns") {
+		t.Errorf("missing flag to handle static inlining in rust_bindgen rule: flags %#v", libbindgen.Args["flags"])
+	}
+}