Merge "Allow BoringSSL's Rust bindings to build"
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
index 26cfd57..28dc8b4 100644
--- a/android/allowlists/allowlists.go
+++ b/android/allowlists/allowlists.go
@@ -104,12 +104,14 @@
"external/google-benchmark": Bp2BuildDefaultTrueRecursively,
"external/googletest": Bp2BuildDefaultTrueRecursively,
"external/gwp_asan": Bp2BuildDefaultTrueRecursively,
+ "external/hamcrest": Bp2BuildDefaultTrueRecursively,
"external/icu": Bp2BuildDefaultTrueRecursively,
"external/icu/android_icu4j": Bp2BuildDefaultFalse, // java rules incomplete
"external/icu/icu4j": Bp2BuildDefaultFalse, // java rules incomplete
"external/javapoet": Bp2BuildDefaultTrueRecursively,
"external/jemalloc_new": Bp2BuildDefaultTrueRecursively,
"external/jsoncpp": Bp2BuildDefaultTrueRecursively,
+ "external/junit": Bp2BuildDefaultTrueRecursively,
"external/libcap": Bp2BuildDefaultTrueRecursively,
"external/libcxx": Bp2BuildDefaultTrueRecursively,
"external/libcxxabi": Bp2BuildDefaultTrueRecursively,
@@ -122,6 +124,7 @@
"external/pcre": Bp2BuildDefaultTrueRecursively,
"external/protobuf": Bp2BuildDefaultTrueRecursively,
"external/python/six": Bp2BuildDefaultTrueRecursively,
+ "external/rappor": Bp2BuildDefaultTrueRecursively,
"external/scudo": Bp2BuildDefaultTrueRecursively,
"external/selinux/libselinux": Bp2BuildDefaultTrueRecursively,
"external/selinux/libsepol": Bp2BuildDefaultTrueRecursively,
diff --git a/android/apex.go b/android/apex.go
index b127f74..030fbdc 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -870,10 +870,6 @@
"gemmlowp_headers": 30,
"guava-listenablefuture-prebuilt-jar": 30,
"ike-internals": 30,
- "kotlinx-coroutines-android": 28,
- "kotlinx-coroutines-android-nodeps": 30,
- "kotlinx-coroutines-core": 28,
- "kotlinx-coroutines-core-nodeps": 30,
"libbrotli": 30,
"libcrypto_static": 30,
"libeigen": 30,
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 6b2be69..fa26fc8 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -28,6 +28,7 @@
"android/soong/bazel/cquery"
"android/soong/shared"
+ "github.com/google/blueprint"
"android/soong/bazel"
)
@@ -101,6 +102,9 @@
// Returns build statements which should get registered to reflect Bazel's outputs.
BuildStatementsToRegister() []bazel.BuildStatement
+
+ // Returns the depsets defined in Bazel's aquery response.
+ AqueryDepsets() []bazel.AqueryDepset
}
type bazelRunner interface {
@@ -128,6 +132,9 @@
// Build statements which should get registered to reflect Bazel's outputs.
buildStatements []bazel.BuildStatement
+
+ // Depsets which should be used for Bazel's build statements.
+ depsets []bazel.AqueryDepset
}
var _ BazelContext = &bazelContext{}
@@ -175,6 +182,10 @@
return []bazel.BuildStatement{}
}
+func (m MockBazelContext) AqueryDepsets() []bazel.AqueryDepset {
+ return []bazel.AqueryDepset{}
+}
+
var _ BazelContext = MockBazelContext{}
func (bazelCtx *bazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, bool) {
@@ -236,6 +247,10 @@
return []bazel.BuildStatement{}
}
+func (m noopBazelContext) AqueryDepsets() []bazel.AqueryDepset {
+ return []bazel.AqueryDepset{}
+}
+
func NewBazelContext(c *config) (BazelContext, error) {
// TODO(cparsons): Assess USE_BAZEL=1 instead once "mixed Soong/Bazel builds"
// are production ready.
@@ -746,7 +761,7 @@
return err
}
- context.buildStatements, err = bazel.AqueryBuildStatements([]byte(aqueryOutput))
+ context.buildStatements, context.depsets, err = bazel.AqueryBuildStatements([]byte(aqueryOutput))
if err != nil {
return err
}
@@ -772,6 +787,10 @@
return context.buildStatements
}
+func (context *bazelContext) AqueryDepsets() []bazel.AqueryDepset {
+ return context.depsets
+}
+
func (context *bazelContext) OutputBase() string {
return context.paths.outputBase
}
@@ -804,6 +823,23 @@
ctx.AddNinjaFileDeps(file)
}
+ for _, depset := range ctx.Config().BazelContext.AqueryDepsets() {
+ var outputs []Path
+ for _, depsetDepId := range depset.TransitiveDepSetIds {
+ otherDepsetName := bazelDepsetName(depsetDepId)
+ outputs = append(outputs, PathForPhony(ctx, otherDepsetName))
+ }
+ for _, artifactPath := range depset.DirectArtifacts {
+ outputs = append(outputs, PathForBazelOut(ctx, artifactPath))
+ }
+ thisDepsetName := bazelDepsetName(depset.Id)
+ ctx.Build(pctx, BuildParams{
+ Rule: blueprint.Phony,
+ Outputs: []WritablePath{PathForPhony(ctx, thisDepsetName)},
+ Implicits: outputs,
+ })
+ }
+
// Register bazel-owned build statements (obtained from the aquery invocation).
for index, buildStatement := range ctx.Config().BazelContext.BuildStatementsToRegister() {
if len(buildStatement.Command) < 1 {
@@ -838,6 +874,10 @@
for _, inputPath := range buildStatement.InputPaths {
cmd.Implicit(PathForBazelOut(ctx, inputPath))
}
+ for _, inputDepsetId := range buildStatement.InputDepsetIds {
+ otherDepsetName := bazelDepsetName(inputDepsetId)
+ cmd.Implicit(PathForPhony(ctx, otherDepsetName))
+ }
if depfile := buildStatement.Depfile; depfile != nil {
cmd.ImplicitDepFile(PathForBazelOut(ctx, *depfile))
@@ -883,3 +923,7 @@
osType: ctx.Os(),
}
}
+
+func bazelDepsetName(depsetId int) string {
+ return fmt.Sprintf("bazel_depset_%d", depsetId)
+}
diff --git a/android/config.go b/android/config.go
index 250c312..ee058e8 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1486,6 +1486,10 @@
return c.productVariables.MissingUsesLibraries
}
+func (c *config) TargetMultitreeUpdateMeta() bool {
+ return c.productVariables.MultitreeUpdateMeta
+}
+
func (c *deviceConfig) DeviceArch() string {
return String(c.config.productVariables.DeviceArch)
}
diff --git a/android/paths.go b/android/paths.go
index e7829b9..e0e5ae5 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -1057,7 +1057,8 @@
}
// absolute path already checked by validateSafePath
- if strings.HasPrefix(ret.String(), ctx.Config().soongOutDir) {
+ // special-case api surface gen files for now
+ if strings.HasPrefix(ret.String(), ctx.Config().soongOutDir) && !strings.Contains(ret.String(), ctx.Config().soongOutDir+"/.export") {
return ret, fmt.Errorf("source path %q is in output", ret.String())
}
@@ -1073,7 +1074,8 @@
}
// absolute path already checked by validatePath
- if strings.HasPrefix(ret.String(), ctx.Config().soongOutDir) {
+ // special-case for now
+ if strings.HasPrefix(ret.String(), ctx.Config().soongOutDir) && !strings.Contains(ret.String(), ctx.Config().soongOutDir+"/.export") {
return ret, fmt.Errorf("source path %q is in output", ret.String())
}
diff --git a/android/variable.go b/android/variable.go
index 373891a..90cb6ff 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -352,6 +352,8 @@
RecoverySnapshotDirsIncluded []string `json:",omitempty"`
HostFakeSnapshotEnabled bool `json:",omitempty"`
+ MultitreeUpdateMeta bool `json:",omitempty"`
+
BoardVendorSepolicyDirs []string `json:",omitempty"`
BoardOdmSepolicyDirs []string `json:",omitempty"`
BoardReqdMaskPolicy []string `json:",omitempty"`
diff --git a/androidmk/parser/make_strings.go b/androidmk/parser/make_strings.go
index 8030326..416e430 100644
--- a/androidmk/parser/make_strings.go
+++ b/androidmk/parser/make_strings.go
@@ -279,7 +279,7 @@
func (ms *MakeString) EndsWith(ch rune) bool {
s := ms.Strings[len(ms.Strings)-1]
- return s[len(s)-1] == uint8(ch)
+ return len(s) > 0 && s[len(s)-1] == uint8(ch)
}
func (ms *MakeString) ReplaceLiteral(input string, output string) {
diff --git a/androidmk/parser/make_strings_test.go b/androidmk/parser/make_strings_test.go
index fbb289b..e243ece 100644
--- a/androidmk/parser/make_strings_test.go
+++ b/androidmk/parser/make_strings_test.go
@@ -217,6 +217,36 @@
}
}
+var endsWithTestCases = []struct {
+ in *MakeString
+ endsWith rune
+ expected bool
+}{
+ {
+ in: genMakeString("foo", "X", "bar ="),
+ endsWith: '=',
+ expected: true,
+ },
+ {
+ in: genMakeString("foo", "X", "bar ="),
+ endsWith: ':',
+ expected: false,
+ },
+ {
+ in: genMakeString("foo", "X", ""),
+ endsWith: '=',
+ expected: false,
+ },
+}
+
+func TestMakeStringEndsWith(t *testing.T) {
+ for _, test := range endsWithTestCases {
+ if test.in.EndsWith(test.endsWith) != test.expected {
+ t.Errorf("with:\n%q\nexpected:\n%t\ngot:\n%t", test.in.Dump(), test.expected, !test.expected)
+ }
+ }
+}
+
func dumpArray(a []*MakeString) string {
ret := make([]string, len(a))
diff --git a/apex/Android.bp b/apex/Android.bp
index 41224ec..d3417c2 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -14,6 +14,7 @@
"soong-cc",
"soong-filesystem",
"soong-java",
+ "soong-multitree",
"soong-provenance",
"soong-python",
"soong-rust",
diff --git a/apex/apex.go b/apex/apex.go
index 76af1b8..f16b72d 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -33,6 +33,7 @@
prebuilt_etc "android/soong/etc"
"android/soong/filesystem"
"android/soong/java"
+ "android/soong/multitree"
"android/soong/python"
"android/soong/rust"
"android/soong/sh"
@@ -358,6 +359,7 @@
android.OverridableModuleBase
android.SdkBase
android.BazelModuleBase
+ multitree.ExportableModuleBase
// Properties
properties apexBundleProperties
@@ -1359,6 +1361,21 @@
}
}
+var _ multitree.Exportable = (*apexBundle)(nil)
+
+func (a *apexBundle) Exportable() bool {
+ if a.properties.ApexType == flattenedApex {
+ return false
+ }
+ return true
+}
+
+func (a *apexBundle) TaggedOutputs() map[string]android.Paths {
+ ret := make(map[string]android.Paths)
+ ret["apex"] = android.Paths{a.outputFile}
+ return ret
+}
+
var _ cc.Coverage = (*apexBundle)(nil)
// Implements cc.Coverage
@@ -2372,6 +2389,7 @@
android.InitSdkAwareModule(module)
android.InitOverridableModule(module, &module.overridableProperties.Overrides)
android.InitBazelModule(module)
+ multitree.InitExportableModule(module)
return module
}
diff --git a/bazel/aquery.go b/bazel/aquery.go
index fd8cf67..e05cbd6 100644
--- a/bazel/aquery.go
+++ b/bazel/aquery.go
@@ -43,6 +43,18 @@
Value string
}
+// AqueryDepset is a depset definition from Bazel's aquery response. This is
+// akin to the `depSetOfFiles` in the response proto, except that direct
+// artifacts are enumerated by full path instead of by ID.
+// A depset is a data structure for efficient transitive handling of artifact
+// paths. A single depset consists of one or more artifact paths and one or
+// more "child" depsets.
+type AqueryDepset struct {
+ Id int
+ DirectArtifacts []string
+ TransitiveDepSetIds []int
+}
+
// depSetOfFiles contains relevant portions of Bazel's aquery proto, DepSetOfFiles.
// Represents a data structure containing one or more files. Depsets in Bazel are an efficient
// data structure for storing large numbers of file paths.
@@ -79,21 +91,21 @@
Command string
Depfile *string
OutputPaths []string
- InputPaths []string
SymlinkPaths []string
Env []KeyValuePair
Mnemonic string
+
+ // Inputs of this build statement, either as unexpanded depsets or expanded
+ // input paths. There should be no overlap between these fields; an input
+ // path should either be included as part of an unexpanded depset or a raw
+ // input path string, but not both.
+ InputDepsetIds []int
+ InputPaths []string
}
// A helper type for aquery processing which facilitates retrieval of path IDs from their
// less readable Bazel structures (depset and path fragment).
type aqueryArtifactHandler struct {
- // Maps middleman artifact Id to input artifact depset ID.
- // Middleman artifacts are treated as "substitute" artifacts for mixed builds. For example,
- // if we find a middleman action which has outputs [foo, bar], and output [baz_middleman], then,
- // for each other action which has input [baz_middleman], we add [foo, bar] to the inputs for
- // that action instead.
- middlemanIdToDepsetIds map[int][]int
// Maps depset Id to depset struct.
depsetIdToDepset map[int]depSetOfFiles
// depsetIdToArtifactIdsCache is a memoization of depset flattening, because flattening
@@ -132,12 +144,11 @@
artifactIdToPath[artifact.Id] = artifactPath
}
- depsetIdToDepset := map[int]depSetOfFiles{}
- for _, depset := range aqueryResult.DepSetOfFiles {
- depsetIdToDepset[depset.Id] = depset
- }
-
- // Do a pass through all actions to identify which artifacts are middleman artifacts.
+ // Map middleman artifact Id to input artifact depset ID.
+ // Middleman artifacts are treated as "substitute" artifacts for mixed builds. For example,
+ // if we find a middleman action which has outputs [foo, bar], and output [baz_middleman], then,
+ // for each other action which has input [baz_middleman], we add [foo, bar] to the inputs for
+ // that action instead.
middlemanIdToDepsetIds := map[int][]int{}
for _, actionEntry := range aqueryResult.Actions {
if actionEntry.Mnemonic == "Middleman" {
@@ -146,14 +157,64 @@
}
}
}
+
+ // Store all depset IDs to validate all depset links are resolvable.
+ depsetIds := map[int]bool{}
+ for _, depset := range aqueryResult.DepSetOfFiles {
+ depsetIds[depset.Id] = true
+ }
+
+ depsetIdToDepset := map[int]depSetOfFiles{}
+ // Validate and adjust aqueryResult.DepSetOfFiles values.
+ for _, depset := range aqueryResult.DepSetOfFiles {
+ filteredArtifactIds := []int{}
+ for _, artifactId := range depset.DirectArtifactIds {
+ path, pathExists := artifactIdToPath[artifactId]
+ if !pathExists {
+ return nil, fmt.Errorf("undefined input artifactId %d", artifactId)
+ }
+ // Filter out any inputs which are universally dropped, and swap middleman
+ // artifacts with their corresponding depsets.
+ if depsetsToUse, isMiddleman := middlemanIdToDepsetIds[artifactId]; isMiddleman {
+ // Swap middleman artifacts with their corresponding depsets and drop the middleman artifacts.
+ depset.TransitiveDepSetIds = append(depset.TransitiveDepSetIds, depsetsToUse...)
+ } else if strings.HasSuffix(path, py3wrapperFileName) || manifestFilePattern.MatchString(path) {
+ // Drop these artifacts.
+ // See go/python-binary-host-mixed-build for more details.
+ // 1) For py3wrapper.sh, there is no action for creating py3wrapper.sh in the aquery output of
+ // Bazel py_binary targets, so there is no Ninja build statements generated for creating it.
+ // 2) For MANIFEST file, SourceSymlinkManifest action is in aquery output of Bazel py_binary targets,
+ // but it doesn't contain sufficient information so no Ninja build statements are generated
+ // for creating it.
+ // So in mixed build mode, when these two are used as input of some Ninja build statement,
+ // since there is no build statement to create them, they should be removed from input paths.
+ // TODO(b/197135294): Clean up this custom runfiles handling logic when
+ // SourceSymlinkManifest and SymlinkTree actions are supported.
+ } else {
+ // TODO(b/216194240): Filter out bazel tools.
+ filteredArtifactIds = append(filteredArtifactIds, artifactId)
+ }
+ }
+ depset.DirectArtifactIds = filteredArtifactIds
+ for _, childDepsetId := range depset.TransitiveDepSetIds {
+ if _, exists := depsetIds[childDepsetId]; !exists {
+ return nil, fmt.Errorf("undefined input depsetId %d (referenced by depsetId %d)", childDepsetId, depset.Id)
+ }
+ }
+ depsetIdToDepset[depset.Id] = depset
+ }
+
return &aqueryArtifactHandler{
- middlemanIdToDepsetIds: middlemanIdToDepsetIds,
depsetIdToDepset: depsetIdToDepset,
depsetIdToArtifactIdsCache: map[int][]int{},
artifactIdToPath: artifactIdToPath,
}, nil
}
+// getInputPaths flattens the depsets of the given IDs and returns all transitive
+// input paths contained in these depsets.
+// This is a potentially expensive operation, and should not be invoked except
+// for actions which need specialized input handling.
func (a *aqueryArtifactHandler) getInputPaths(depsetIds []int) ([]string, error) {
inputPaths := []string{}
@@ -163,48 +224,15 @@
return nil, err
}
for _, inputId := range inputArtifacts {
- if middlemanInputDepsetIds, isMiddlemanArtifact := a.middlemanIdToDepsetIds[inputId]; isMiddlemanArtifact {
- // Add all inputs from middleman actions which created middleman artifacts which are
- // in the inputs for this action.
- swappedInputPaths, err := a.getInputPaths(middlemanInputDepsetIds)
- if err != nil {
- return nil, err
- }
- inputPaths = append(inputPaths, swappedInputPaths...)
- } else {
- inputPath, exists := a.artifactIdToPath[inputId]
- if !exists {
- return nil, fmt.Errorf("undefined input artifactId %d", inputId)
- }
- inputPaths = append(inputPaths, inputPath)
+ inputPath, exists := a.artifactIdToPath[inputId]
+ if !exists {
+ return nil, fmt.Errorf("undefined input artifactId %d", inputId)
}
+ inputPaths = append(inputPaths, inputPath)
}
}
- // TODO(b/197135294): Clean up this custom runfiles handling logic when
- // SourceSymlinkManifest and SymlinkTree actions are supported.
- filteredInputPaths := filterOutPy3wrapperAndManifestFileFromInputPaths(inputPaths)
-
- return filteredInputPaths, nil
-}
-
-// See go/python-binary-host-mixed-build for more details.
-// 1) For py3wrapper.sh, there is no action for creating py3wrapper.sh in the aquery output of
-// Bazel py_binary targets, so there is no Ninja build statements generated for creating it.
-// 2) For MANIFEST file, SourceSymlinkManifest action is in aquery output of Bazel py_binary targets,
-// but it doesn't contain sufficient information so no Ninja build statements are generated
-// for creating it.
-// So in mixed build mode, when these two are used as input of some Ninja build statement,
-// since there is no build statement to create them, they should be removed from input paths.
-func filterOutPy3wrapperAndManifestFileFromInputPaths(inputPaths []string) []string {
- filteredInputPaths := []string{}
- for _, path := range inputPaths {
- if strings.HasSuffix(path, py3wrapperFileName) || manifestFilePattern.MatchString(path) {
- continue
- }
- filteredInputPaths = append(filteredInputPaths, path)
- }
- return filteredInputPaths
+ return inputPaths, nil
}
func (a *aqueryArtifactHandler) artifactIdsFromDepsetId(depsetId int) ([]int, error) {
@@ -227,115 +255,233 @@
}
}
-// AqueryBuildStatements returns an array of BuildStatements which should be registered (and output
-// to a ninja file) to correspond one-to-one with the given action graph json proto (from a bazel
-// aquery invocation).
-func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) {
+// AqueryBuildStatements returns a slice of BuildStatements and a slice of AqueryDepset
+// which should be registered (and output to a ninja file) to correspond with Bazel's
+// action graph, as described by the given action graph json proto.
+// BuildStatements are one-to-one with actions in the given action graph, and AqueryDepsets
+// are one-to-one with Bazel's depSetOfFiles objects.
+func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, []AqueryDepset, error) {
buildStatements := []BuildStatement{}
+ depsets := []AqueryDepset{}
var aqueryResult actionGraphContainer
err := json.Unmarshal(aqueryJsonProto, &aqueryResult)
if err != nil {
- return nil, err
+ return nil, nil, err
}
aqueryHandler, err := newAqueryHandler(aqueryResult)
if err != nil {
- return nil, err
+ return nil, nil, err
}
for _, actionEntry := range aqueryResult.Actions {
if shouldSkipAction(actionEntry) {
continue
}
- outputPaths := []string{}
- var depfile *string
- for _, outputId := range actionEntry.OutputIds {
- outputPath, exists := aqueryHandler.artifactIdToPath[outputId]
- if !exists {
- return nil, fmt.Errorf("undefined outputId %d", outputId)
- }
- ext := filepath.Ext(outputPath)
- if ext == ".d" {
- if depfile != nil {
- return nil, fmt.Errorf("found multiple potential depfiles %q, %q", *depfile, outputPath)
- } else {
- depfile = &outputPath
- }
- } else {
- outputPaths = append(outputPaths, outputPath)
- }
- }
- inputPaths, err := aqueryHandler.getInputPaths(actionEntry.InputDepSetIds)
- if err != nil {
- return nil, err
- }
- buildStatement := BuildStatement{
- Command: strings.Join(proptools.ShellEscapeListIncludingSpaces(actionEntry.Arguments), " "),
- Depfile: depfile,
- OutputPaths: outputPaths,
- InputPaths: inputPaths,
- Env: actionEntry.EnvironmentVariables,
- Mnemonic: actionEntry.Mnemonic,
- }
-
+ var buildStatement BuildStatement
if isSymlinkAction(actionEntry) {
- if len(inputPaths) != 1 || len(outputPaths) != 1 {
- return nil, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
- }
- out := outputPaths[0]
- outDir := proptools.ShellEscapeIncludingSpaces(filepath.Dir(out))
- out = proptools.ShellEscapeIncludingSpaces(out)
- in := filepath.Join("$PWD", proptools.ShellEscapeIncludingSpaces(inputPaths[0]))
- // Use absolute paths, because some soong actions don't play well with relative paths (for example, `cp -d`).
- buildStatement.Command = fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -sf %[3]s %[2]s", outDir, out, in)
- buildStatement.SymlinkPaths = outputPaths[:]
+ buildStatement, err = aqueryHandler.symlinkActionBuildStatement(actionEntry)
} else if isTemplateExpandAction(actionEntry) && len(actionEntry.Arguments) < 1 {
- if len(outputPaths) != 1 {
- return nil, fmt.Errorf("Expect 1 output to template expand action, got: output %q", outputPaths)
- }
- expandedTemplateContent := expandTemplateContent(actionEntry)
- // The expandedTemplateContent is escaped for being used in double quotes and shell unescape,
- // and the new line characters (\n) are also changed to \\n which avoids some Ninja escape on \n, which might
- // change \n to space and mess up the format of Python programs.
- // sed is used to convert \\n back to \n before saving to output file.
- // See go/python-binary-host-mixed-build for more details.
- command := fmt.Sprintf(`/bin/bash -c 'echo "%[1]s" | sed "s/\\\\n/\\n/g" > %[2]s && chmod a+x %[2]s'`,
- escapeCommandlineArgument(expandedTemplateContent), outputPaths[0])
- buildStatement.Command = command
+ buildStatement, err = aqueryHandler.templateExpandActionBuildStatement(actionEntry)
} else if isPythonZipperAction(actionEntry) {
- if len(inputPaths) < 1 || len(outputPaths) != 1 {
- return nil, fmt.Errorf("Expect 1+ input and 1 output to python zipper action, got: input %q, output %q", inputPaths, outputPaths)
- }
- buildStatement.InputPaths, buildStatement.Command = removePy3wrapperScript(buildStatement)
- buildStatement.Command = addCommandForPyBinaryRunfilesDir(buildStatement, inputPaths[0], outputPaths[0])
- // Add the python zip file as input of the corresponding python binary stub script in Ninja build statements.
- // In Ninja build statements, the outputs of dependents of a python binary have python binary stub script as input,
- // which is not sufficient without the python zip file from which runfiles directory is created for py_binary.
- //
- // The following logic relies on that Bazel aquery output returns actions in the order that
- // PythonZipper is after TemplateAction of creating Python binary stub script. If later Bazel doesn't return actions
- // in that order, the following logic might not find the build statement generated for Python binary
- // stub script and the build might fail. So the check of pyBinaryFound is added to help debug in case later Bazel might change aquery output.
- // See go/python-binary-host-mixed-build for more details.
- pythonZipFilePath := outputPaths[0]
- pyBinaryFound := false
- for i, _ := range buildStatements {
- if len(buildStatements[i].OutputPaths) == 1 && buildStatements[i].OutputPaths[0]+".zip" == pythonZipFilePath {
- buildStatements[i].InputPaths = append(buildStatements[i].InputPaths, pythonZipFilePath)
- pyBinaryFound = true
- }
- }
- if !pyBinaryFound {
- return nil, fmt.Errorf("Could not find the correspondinging Python binary stub script of PythonZipper: %q", outputPaths)
- }
+ buildStatement, err = aqueryHandler.pythonZipperActionBuildStatement(actionEntry, buildStatements)
} else if len(actionEntry.Arguments) < 1 {
- return nil, fmt.Errorf("received action with no command: [%v]", buildStatement)
+ return nil, nil, fmt.Errorf("received action with no command: [%s]", actionEntry.Mnemonic)
+ } else {
+ buildStatement, err = aqueryHandler.normalActionBuildStatement(actionEntry)
+ }
+
+ if err != nil {
+ return nil, nil, err
}
buildStatements = append(buildStatements, buildStatement)
}
- return buildStatements, nil
+ // Iterate over depset IDs in the initial aquery order to preserve determinism.
+ for _, depset := range aqueryResult.DepSetOfFiles {
+ // Use the depset in the aqueryHandler, as this contains the augmented depsets.
+ depset = aqueryHandler.depsetIdToDepset[depset.Id]
+ directPaths := []string{}
+ for _, artifactId := range depset.DirectArtifactIds {
+ pathString := aqueryHandler.artifactIdToPath[artifactId]
+ directPaths = append(directPaths, pathString)
+ }
+ aqueryDepset := AqueryDepset{
+ Id: depset.Id,
+ DirectArtifacts: directPaths,
+ TransitiveDepSetIds: depset.TransitiveDepSetIds,
+ }
+ depsets = append(depsets, aqueryDepset)
+ }
+ return buildStatements, depsets, nil
+}
+
+func (aqueryHandler *aqueryArtifactHandler) validateInputDepsets(inputDepsetIds []int) ([]int, error) {
+ // Validate input depsets correspond to real depsets.
+ for _, depsetId := range inputDepsetIds {
+ if _, exists := aqueryHandler.depsetIdToDepset[depsetId]; !exists {
+ return nil, fmt.Errorf("undefined input depsetId %d", depsetId)
+ }
+ }
+ return inputDepsetIds, nil
+}
+
+func (aqueryHandler *aqueryArtifactHandler) normalActionBuildStatement(actionEntry action) (BuildStatement, error) {
+ command := strings.Join(proptools.ShellEscapeListIncludingSpaces(actionEntry.Arguments), " ")
+ inputDepsetIds, err := aqueryHandler.validateInputDepsets(actionEntry.InputDepSetIds)
+ if err != nil {
+ return BuildStatement{}, err
+ }
+ outputPaths, depfile, err := aqueryHandler.getOutputPaths(actionEntry)
+ if err != nil {
+ return BuildStatement{}, err
+ }
+
+ buildStatement := BuildStatement{
+ Command: command,
+ Depfile: depfile,
+ OutputPaths: outputPaths,
+ InputDepsetIds: inputDepsetIds,
+ Env: actionEntry.EnvironmentVariables,
+ Mnemonic: actionEntry.Mnemonic,
+ }
+ return buildStatement, nil
+}
+
+func (aqueryHandler *aqueryArtifactHandler) pythonZipperActionBuildStatement(actionEntry action, prevBuildStatements []BuildStatement) (BuildStatement, error) {
+ inputPaths, err := aqueryHandler.getInputPaths(actionEntry.InputDepSetIds)
+ if err != nil {
+ return BuildStatement{}, err
+ }
+ outputPaths, depfile, err := aqueryHandler.getOutputPaths(actionEntry)
+ if err != nil {
+ return BuildStatement{}, err
+ }
+
+ if len(inputPaths) < 1 || len(outputPaths) != 1 {
+ return BuildStatement{}, fmt.Errorf("Expect 1+ input and 1 output to python zipper action, got: input %q, output %q", inputPaths, outputPaths)
+ }
+ command := strings.Join(proptools.ShellEscapeListIncludingSpaces(actionEntry.Arguments), " ")
+ inputPaths, command = removePy3wrapperScript(inputPaths, command)
+ command = addCommandForPyBinaryRunfilesDir(command, inputPaths[0], outputPaths[0])
+ // Add the python zip file as input of the corresponding python binary stub script in Ninja build statements.
+ // In Ninja build statements, the outputs of dependents of a python binary have python binary stub script as input,
+ // which is not sufficient without the python zip file from which runfiles directory is created for py_binary.
+ //
+ // The following logic relies on that Bazel aquery output returns actions in the order that
+ // PythonZipper is after TemplateAction of creating Python binary stub script. If later Bazel doesn't return actions
+ // in that order, the following logic might not find the build statement generated for Python binary
+ // stub script and the build might fail. So the check of pyBinaryFound is added to help debug in case later Bazel might change aquery output.
+ // See go/python-binary-host-mixed-build for more details.
+ pythonZipFilePath := outputPaths[0]
+ pyBinaryFound := false
+ for i, _ := range prevBuildStatements {
+ if len(prevBuildStatements[i].OutputPaths) == 1 && prevBuildStatements[i].OutputPaths[0]+".zip" == pythonZipFilePath {
+ prevBuildStatements[i].InputPaths = append(prevBuildStatements[i].InputPaths, pythonZipFilePath)
+ pyBinaryFound = true
+ }
+ }
+ if !pyBinaryFound {
+ return BuildStatement{}, fmt.Errorf("Could not find the correspondinging Python binary stub script of PythonZipper: %q", outputPaths)
+ }
+
+ buildStatement := BuildStatement{
+ Command: command,
+ Depfile: depfile,
+ OutputPaths: outputPaths,
+ InputPaths: inputPaths,
+ Env: actionEntry.EnvironmentVariables,
+ Mnemonic: actionEntry.Mnemonic,
+ }
+ return buildStatement, nil
+}
+
+func (aqueryHandler *aqueryArtifactHandler) templateExpandActionBuildStatement(actionEntry action) (BuildStatement, error) {
+ outputPaths, depfile, err := aqueryHandler.getOutputPaths(actionEntry)
+ if err != nil {
+ return BuildStatement{}, err
+ }
+ if len(outputPaths) != 1 {
+ return BuildStatement{}, fmt.Errorf("Expect 1 output to template expand action, got: output %q", outputPaths)
+ }
+ expandedTemplateContent := expandTemplateContent(actionEntry)
+ // The expandedTemplateContent is escaped for being used in double quotes and shell unescape,
+ // and the new line characters (\n) are also changed to \\n which avoids some Ninja escape on \n, which might
+ // change \n to space and mess up the format of Python programs.
+ // sed is used to convert \\n back to \n before saving to output file.
+ // See go/python-binary-host-mixed-build for more details.
+ command := fmt.Sprintf(`/bin/bash -c 'echo "%[1]s" | sed "s/\\\\n/\\n/g" > %[2]s && chmod a+x %[2]s'`,
+ escapeCommandlineArgument(expandedTemplateContent), outputPaths[0])
+ inputDepsetIds, err := aqueryHandler.validateInputDepsets(actionEntry.InputDepSetIds)
+ if err != nil {
+ return BuildStatement{}, err
+ }
+
+ buildStatement := BuildStatement{
+ Command: command,
+ Depfile: depfile,
+ OutputPaths: outputPaths,
+ InputDepsetIds: inputDepsetIds,
+ Env: actionEntry.EnvironmentVariables,
+ Mnemonic: actionEntry.Mnemonic,
+ }
+ return buildStatement, nil
+}
+
+func (aqueryHandler *aqueryArtifactHandler) symlinkActionBuildStatement(actionEntry action) (BuildStatement, error) {
+ outputPaths, depfile, err := aqueryHandler.getOutputPaths(actionEntry)
+ if err != nil {
+ return BuildStatement{}, err
+ }
+
+ inputPaths, err := aqueryHandler.getInputPaths(actionEntry.InputDepSetIds)
+ if err != nil {
+ return BuildStatement{}, err
+ }
+ if len(inputPaths) != 1 || len(outputPaths) != 1 {
+ return BuildStatement{}, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
+ }
+ out := outputPaths[0]
+ outDir := proptools.ShellEscapeIncludingSpaces(filepath.Dir(out))
+ out = proptools.ShellEscapeIncludingSpaces(out)
+ in := filepath.Join("$PWD", proptools.ShellEscapeIncludingSpaces(inputPaths[0]))
+ // Use absolute paths, because some soong actions don't play well with relative paths (for example, `cp -d`).
+ command := fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -sf %[3]s %[2]s", outDir, out, in)
+ symlinkPaths := outputPaths[:]
+
+ buildStatement := BuildStatement{
+ Command: command,
+ Depfile: depfile,
+ OutputPaths: outputPaths,
+ InputPaths: inputPaths,
+ Env: actionEntry.EnvironmentVariables,
+ Mnemonic: actionEntry.Mnemonic,
+ SymlinkPaths: symlinkPaths,
+ }
+ return buildStatement, nil
+}
+
+func (aqueryHandler *aqueryArtifactHandler) getOutputPaths(actionEntry action) (outputPaths []string, depfile *string, err error) {
+ for _, outputId := range actionEntry.OutputIds {
+ outputPath, exists := aqueryHandler.artifactIdToPath[outputId]
+ if !exists {
+ err = fmt.Errorf("undefined outputId %d", outputId)
+ return
+ }
+ ext := filepath.Ext(outputPath)
+ if ext == ".d" {
+ if depfile != nil {
+ err = fmt.Errorf("found multiple potential depfiles %q, %q", *depfile, outputPath)
+ return
+ } else {
+ depfile = &outputPath
+ }
+ } else {
+ outputPaths = append(outputPaths, outputPath)
+ }
+ }
+ return
}
// expandTemplateContent substitutes the tokens in a template.
@@ -372,10 +518,10 @@
// removed from input paths and command of creating python zip file.
// See go/python-binary-host-mixed-build for more details.
// TODO(b/205879240) remove this after py3wrapper.sh could be created in the mixed build mode.
-func removePy3wrapperScript(bs BuildStatement) (newInputPaths []string, newCommand string) {
+func removePy3wrapperScript(inputPaths []string, command string) (newInputPaths []string, newCommand string) {
// Remove from inputs
filteredInputPaths := []string{}
- for _, path := range bs.InputPaths {
+ for _, path := range inputPaths {
if !strings.HasSuffix(path, py3wrapperFileName) {
filteredInputPaths = append(filteredInputPaths, path)
}
@@ -384,7 +530,7 @@
// Remove from command line
var re = regexp.MustCompile(`\S*` + py3wrapperFileName)
- newCommand = re.ReplaceAllString(bs.Command, "")
+ newCommand = re.ReplaceAllString(command, "")
return
}
@@ -395,14 +541,14 @@
// so MANIFEST file could not be created, which also blocks the creation of runfiles directory.
// See go/python-binary-host-mixed-build for more details.
// TODO(b/197135294) create runfiles directory from MANIFEST file once it can be created from SourceSymlinkManifest action.
-func addCommandForPyBinaryRunfilesDir(bs BuildStatement, zipperCommandPath, zipFilePath string) string {
+func addCommandForPyBinaryRunfilesDir(oldCommand string, zipperCommandPath, zipFilePath string) string {
// Unzip the zip file, zipFilePath looks like <python_binary>.zip
runfilesDirName := zipFilePath[0:len(zipFilePath)-4] + ".runfiles"
command := fmt.Sprintf("%s x %s -d %s", zipperCommandPath, zipFilePath, runfilesDirName)
// Create a symbolic link in <python_binary>.runfiles/, which is the expected structure
// when running the python binary stub script.
command += fmt.Sprintf(" && ln -sf runfiles/__main__ %s", runfilesDirName)
- return bs.Command + " && " + command
+ return oldCommand + " && " + command
}
func isSymlinkAction(a action) bool {
diff --git a/bazel/aquery_test.go b/bazel/aquery_test.go
index 68e50c2..2328411 100644
--- a/bazel/aquery_test.go
+++ b/bazel/aquery_test.go
@@ -223,7 +223,7 @@
"parentId": 13
}]
}`
- actualbuildStatements, _ := AqueryBuildStatements([]byte(inputString))
+ actualbuildStatements, actualDepsets, _ := AqueryBuildStatements([]byte(inputString))
expectedBuildStatements := []BuildStatement{}
for _, arch := range []string{"arm", "arm64", "x86", "x86_64"} {
expectedBuildStatements = append(expectedBuildStatements,
@@ -234,11 +234,7 @@
OutputPaths: []string{
fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-%s.S", arch),
},
- InputPaths: []string{
- "../sourceroot/bionic/libc/SYSCALLS.TXT",
- "../sourceroot/bionic/libc/tools/gensyscalls.py",
- "../bazel_tools/tools/genrule/genrule-setup.sh",
- },
+ InputDepsetIds: []int{1},
Env: []KeyValuePair{
KeyValuePair{Key: "PATH", Value: "/bin:/usr/bin:/usr/local/bin"},
},
@@ -246,6 +242,16 @@
})
}
assertBuildStatements(t, expectedBuildStatements, actualbuildStatements)
+
+ expectedFlattenedInputs := []string{
+ "../sourceroot/bionic/libc/SYSCALLS.TXT",
+ "../sourceroot/bionic/libc/tools/gensyscalls.py",
+ "../bazel_tools/tools/genrule/genrule-setup.sh",
+ }
+ actualFlattenedInputs := flattenDepsets([]int{1}, actualDepsets)
+ if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
+ t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
+ }
}
func TestInvalidOutputId(t *testing.T) {
@@ -280,11 +286,11 @@
}]
}`
- _, err := AqueryBuildStatements([]byte(inputString))
+ _, _, err := AqueryBuildStatements([]byte(inputString))
assertError(t, err, "undefined outputId 3")
}
-func TestInvalidInputDepsetId(t *testing.T) {
+func TestInvalidInputDepsetIdFromAction(t *testing.T) {
const inputString = `
{
"artifacts": [{
@@ -316,10 +322,47 @@
}]
}`
- _, err := AqueryBuildStatements([]byte(inputString))
+ _, _, err := AqueryBuildStatements([]byte(inputString))
assertError(t, err, "undefined input depsetId 2")
}
+func TestInvalidInputDepsetIdFromDepset(t *testing.T) {
+ const inputString = `
+{
+ "artifacts": [{
+ "id": 1,
+ "pathFragmentId": 1
+ }, {
+ "id": 2,
+ "pathFragmentId": 2
+ }],
+ "actions": [{
+ "targetId": 1,
+ "actionKey": "x",
+ "mnemonic": "x",
+ "arguments": ["touch", "foo"],
+ "inputDepSetIds": [1],
+ "outputIds": [1],
+ "primaryOutputId": 1
+ }],
+ "depSetOfFiles": [{
+ "id": 1,
+ "directArtifactIds": [1, 2],
+ "transitiveDepSetIds": [42]
+ }],
+ "pathFragments": [{
+ "id": 1,
+ "label": "one"
+ }, {
+ "id": 2,
+ "label": "two"
+ }]
+}`
+
+ _, _, err := AqueryBuildStatements([]byte(inputString))
+ assertError(t, err, "undefined input depsetId 42 (referenced by depsetId 1)")
+}
+
func TestInvalidInputArtifactId(t *testing.T) {
const inputString = `
{
@@ -352,7 +395,7 @@
}]
}`
- _, err := AqueryBuildStatements([]byte(inputString))
+ _, _, err := AqueryBuildStatements([]byte(inputString))
assertError(t, err, "undefined input artifactId 3")
}
@@ -389,7 +432,7 @@
}]
}`
- _, err := AqueryBuildStatements([]byte(inputString))
+ _, _, err := AqueryBuildStatements([]byte(inputString))
assertError(t, err, "undefined path fragment id 3")
}
@@ -431,7 +474,7 @@
}]
}`
- actual, err := AqueryBuildStatements([]byte(inputString))
+ actual, _, err := AqueryBuildStatements([]byte(inputString))
if err != nil {
t.Errorf("Unexpected error %q", err)
}
@@ -492,7 +535,7 @@
}]
}`
- _, err := AqueryBuildStatements([]byte(inputString))
+ _, _, err := AqueryBuildStatements([]byte(inputString))
assertError(t, err, `found multiple potential depfiles "two.d", "other.d"`)
}
@@ -699,23 +742,31 @@
}]
}`
- actualbuildStatements, _ := AqueryBuildStatements([]byte(inputString))
- // Inputs for the action are test_{i} from 1 to 20, and test_root. These inputs
- // are given via a deep depset, but the depset is flattened when returned as a
- // BuildStatement slice.
- inputPaths := []string{"bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_root"}
- for i := 1; i < 20; i++ {
- inputPaths = append(inputPaths, fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_%d", i))
- }
+ actualbuildStatements, actualDepsets, _ := AqueryBuildStatements([]byte(inputString))
+
expectedBuildStatements := []BuildStatement{
BuildStatement{
- Command: "/bin/bash -c 'touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out'",
- OutputPaths: []string{"bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"},
- InputPaths: inputPaths,
- Mnemonic: "Action",
+ Command: "/bin/bash -c 'touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out'",
+ OutputPaths: []string{"bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"},
+ InputDepsetIds: []int{1},
+ Mnemonic: "Action",
},
}
assertBuildStatements(t, expectedBuildStatements, actualbuildStatements)
+
+ // Inputs for the action are test_{i} from 1 to 20, and test_root. These inputs
+ // are given via a deep depset, but the depset is flattened when returned as a
+ // BuildStatement slice.
+ expectedFlattenedInputs := []string{}
+ for i := 1; i < 20; i++ {
+ expectedFlattenedInputs = append(expectedFlattenedInputs, fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_%d", i))
+ }
+ expectedFlattenedInputs = append(expectedFlattenedInputs, "bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_root")
+
+ actualFlattenedInputs := flattenDepsets([]int{1}, actualDepsets)
+ if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
+ t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
+ }
}
func TestMiddlemenAction(t *testing.T) {
@@ -785,24 +836,74 @@
}]
}`
- actual, err := AqueryBuildStatements([]byte(inputString))
+ actualBuildStatements, actualDepsets, err := AqueryBuildStatements([]byte(inputString))
if err != nil {
t.Errorf("Unexpected error %q", err)
}
- if expected := 1; len(actual) != expected {
- t.Fatalf("Expected %d build statements, got %d", expected, len(actual))
+ if expected := 1; len(actualBuildStatements) != expected {
+ t.Fatalf("Expected %d build statements, got %d", expected, len(actualBuildStatements))
}
- bs := actual[0]
- expectedInputs := []string{"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"}
- if !reflect.DeepEqual(bs.InputPaths, expectedInputs) {
- t.Errorf("Expected main action inputs %q, but got %q", expectedInputs, bs.InputPaths)
+ bs := actualBuildStatements[0]
+ if len(bs.InputPaths) > 0 {
+ t.Errorf("Expected main action raw inputs to be empty, but got %q", bs.InputPaths)
+ }
+
+ expectedInputDepsets := []int{2}
+ if !reflect.DeepEqual(bs.InputDepsetIds, expectedInputDepsets) {
+ t.Errorf("Expected main action depset IDs %v, but got %v", expectedInputDepsets, bs.InputDepsetIds)
}
expectedOutputs := []string{"output"}
if !reflect.DeepEqual(bs.OutputPaths, expectedOutputs) {
t.Errorf("Expected main action outputs %q, but got %q", expectedOutputs, bs.OutputPaths)
}
+
+ expectedAllDepsets := []AqueryDepset{
+ {
+ Id: 1,
+ DirectArtifacts: []string{"middleinput_one", "middleinput_two"},
+ },
+ {
+ Id: 2,
+ DirectArtifacts: []string{"maininput_one", "maininput_two"},
+ TransitiveDepSetIds: []int{1},
+ },
+ }
+ if !reflect.DeepEqual(actualDepsets, expectedAllDepsets) {
+ t.Errorf("Expected depsets %v, but got %v", expectedAllDepsets, actualDepsets)
+ }
+
+ expectedFlattenedInputs := []string{"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"}
+ actualFlattenedInputs := flattenDepsets(bs.InputDepsetIds, actualDepsets)
+
+ if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
+ t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
+ }
+}
+
+// Returns the contents of given depsets in concatenated post order.
+func flattenDepsets(depsetIdsToFlatten []int, allDepsets []AqueryDepset) []string {
+ depsetsById := map[int]AqueryDepset{}
+ for _, depset := range allDepsets {
+ depsetsById[depset.Id] = depset
+ }
+ result := []string{}
+ for _, depsetId := range depsetIdsToFlatten {
+ result = append(result, flattenDepset(depsetId, depsetsById)...)
+ }
+ return result
+}
+
+// Returns the contents of a given depset in post order.
+func flattenDepset(depsetIdToFlatten int, allDepsets map[int]AqueryDepset) []string {
+ depset := allDepsets[depsetIdToFlatten]
+ result := []string{}
+ for _, depsetId := range depset.TransitiveDepSetIds {
+ result = append(result, flattenDepset(depsetId, allDepsets)...)
+ }
+ result = append(result, depset.DirectArtifacts...)
+ return result
}
func TestSimpleSymlink(t *testing.T) {
@@ -849,7 +950,7 @@
}]
}`
- actual, err := AqueryBuildStatements([]byte(inputString))
+ actual, _, err := AqueryBuildStatements([]byte(inputString))
if err != nil {
t.Errorf("Unexpected error %q", err)
@@ -913,7 +1014,7 @@
}]
}`
- actual, err := AqueryBuildStatements([]byte(inputString))
+ actual, _, err := AqueryBuildStatements([]byte(inputString))
if err != nil {
t.Errorf("Unexpected error %q", err)
@@ -970,7 +1071,7 @@
}]
}`
- _, err := AqueryBuildStatements([]byte(inputString))
+ _, _, err := AqueryBuildStatements([]byte(inputString))
assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file" "other_file"], output ["symlink"]`)
}
@@ -1011,7 +1112,7 @@
}]
}`
- _, err := AqueryBuildStatements([]byte(inputString))
+ _, _, err := AqueryBuildStatements([]byte(inputString))
assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file"], output ["symlink" "other_symlink"]`)
}
@@ -1045,7 +1146,7 @@
}]
}`
- actual, err := AqueryBuildStatements([]byte(inputString))
+ actual, _, err := AqueryBuildStatements([]byte(inputString))
if err != nil {
t.Errorf("Unexpected error %q", err)
@@ -1091,7 +1192,7 @@
}]
}`
- _, err := AqueryBuildStatements([]byte(inputString))
+ _, _, err := AqueryBuildStatements([]byte(inputString))
assertError(t, err, `Expect 1 output to template expand action, got: output []`)
}
@@ -1211,7 +1312,7 @@
"label": "python_binary"
}]
}`
- actual, err := AqueryBuildStatements([]byte(inputString))
+ actual, _, err := AqueryBuildStatements([]byte(inputString))
if err != nil {
t.Errorf("Unexpected error %q", err)
@@ -1264,7 +1365,7 @@
"label": "python_binary.zip"
}]
}`
- _, err := AqueryBuildStatements([]byte(inputString))
+ _, _, err := AqueryBuildStatements([]byte(inputString))
assertError(t, err, `Expect 1+ input and 1 output to python zipper action, got: input [], output ["python_binary.zip"]`)
}
@@ -1360,7 +1461,7 @@
"parentId": 11
}]
}`
- _, err := AqueryBuildStatements([]byte(inputString))
+ _, _, err := AqueryBuildStatements([]byte(inputString))
assertError(t, err, `Expect 1+ input and 1 output to python zipper action, got: input ["../bazel_tools/tools/zip/zipper/zipper" "python_binary.py"], output []`)
}
diff --git a/bp2build/android_app_conversion_test.go b/bp2build/android_app_conversion_test.go
index 3824586..a216c9d 100644
--- a/bp2build/android_app_conversion_test.go
+++ b/bp2build/android_app_conversion_test.go
@@ -74,7 +74,8 @@
package_name: "com.google",
resource_dirs: ["resa", "resb"],
manifest: "manifest/AndroidManifest.xml",
- static_libs: ["static_lib_dep"]
+ static_libs: ["static_lib_dep"],
+ java_version: "7",
}
`,
expectedBazelTargets: []string{
@@ -87,6 +88,7 @@
]`,
"custom_package": `"com.google"`,
"deps": `[":static_lib_dep"]`,
+ "javacopts": `["-source 1.7 -target 1.7"]`,
}),
}})
}
diff --git a/bp2build/java_binary_host_conversion_test.go b/bp2build/java_binary_host_conversion_test.go
index 4fc07e0..d7a76a8 100644
--- a/bp2build/java_binary_host_conversion_test.go
+++ b/bp2build/java_binary_host_conversion_test.go
@@ -52,6 +52,7 @@
jni_libs: ["jni-lib-1"],
javacflags: ["-Xdoclint:all/protected"],
bazel_module: { bp2build_available: true },
+ java_version: "8",
}`,
expectedBazelTargets: []string{
makeBazelTarget("java_binary", "java-binary-host-1", attrNameToString{
@@ -59,7 +60,10 @@
"main_class": `"com.android.test.MainClass"`,
"deps": `["//other:jni-lib-1"]`,
"jvm_flags": `["-Djava.library.path=$${RUNPATH}other"]`,
- "javacopts": `["-Xdoclint:all/protected"]`,
+ "javacopts": `[
+ "-Xdoclint:all/protected",
+ "-source 1.8 -target 1.8",
+ ]`,
"target_compatible_with": `select({
"//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
"//conditions:default": [],
diff --git a/bp2build/java_library_conversion_test.go b/bp2build/java_library_conversion_test.go
index ccc52ef..3b66369 100644
--- a/bp2build/java_library_conversion_test.go
+++ b/bp2build/java_library_conversion_test.go
@@ -158,6 +158,22 @@
})
}
+func TestJavaLibraryJavaVersion(t *testing.T) {
+ runJavaLibraryTestCase(t, bp2buildTestCase{
+ blueprint: `java_library {
+ name: "java-lib-1",
+ srcs: ["a.java"],
+ java_version: "11",
+}`,
+ expectedBazelTargets: []string{
+ makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+ "srcs": `["a.java"]`,
+ "javacopts": `["-source 11 -target 11"]`,
+ }),
+ },
+ })
+}
+
func TestJavaLibraryErrorproneJavacflagsEnabledManually(t *testing.T) {
runJavaLibraryTestCase(t, bp2buildTestCase{
blueprint: `java_library {
diff --git a/bp2build/java_library_host_conversion_test.go b/bp2build/java_library_host_conversion_test.go
index 73abdd2..83cc551 100644
--- a/bp2build/java_library_host_conversion_test.go
+++ b/bp2build/java_library_host_conversion_test.go
@@ -43,6 +43,7 @@
name: "java-lib-host-2",
srcs: ["c.java"],
bazel_module: { bp2build_available: true },
+ java_version: "9",
}`,
expectedBazelTargets: []string{
makeBazelTarget("java_library", "java-lib-host-1", attrNameToString{
@@ -54,7 +55,8 @@
})`,
}),
makeBazelTarget("java_library", "java-lib-host-2", attrNameToString{
- "srcs": `["c.java"]`,
+ "javacopts": `["-source 1.9 -target 1.9"]`,
+ "srcs": `["c.java"]`,
"target_compatible_with": `select({
"//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
"//conditions:default": [],
diff --git a/bp2build/java_plugin_conversion_test.go b/bp2build/java_plugin_conversion_test.go
index c2a2182..dc763e7 100644
--- a/bp2build/java_plugin_conversion_test.go
+++ b/bp2build/java_plugin_conversion_test.go
@@ -39,6 +39,7 @@
libs: ["java-lib-1"],
static_libs: ["java-lib-2"],
bazel_module: { bp2build_available: true },
+ java_version: "7",
}
java_library {
@@ -66,6 +67,7 @@
"a.java",
"b.java",
]`,
+ "javacopts": `["-source 1.7 -target 1.7"]`,
}),
},
})
diff --git a/bp2build/java_proto_conversion_test.go b/bp2build/java_proto_conversion_test.go
index 67f8044..c6feeb8 100644
--- a/bp2build/java_proto_conversion_test.go
+++ b/bp2build/java_proto_conversion_test.go
@@ -102,6 +102,7 @@
blueprint: `java_library_static {
name: "java-protos",
srcs: ["a.proto"],
+ java_version: "7",
}
`,
expectedBazelTargets: []string{
@@ -115,7 +116,8 @@
"deps": `[":java-protos_proto"]`,
}),
makeBazelTarget("java_library", "java-protos", attrNameToString{
- "exports": `[":java-protos_java_proto_lite"]`,
+ "exports": `[":java-protos_java_proto_lite"]`,
+ "javacopts": `["-source 1.7 -target 1.7"]`,
}),
},
})
diff --git a/cc/Android.bp b/cc/Android.bp
index b105e7c..60d329e 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -15,6 +15,7 @@
"soong-etc",
"soong-fuzz",
"soong-genrule",
+ "soong-multitree",
"soong-snapshot",
"soong-tradefed",
],
@@ -65,6 +66,7 @@
"library.go",
"library_headers.go",
"library_sdk_member.go",
+ "library_stub.go",
"native_bridge_sdk_trait.go",
"object.go",
"test.go",
@@ -94,6 +96,7 @@
"gen_test.go",
"genrule_test.go",
"library_headers_test.go",
+ "library_stub_test.go",
"library_test.go",
"object_test.go",
"prebuilt_test.go",
diff --git a/cc/builder.go b/cc/builder.go
index 525b1a1..107cd58 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -855,13 +855,24 @@
deps = append(deps, crtBegin...)
deps = append(deps, crtEnd...)
+ var depFile android.WritablePath
+ var depFileLdFlags string
+ depsType := blueprint.DepsNone
+ if !ctx.Windows() && !ctx.Darwin() {
+ // lld only supports --dependency-file for elf files
+ depFile = outputFile.ReplaceExtension(ctx, "d")
+ depFileLdFlags = " -Wl,--dependency-file=" + depFile.String()
+ depsType = blueprint.DepsGCC
+ implicitOutputs = append(implicitOutputs, depFile)
+ }
+
rule := ld
args := map[string]string{
"ldCmd": ldCmd,
"crtBegin": strings.Join(crtBegin.Strings(), " "),
"libFlags": strings.Join(libFlagsList, " "),
"extraLibFlags": flags.extraLibFlags,
- "ldFlags": flags.globalLdFlags + " " + flags.localLdFlags,
+ "ldFlags": flags.globalLdFlags + " " + flags.localLdFlags + depFileLdFlags,
"crtEnd": strings.Join(crtEnd.Strings(), " "),
}
if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_CXX_LINKS") {
@@ -872,6 +883,8 @@
ctx.Build(pctx, android.BuildParams{
Rule: rule,
+ Deps: depsType,
+ Depfile: depFile,
Description: "link " + outputFile.Base(),
Output: outputFile,
ImplicitOutputs: implicitOutputs,
@@ -1025,18 +1038,33 @@
ldCmd := "${config.ClangBin}/clang++"
+ var implicitOutputs android.WritablePaths
+ var depFile android.WritablePath
+ var depFileLdFlags string
+ depsType := blueprint.DepsNone
+ if !ctx.Windows() && !ctx.Darwin() {
+ // lld only supports --dependency-file for elf files
+ depFile = outputFile.ReplaceExtension(ctx, "d")
+ depFileLdFlags = " -Wl,--dependency-file=" + depFile.String()
+ depsType = blueprint.DepsGCC
+ implicitOutputs = append(implicitOutputs, depFile)
+ }
+
rule := partialLd
args := map[string]string{
"ldCmd": ldCmd,
- "ldFlags": flags.globalLdFlags + " " + flags.localLdFlags,
+ "ldFlags": flags.globalLdFlags + " " + flags.localLdFlags + depFileLdFlags,
}
if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_CXX_LINKS") {
rule = partialLdRE
args["inCommaList"] = strings.Join(objFiles.Strings(), ",")
args["implicitInputs"] = strings.Join(deps.Strings(), ",")
+ args["implicitOutputs"] = strings.Join(implicitOutputs.Strings(), ",")
}
ctx.Build(pctx, android.BuildParams{
Rule: rule,
+ Deps: depsType,
+ Depfile: depFile,
Description: "link " + outputFile.Base(),
Output: outputFile,
Inputs: objFiles,
diff --git a/cc/config/global.go b/cc/config/global.go
index 3caf327..dc6310c 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -286,8 +286,8 @@
// prebuilts/clang default settings.
ClangDefaultBase = "prebuilts/clang/host"
- ClangDefaultVersion = "clang-r450784d"
- ClangDefaultShortVersion = "14.0.6"
+ ClangDefaultVersion = "clang-r450784e"
+ ClangDefaultShortVersion = "14.0.7"
// Directories with warnings from Android.bp files.
WarningAllowedProjects = []string{
diff --git a/cc/libbuildversion/tests/Android.bp b/cc/libbuildversion/tests/Android.bp
index 0e97fed..c616a33 100644
--- a/cc/libbuildversion/tests/Android.bp
+++ b/cc/libbuildversion/tests/Android.bp
@@ -35,6 +35,16 @@
dir: "host/",
},
},
+ linux_musl_x86: {
+ dist: {
+ dir: "host32/",
+ },
+ },
+ linux_musl_x86_64: {
+ dist: {
+ dir: "host/",
+ },
+ },
linux_glibc_x86: {
dist: {
dir: "host32/",
diff --git a/cc/library_stub.go b/cc/library_stub.go
new file mode 100644
index 0000000..4d0148d
--- /dev/null
+++ b/cc/library_stub.go
@@ -0,0 +1,163 @@
+// Copyright 2021 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 cc
+
+import (
+ "android/soong/android"
+ "android/soong/multitree"
+)
+
+func init() {
+ RegisterLibraryStubBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterLibraryStubBuildComponents(ctx android.RegistrationContext) {
+ // cc_api_stub_library shares a lot of ndk_library, and this will be refactored later
+ ctx.RegisterModuleType("cc_api_stub_library", CcApiStubLibraryFactory)
+ ctx.RegisterModuleType("cc_api_contribution", CcApiContributionFactory)
+}
+
+func CcApiStubLibraryFactory() android.Module {
+ module, decorator := NewLibrary(android.DeviceSupported)
+ apiStubDecorator := &apiStubDecorator{
+ libraryDecorator: decorator,
+ }
+ apiStubDecorator.BuildOnlyShared()
+
+ module.compiler = apiStubDecorator
+ module.linker = apiStubDecorator
+ module.installer = nil
+ module.library = apiStubDecorator
+ module.Properties.HideFromMake = true // TODO: remove
+
+ android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibBoth)
+ module.AddProperties(&module.Properties,
+ &apiStubDecorator.properties,
+ &apiStubDecorator.MutatedProperties,
+ &apiStubDecorator.apiStubLibraryProperties)
+ return module
+}
+
+type apiStubLiraryProperties struct {
+ Imported_includes []string `android:"path"`
+}
+
+type apiStubDecorator struct {
+ *libraryDecorator
+ properties libraryProperties
+ apiStubLibraryProperties apiStubLiraryProperties
+}
+
+func (compiler *apiStubDecorator) stubsVersions(ctx android.BaseMutatorContext) []string {
+ firstVersion := String(compiler.properties.First_version)
+ return ndkLibraryVersions(ctx, android.ApiLevelOrPanic(ctx, firstVersion))
+}
+
+func (decorator *apiStubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
+ if decorator.stubsVersion() == "" {
+ decorator.setStubsVersion("current")
+ } // TODO: fix
+ symbolFile := String(decorator.properties.Symbol_file)
+ nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile,
+ android.ApiLevelOrPanic(ctx, decorator.stubsVersion()),
+ "")
+ return compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
+}
+
+func (decorator *apiStubDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, objects Objects) android.Path {
+ decorator.reexportDirs(android.PathsForModuleSrc(ctx, decorator.apiStubLibraryProperties.Imported_includes)...)
+ return decorator.libraryDecorator.link(ctx, flags, deps, objects)
+}
+
+func init() {
+ pctx.HostBinToolVariable("gen_api_surface_build_files", "gen_api_surface_build_files")
+}
+
+type CcApiContribution struct {
+ android.ModuleBase
+ properties ccApiContributionProperties
+}
+
+type ccApiContributionProperties struct {
+ Symbol_file *string `android:"path"`
+ First_version *string
+ Export_include_dir *string
+}
+
+func CcApiContributionFactory() android.Module {
+ module := &CcApiContribution{}
+ module.AddProperties(&module.properties)
+ android.InitAndroidModule(module)
+ return module
+}
+
+// Do some simple validations
+// Majority of the build rules will be created in the ctx of the api surface this module contributes to
+func (contrib *CcApiContribution) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ if contrib.properties.Symbol_file == nil {
+ ctx.PropertyErrorf("symbol_file", "%v does not have symbol file", ctx.ModuleName())
+ }
+ if contrib.properties.First_version == nil {
+ ctx.PropertyErrorf("first_version", "%v does not have first_version for stub variants", ctx.ModuleName())
+ }
+}
+
+// Path is out/soong/.export/ but will be different in final multi-tree layout
+func outPathApiSurface(ctx android.ModuleContext, myModuleName string, pathComponent string) android.OutputPath {
+ return android.PathForOutput(ctx, ".export", ctx.ModuleName(), myModuleName, pathComponent)
+}
+
+func (contrib *CcApiContribution) CopyFilesWithTag(apiSurfaceContext android.ModuleContext) map[string]android.Paths {
+ // copy map.txt for now
+ // hardlinks cannot be created since nsjail creates a different mountpoint for out/
+ myDir := apiSurfaceContext.OtherModuleDir(contrib)
+ genMapTxt := outPathApiSurface(apiSurfaceContext, contrib.Name(), String(contrib.properties.Symbol_file))
+ apiSurfaceContext.Build(pctx, android.BuildParams{
+ Rule: android.Cp,
+ Description: "import map.txt file",
+ Input: android.PathForSource(apiSurfaceContext, myDir, String(contrib.properties.Symbol_file)),
+ Output: genMapTxt,
+ })
+
+ outputs := make(map[string]android.Paths)
+ outputs["map"] = []android.Path{genMapTxt}
+
+ if contrib.properties.Export_include_dir != nil {
+ includeDir := android.PathForSource(apiSurfaceContext, myDir, String(contrib.properties.Export_include_dir))
+ outputs["export_include_dir"] = []android.Path{includeDir}
+ }
+ return outputs
+}
+
+var _ multitree.ApiContribution = (*CcApiContribution)(nil)
+
+/*
+func (contrib *CcApiContribution) GenerateBuildFiles(apiSurfaceContext android.ModuleContext) android.Paths {
+ genAndroidBp := outPathApiSurface(apiSurfaceContext, contrib.Name(), "Android.bp")
+
+ // generate Android.bp
+ apiSurfaceContext.Build(pctx, android.BuildParams{
+ Rule: genApiSurfaceBuildFiles,
+ Description: "generate API surface build files",
+ Outputs: []android.WritablePath{genAndroidBp},
+ Args: map[string]string{
+ "name": contrib.Name() + "." + apiSurfaceContext.ModuleName(), //e.g. liblog.ndk
+ "symbol_file": String(contrib.properties.Symbol_file),
+ "first_version": String(contrib.properties.First_version),
+ },
+ })
+ return []android.Path{genAndroidBp}
+}
+*/
diff --git a/cc/library_stub_test.go b/cc/library_stub_test.go
new file mode 100644
index 0000000..15b56d2
--- /dev/null
+++ b/cc/library_stub_test.go
@@ -0,0 +1,108 @@
+// Copyright 2021 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 cc
+
+import (
+ _ "fmt"
+ _ "sort"
+
+ "testing"
+
+ "android/soong/android"
+ "android/soong/multitree"
+)
+
+func TestCcApiStubLibraryOutputFiles(t *testing.T) {
+ bp := `
+ cc_api_stub_library {
+ name: "foo",
+ symbol_file: "foo.map.txt",
+ first_version: "29",
+ }
+ `
+ result := prepareForCcTest.RunTestWithBp(t, bp)
+ outputs := result.ModuleForTests("foo", "android_arm64_armv8-a_shared").AllOutputs()
+ expected_file_suffixes := []string{".c", "stub.map", ".o", ".so"}
+ for _, expected_file_suffix := range expected_file_suffixes {
+ android.AssertBoolEquals(t, expected_file_suffix+" file not found in output", true, android.SuffixInList(outputs, expected_file_suffix))
+ }
+}
+
+func TestCcApiStubLibraryVariants(t *testing.T) {
+ bp := `
+ cc_api_stub_library {
+ name: "foo",
+ symbol_file: "foo.map.txt",
+ first_version: "29",
+ }
+ `
+ result := prepareForCcTest.RunTestWithBp(t, bp)
+ variants := result.ModuleVariantsForTests("foo")
+ expected_variants := []string{"29", "30", "S", "Tiramisu"} //TODO: make this test deterministic by using fixtures
+ for _, expected_variant := range expected_variants {
+ android.AssertBoolEquals(t, expected_variant+" variant not found in foo", true, android.SubstringInList(variants, expected_variant))
+ }
+}
+
+func TestCcLibraryUsesCcApiStubLibrary(t *testing.T) {
+ bp := `
+ cc_api_stub_library {
+ name: "foo",
+ symbol_file: "foo.map.txt",
+ first_version: "29",
+ }
+ cc_library {
+ name: "foo_user",
+ shared_libs: [
+ "foo#29",
+ ],
+ }
+
+ `
+ prepareForCcTest.RunTestWithBp(t, bp)
+}
+
+func TestApiSurfaceOutputs(t *testing.T) {
+ bp := `
+ api_surface {
+ name: "mysdk",
+ contributions: [
+ "foo",
+ ],
+ }
+
+ cc_api_contribution {
+ name: "foo",
+ symbol_file: "foo.map.txt",
+ first_version: "29",
+ }
+ `
+ result := android.GroupFixturePreparers(
+ prepareForCcTest,
+ multitree.PrepareForTestWithApiSurface,
+ ).RunTestWithBp(t, bp)
+ mysdk := result.ModuleForTests("mysdk", "")
+
+ actual_surface_inputs := mysdk.Rule("phony").BuildParams.Inputs.Strings()
+ expected_file_suffixes := []string{"mysdk/foo/foo.map.txt"}
+ for _, expected_file_suffix := range expected_file_suffixes {
+ android.AssertBoolEquals(t, expected_file_suffix+" file not found in input", true, android.SuffixInList(actual_surface_inputs, expected_file_suffix))
+ }
+
+ // check args/inputs to rule
+ /*api_surface_gen_rule_args := result.ModuleForTests("mysdk", "").Rule("genApiSurfaceBuildFiles").Args
+ android.AssertStringEquals(t, "name", "foo.mysdk", api_surface_gen_rule_args["name"])
+ android.AssertStringEquals(t, "symbol_file", "foo.map.txt", api_surface_gen_rule_args["symbol_file"])*/
+}
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 5ef41ea..c031b14 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -93,7 +93,7 @@
type libraryProperties struct {
// Relative path to the symbol map.
// An example file can be seen here: TODO(danalbert): Make an example.
- Symbol_file *string
+ Symbol_file *string `android:"path"`
// The first API level a library was available. A library will be generated
// for every API level beginning with this one.
diff --git a/cc/testing.go b/cc/testing.go
index 32f7c60..ecdae8b 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -29,6 +29,7 @@
RegisterBinaryBuildComponents(ctx)
RegisterLibraryBuildComponents(ctx)
RegisterLibraryHeadersBuildComponents(ctx)
+ RegisterLibraryStubBuildComponents(ctx)
ctx.RegisterModuleType("cc_benchmark", BenchmarkFactory)
ctx.RegisterModuleType("cc_object", ObjectFactory)
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index 36513b6..d0a6a39 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -678,6 +678,9 @@
jClcMap := make(jsonClassLoaderContextMap)
for sdkVer, clcs := range clcMap {
sdkVerStr := fmt.Sprintf("%d", sdkVer)
+ if sdkVer == AnySdkVersion {
+ sdkVerStr = "any"
+ }
jClcMap[sdkVerStr] = toJsonClassLoaderContextRec(clcs)
}
return jClcMap
diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go
index 5d3a9d9..614681f 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -389,6 +389,38 @@
})
}
+// Test that CLC is correctly serialized to JSON.
+func TestCLCtoJSON(t *testing.T) {
+ ctx := testContext()
+ optional := false
+ implicit := true
+ m := make(ClassLoaderContextMap)
+ m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+ m.AddContext(ctx, AnySdkVersion, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
+ android.AssertStringEquals(t, "output CLCM ", `{
+ "28": [
+ {
+ "Name": "a",
+ "Optional": false,
+ "Implicit": true,
+ "Host": "out/soong/a.jar",
+ "Device": "/system/a.jar",
+ "Subcontexts": []
+ }
+ ],
+ "any": [
+ {
+ "Name": "b",
+ "Optional": false,
+ "Implicit": true,
+ "Host": "out/soong/b.jar",
+ "Device": "/system/b.jar",
+ "Subcontexts": []
+ }
+ ]
+}`, m.Dump())
+}
+
func checkError(t *testing.T, have error, want string) {
if have == nil {
t.Errorf("\nwant error: '%s'\nhave: none", want)
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 153b025..ab9e418 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -36,8 +36,6 @@
PreoptWithUpdatableBcp bool // If updatable boot jars are included in dexpreopt or not.
- UseArtImage bool // use the art image (use other boot class path dex files without image)
-
HasSystemOther bool // store odex files that match PatternsOnSystemOther on the system_other partition
PatternsOnSystemOther []string // patterns (using '%' to denote a prefix match) to put odex on the system_other partition
diff --git a/java/config/config.go b/java/config/config.go
index 46c91a2..d744002 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -68,6 +68,11 @@
"-J-XX:+TieredCompilation",
"-J-XX:TieredStopAtLevel=1",
}
+ dexerJavaVmFlagsList = []string{
+ `-JXX:OnError="cat hs_err_pid%p.log"`,
+ "-JXX:CICompilerCount=6",
+ "-JXX:+UseDynamicNumberOfGCThreads",
+ }
)
func init() {
@@ -83,19 +88,14 @@
// D8 invocations are shorter lived, so we restrict their JIT tiering relative to R8.
// Note that the `-JXX` prefix syntax is specific to the R8/D8 invocation wrappers.
- exportedVars.ExportStringListStaticVariable("D8Flags", []string{
- `-JXX:OnError="cat hs_err_pid%p.log"`,
- "-JXX:CICompilerCount=6",
- "-JXX:+UseDynamicNumberOfGCThreads",
+ exportedVars.ExportStringListStaticVariable("D8Flags", append([]string{
+ "-JXmx2048M",
"-JXX:+TieredCompilation",
"-JXX:TieredStopAtLevel=1",
- })
-
- exportedVars.ExportStringListStaticVariable("R8Flags", []string{
- `-JXX:OnError="cat hs_err_pid%p.log"`,
- "-JXX:CICompilerCount=6",
- "-JXX:+UseDynamicNumberOfGCThreads",
- })
+ }, dexerJavaVmFlagsList...))
+ exportedVars.ExportStringListStaticVariable("R8Flags", append([]string{
+ "-JXmx2048M",
+ }, dexerJavaVmFlagsList...))
exportedVars.ExportStringListStaticVariable("CommonJdkFlags", []string{
`-Xmaxerrs 9999999`,
diff --git a/java/config/makevars.go b/java/config/makevars.go
index bc6848f..273aca0 100644
--- a/java/config/makevars.go
+++ b/java/config/makevars.go
@@ -43,9 +43,10 @@
ctx.Strict("JAVADOC", "${JavadocCmd}")
ctx.Strict("COMMON_JDK_FLAGS", "${CommonJdkFlags}")
- ctx.Strict("DX", "${D8Cmd}")
- ctx.Strict("DX_COMMAND", "${D8Cmd} -JXms16M -JXmx2048M")
- ctx.Strict("R8_COMPAT_PROGUARD", "${R8Cmd}")
+ ctx.Strict("D8", "${D8Cmd}")
+ ctx.Strict("R8", "${R8Cmd}")
+ ctx.Strict("D8_COMMAND", "${D8Cmd} ${D8Flags}")
+ ctx.Strict("R8_COMMAND", "${R8Cmd} ${R8Flags}")
ctx.Strict("TURBINE", "${TurbineJar}")
@@ -78,9 +79,6 @@
ctx.Strict("CLASS2NONSDKLIST", "${Class2NonSdkList}")
ctx.Strict("HIDDENAPI", "${HiddenAPI}")
- ctx.Strict("D8_FLAGS", "${D8Flags}")
- ctx.Strict("R8_FLAGS", "${R8Flags}")
-
ctx.Strict("AIDL", "${AidlCmd}")
ctx.Strict("AAPT2", "${Aapt2Cmd}")
ctx.Strict("ZIPALIGN", "${ZipAlign}")
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 7c5f055..0adaf99 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -259,10 +259,6 @@
isSystemServerJar := global.AllSystemServerJars(ctx).ContainsJar(moduleName(ctx))
bootImage := defaultBootImageConfig(ctx)
- if global.UseArtImage {
- bootImage = artBootImageConfig(ctx)
- }
-
dexFiles, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp)
targets := ctx.MultiTargets()
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 3d91aec..7c4da3e 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -213,12 +213,6 @@
// writes out a few DEXPREOPT_IMAGE_* variables for Make; these variables contain boot image names,
// paths and so on.
//
-// 2.5. JIT-Zygote configuration
-// -----------------------------
-//
-// One special configuration is JIT-Zygote build, when the primary ART image is used for compiling
-// apps instead of the Framework boot image extension (see DEXPREOPT_USE_ART_IMAGE and UseArtImage).
-//
var artApexNames = []string{
"com.android.art",
@@ -938,11 +932,8 @@
ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(dexLocations, " "))
var imageNames []string
- // TODO: the primary ART boot image should not be exposed to Make, as it is installed in a
- // different way as a part of the ART APEX. However, there is a special JIT-Zygote build
- // configuration which uses the primary ART image instead of the Framework boot image
- // extension, and it relies on the ART image being exposed to Make. To fix this, it is
- // necessary to rework the logic in makefiles.
+ // The primary ART boot image is exposed to Make for testing (gtests) and benchmarking
+ // (golem) purposes.
for _, current := range append(d.otherImages, image) {
imageNames = append(imageNames, current.name)
for _, variant := range current.variants {
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index 3af5f1c..cf9c7ad 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -65,6 +65,8 @@
type hiddenAPIModule interface {
android.Module
hiddenAPIIntf
+
+ MinSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec
}
type hiddenAPIIntf interface {
@@ -148,7 +150,7 @@
// Create a copy of the dex jar which has been encoded with hiddenapi flags.
flagsCSV := hiddenAPISingletonPaths(ctx).flags
outputDir := android.PathForModuleOut(ctx, "hiddenapi").OutputPath
- encodedDex := hiddenAPIEncodeDex(ctx, dexJar, flagsCSV, uncompressDex, outputDir)
+ encodedDex := hiddenAPIEncodeDex(ctx, dexJar, flagsCSV, uncompressDex, android.SdkSpecNone, outputDir)
// Use the encoded dex jar from here onwards.
return encodedDex
@@ -246,7 +248,7 @@
// The encode dex rule requires unzipping, encoding and rezipping the classes.dex files along with
// all the resources from the input jar. It also ensures that if it was uncompressed in the input
// it stays uncompressed in the output.
-func hiddenAPIEncodeDex(ctx android.ModuleContext, dexInput, flagsCSV android.Path, uncompressDex bool, outputDir android.OutputPath) android.OutputPath {
+func hiddenAPIEncodeDex(ctx android.ModuleContext, dexInput, flagsCSV android.Path, uncompressDex bool, minSdkVersion android.SdkSpec, outputDir android.OutputPath) android.OutputPath {
// The output file has the same name as the input file and is in the output directory.
output := outputDir.Join(ctx, dexInput.Base())
@@ -274,6 +276,15 @@
hiddenapiFlags = "--no-force-assign-all"
}
+ // If the library is targeted for Q and/or R then make sure that they do not
+ // have any S+ flags encoded as that will break the runtime.
+ minApiLevel := minSdkVersion.ApiLevel
+ if !minApiLevel.IsNone() {
+ if minApiLevel.LessThanOrEqualTo(android.ApiLevelOrPanic(ctx, "R")) {
+ hiddenapiFlags = hiddenapiFlags + " --max-hiddenapi-level=max-target-r"
+ }
+ }
+
ctx.Build(pctx, android.BuildParams{
Rule: hiddenAPIEncodeDexRule,
Description: "hiddenapi encode dex",
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
index 534a814..c90b2ff 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -1104,7 +1104,7 @@
for _, name := range android.SortedStringKeys(bootDexInfoByModule) {
bootDexInfo := bootDexInfoByModule[name]
unencodedDex := bootDexInfo.path
- encodedDex := hiddenAPIEncodeDex(ctx, unencodedDex, allFlagsCSV, bootDexInfo.uncompressDex, outputDir)
+ encodedDex := hiddenAPIEncodeDex(ctx, unencodedDex, allFlagsCSV, bootDexInfo.uncompressDex, bootDexInfo.minSdkVersion, outputDir)
encodedBootDexJarsByModule[name] = encodedDex
}
@@ -1188,6 +1188,9 @@
// Indicates whether the dex jar needs uncompressing before encoding.
uncompressDex bool
+
+ // The minimum sdk version that the dex jar will be used on.
+ minSdkVersion android.SdkSpec
}
// bootDexInfoByModule is a map from module name (as returned by module.Name()) to the boot dex
@@ -1213,6 +1216,7 @@
bootDexJarsByModule[module.Name()] = bootDexInfo{
path: bootDexJar,
uncompressDex: *hiddenAPIModule.uncompressDex(),
+ minSdkVersion: hiddenAPIModule.MinSdkVersion(ctx),
}
}
diff --git a/java/java.go b/java/java.go
index b34d6de..4e7e14c 100644
--- a/java/java.go
+++ b/java/java.go
@@ -2089,6 +2089,11 @@
if m.properties.Javacflags != nil {
javacopts = append(javacopts, m.properties.Javacflags...)
}
+ if m.properties.Java_version != nil {
+ javaVersion := normalizeJavaVersion(ctx, *m.properties.Java_version).String()
+ javacopts = append(javacopts, fmt.Sprintf("-source %s -target %s", javaVersion, javaVersion))
+ }
+
epEnabled := m.properties.Errorprone.Enabled
//TODO(b/227504307) add configuration that depends on RUN_ERROR_PRONE environment variable
if Bool(epEnabled) {
diff --git a/java/sdk_library.go b/java/sdk_library.go
index c37ed1a..cd8e875 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -377,6 +377,9 @@
// List of Java libraries that will be in the classpath when building the implementation lib
Impl_only_libs []string `android:"arch_variant"`
+ // List of Java libraries that will included in the implementation lib.
+ Impl_only_static_libs []string `android:"arch_variant"`
+
// List of Java libraries that will be in the classpath when building stubs
Stub_only_libs []string `android:"arch_variant"`
@@ -1346,10 +1349,12 @@
visibility := childModuleVisibility(module.sdkLibraryProperties.Impl_library_visibility)
props := struct {
- Name *string
- Visibility []string
- Instrument bool
- Libs []string
+ Name *string
+ Visibility []string
+ Instrument bool
+ Libs []string
+ Static_libs []string
+ Apex_available []string
}{
Name: proptools.StringPtr(module.implLibraryModuleName()),
Visibility: visibility,
@@ -1358,6 +1363,12 @@
// Set the impl_only libs. Note that the module's "Libs" get appended as well, via the
// addition of &module.properties below.
Libs: module.sdkLibraryProperties.Impl_only_libs,
+ // Set the impl_only static libs. Note that the module's "static_libs" get appended as well, via the
+ // addition of &module.properties below.
+ Static_libs: module.sdkLibraryProperties.Impl_only_static_libs,
+ // Pass the apex_available settings down so that the impl library can be statically
+ // embedded within a library that is added to an APEX. Needed for updatable-media.
+ Apex_available: module.ApexAvailable(),
}
properties := []interface{}{
@@ -1814,8 +1825,9 @@
*javaSdkLibraries = append(*javaSdkLibraries, module.BaseModuleName())
}
- // Add the impl_only_libs *after* we're done using the Libs prop in submodules.
+ // Add the impl_only_libs and impl_only_static_libs *after* we're done using them in submodules.
module.properties.Libs = append(module.properties.Libs, module.sdkLibraryProperties.Impl_only_libs...)
+ module.properties.Static_libs = append(module.properties.Static_libs, module.sdkLibraryProperties.Impl_only_static_libs...)
}
func (module *SdkLibrary) InitSdkLibraryProperties() {
@@ -2211,8 +2223,23 @@
return module.uniqueApexVariations()
}
+// MinSdkVersion - Implements hiddenAPIModule
+func (module *SdkLibraryImport) MinSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
+ return android.SdkSpecNone
+}
+
+var _ hiddenAPIModule = (*SdkLibraryImport)(nil)
+
func (module *SdkLibraryImport) OutputFiles(tag string) (android.Paths, error) {
- return module.commonOutputFiles(tag)
+ paths, err := module.commonOutputFiles(tag)
+ if paths != nil || err != nil {
+ return paths, err
+ }
+ if module.implLibraryModule != nil {
+ return module.implLibraryModule.OutputFiles(tag)
+ } else {
+ return nil, nil
+ }
}
func (module *SdkLibraryImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
diff --git a/mk2rbc/cmd/mk2rbc.go b/mk2rbc/cmd/mk2rbc.go
index e84eacd..cc83430 100644
--- a/mk2rbc/cmd/mk2rbc.go
+++ b/mk2rbc/cmd/mk2rbc.go
@@ -173,7 +173,7 @@
}
ok := true
for _, mkFile := range files {
- ok = convertOne(mkFile) && ok
+ ok = convertOne(mkFile, []string{}) && ok
}
if *launcher != "" {
@@ -183,7 +183,7 @@
if *inputVariables == "" {
quit(fmt.Errorf("the product launcher requires an input variables file"))
}
- if !convertOne(*inputVariables) {
+ if !convertOne(*inputVariables, []string{}) {
quit(fmt.Errorf("the product launcher input variables file failed to convert"))
}
@@ -201,7 +201,7 @@
if *inputVariables == "" {
quit(fmt.Errorf("the board launcher requires an input variables file"))
}
- if !convertOne(*inputVariables) {
+ if !convertOne(*inputVariables, []string{}) {
quit(fmt.Errorf("the board launcher input variables file failed to convert"))
}
err := writeGenerated(*boardlauncher, mk2rbc.BoardLauncher(
@@ -310,9 +310,13 @@
// the output hierarchy, or to the stdout.
// Optionally, recursively convert the files this one includes by
// $(call inherit-product) or an include statement.
-func convertOne(mkFile string) (ok bool) {
+func convertOne(mkFile string, loadStack []string) (ok bool) {
if v, ok := converted[mkFile]; ok {
- return v != nil
+ if v == nil {
+ fmt.Fprintf(os.Stderr, "Cycle in load graph:\n%s\n%s\n\n", strings.Join(loadStack, "\n"), mkFile)
+ return false
+ }
+ return true
}
converted[mkFile] = nil
defer func() {
@@ -356,6 +360,7 @@
return false
}
}
+ loadStack = append(loadStack, mkFile)
ok = true
if *recurse {
for _, sub := range ss.SubConfigFiles() {
@@ -363,7 +368,7 @@
if _, err := os.Stat(sub); os.IsNotExist(err) {
continue
}
- ok = convertOne(sub) && ok
+ ok = convertOne(sub, loadStack) && ok
}
}
converted[mkFile] = ss
diff --git a/mk2rbc/expr.go b/mk2rbc/expr.go
index 9266520..6a6eb46 100644
--- a/mk2rbc/expr.go
+++ b/mk2rbc/expr.go
@@ -741,8 +741,8 @@
return starlarkTypeUnknown
}
-func (_ *badExpr) emitListVarCopy(_ *generationContext) {
- panic("implement me")
+func (b *badExpr) emitListVarCopy(gctx *generationContext) {
+ b.emit(gctx)
}
func (b *badExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
index 02b3d08..1041108 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -116,6 +116,7 @@
"subst": &substCallParser{fname: "subst"},
"warning": &makeControlFuncParser{name: baseName + ".mkwarning"},
"word": &wordCallParser{},
+ "words": &wordsCallParser{},
"wildcard": &simpleCallParser{name: baseName + ".expand_wildcard", returnType: starlarkTypeList},
}
@@ -130,6 +131,14 @@
"foreach": &foreachCallNodeParser{},
}
+// These look like variables, but are actually functions, and would give
+// undefined variable errors if we converted them as variables. Instead,
+// emit an error instead of converting them.
+var unsupportedFunctions = map[string]bool{
+ "local-generated-sources-dir": true,
+ "local-intermediates-dir": true,
+}
+
// 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{
@@ -562,9 +571,6 @@
if lhs.valueType() == starlarkTypeUnknown {
// Try to divine variable type from the RHS
asgn.value = ctx.parseMakeString(a, a.Value)
- if xBad, ok := asgn.value.(*badExpr); ok {
- return []starlarkNode{&exprNode{xBad}}
- }
inferred_type := asgn.value.typ()
if inferred_type != starlarkTypeUnknown {
lhs.setValueType(inferred_type)
@@ -573,21 +579,19 @@
if lhs.valueType() == starlarkTypeList {
xConcat, xBad := ctx.buildConcatExpr(a)
if xBad != nil {
- return []starlarkNode{&exprNode{expr: xBad}}
- }
- switch len(xConcat.items) {
- case 0:
- asgn.value = &listExpr{}
- case 1:
- asgn.value = xConcat.items[0]
- default:
- asgn.value = xConcat
+ asgn.value = xBad
+ } else {
+ switch len(xConcat.items) {
+ case 0:
+ asgn.value = &listExpr{}
+ case 1:
+ asgn.value = xConcat.items[0]
+ default:
+ asgn.value = xConcat
+ }
}
} else {
asgn.value = ctx.parseMakeString(a, a.Value)
- if xBad, ok := asgn.value.(*badExpr); ok {
- return []starlarkNode{&exprNode{expr: xBad}}
- }
}
if asgn.lhs.valueType() == starlarkTypeString &&
@@ -817,35 +821,40 @@
// rblf.inherit(handle, _e[0], _e[1])
//
var matchingPaths []string
- varPath, ok := pathExpr.(*interpolateExpr)
- if !ok {
+ var needsWarning = false
+ if interpolate, ok := pathExpr.(*interpolateExpr); ok {
+ pathPattern := []string{interpolate.chunks[0]}
+ for _, chunk := range interpolate.chunks[1:] {
+ if chunk != "" {
+ pathPattern = append(pathPattern, chunk)
+ }
+ }
+ if pathPattern[0] == "" && len(ctx.includeTops) > 0 {
+ // If pattern starts from the top. restrict it to the directories where
+ // we know inherit-product uses dynamically calculated path.
+ for _, p := range ctx.includeTops {
+ pathPattern[0] = p
+ matchingPaths = append(matchingPaths, ctx.findMatchingPaths(pathPattern)...)
+ }
+ } else {
+ matchingPaths = ctx.findMatchingPaths(pathPattern)
+ }
+ needsWarning = pathPattern[0] == "" && len(ctx.includeTops) == 0
+ } else if len(ctx.includeTops) > 0 {
+ for _, p := range ctx.includeTops {
+ matchingPaths = append(matchingPaths, ctx.findMatchingPaths([]string{p, ""})...)
+ }
+ } else {
return []starlarkNode{ctx.newBadNode(v, "inherit-product/include argument is too complex")}
}
- pathPattern := []string{varPath.chunks[0]}
- for _, chunk := range varPath.chunks[1:] {
- if chunk != "" {
- pathPattern = append(pathPattern, chunk)
- }
- }
- if pathPattern[0] == "" && len(ctx.includeTops) > 0 {
- // If pattern starts from the top. restrict it to the directories where
- // we know inherit-product uses dynamically calculated path.
- for _, p := range ctx.includeTops {
- pathPattern[0] = p
- matchingPaths = append(matchingPaths, ctx.findMatchingPaths(pathPattern)...)
- }
- } else {
- matchingPaths = ctx.findMatchingPaths(pathPattern)
- }
// Safeguard against $(call inherit-product,$(PRODUCT_PATH))
const maxMatchingFiles = 150
if len(matchingPaths) > maxMatchingFiles {
return []starlarkNode{ctx.newBadNode(v, "there are >%d files matching the pattern, please rewrite it", maxMatchingFiles)}
}
- needsWarning := pathPattern[0] == "" && len(ctx.includeTops) == 0
- res := inheritedDynamicModule{*varPath, []*moduleInfo{}, loadAlways, ctx.errorLocation(v), needsWarning}
+ res := inheritedDynamicModule{pathExpr, []*moduleInfo{}, loadAlways, ctx.errorLocation(v), needsWarning}
for _, p := range matchingPaths {
// A product configuration files discovered dynamically may attempt to inherit
// from another one which does not exist in this source tree. Prevent load errors
@@ -895,8 +904,9 @@
})
}
-func (ctx *parseContext) handleInclude(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) []starlarkNode {
- return ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) starlarkNode {
+func (ctx *parseContext) handleInclude(v *mkparser.Directive) []starlarkNode {
+ loadAlways := v.Name[0] != '-'
+ return ctx.handleSubConfig(v, ctx.parseMakeString(v, v.Args), loadAlways, func(im inheritedModule) starlarkNode {
return &includeNode{im, loadAlways}
})
}
@@ -1074,6 +1084,18 @@
return otherOperand
}
}
+ if otherOperand.typ() == starlarkTypeList {
+ fields := strings.Fields(stringOperand)
+ elements := make([]starlarkExpr, len(fields))
+ for i, s := range fields {
+ elements[i] = &stringLiteralExpr{literal: s}
+ }
+ return &eqExpr{
+ left: otherOperand,
+ right: &listExpr{elements},
+ isEq: isEq,
+ }
+ }
if intOperand, err := strconv.Atoi(strings.TrimSpace(stringOperand)); err == nil && otherOperand.typ() == starlarkTypeInt {
return &eqExpr{
left: otherOperand,
@@ -1119,8 +1141,6 @@
switch call.name {
case baseName + ".filter":
return ctx.parseCompareFilterFuncResult(directive, call, value, isEq)
- case baseName + ".expand_wildcard":
- return ctx.parseCompareWildcardFuncResult(directive, call, value, !isEq), true
case baseName + ".findstring":
return ctx.parseCheckFindstringFuncResult(directive, call, value, !isEq), true
case baseName + ".strip":
@@ -1165,22 +1185,6 @@
}
}
-func (ctx *parseContext) parseCompareWildcardFuncResult(directive *mkparser.Directive,
- xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
- if !isEmptyString(xValue) {
- return ctx.newBadExpr(directive, "wildcard result can be compared only to empty: %s", xValue)
- }
- callFunc := baseName + ".file_wildcard_exists"
- if s, ok := xCall.args[0].(*stringLiteralExpr); ok && !strings.ContainsAny(s.literal, "*?{[") {
- callFunc = baseName + ".file_exists"
- }
- var cc starlarkExpr = &callExpr{name: callFunc, args: xCall.args, returnType: starlarkTypeBool}
- if !negate {
- cc = ¬Expr{cc}
- }
- return cc
-}
-
func (ctx *parseContext) parseCheckFindstringFuncResult(directive *mkparser.Directive,
xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
if isEmptyString(xValue) {
@@ -1268,6 +1272,12 @@
return ctx.newBadExpr(node, "reference is too complex: %s", refDump)
}
+ if name, _, ok := ctx.maybeParseFunctionCall(node, ref); ok {
+ if _, unsupported := unsupportedFunctions[name]; unsupported {
+ return ctx.newBadExpr(node, "%s is not supported", refDump)
+ }
+ }
+
// 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" && refDump != "eval" {
@@ -1315,9 +1325,8 @@
} else {
return ctx.newBadExpr(node, "cannot handle invoking %s", name)
}
- } else {
- return ctx.newBadExpr(node, "cannot handle %s", refDump)
}
+ return ctx.newBadExpr(node, "cannot handle %s", refDump)
}
type simpleCallParser struct {
@@ -1593,6 +1602,16 @@
for _, n := range a.actions {
transformNode(n, transformer)
}
+ case *inheritNode:
+ if b, ok := a.module.(inheritedDynamicModule); ok {
+ b.path = b.path.transform(transformer)
+ a.module = b
+ }
+ case *includeNode:
+ if b, ok := a.module.(inheritedDynamicModule); ok {
+ b.path = b.path.transform(transformer)
+ a.module = b
+ }
}
}
@@ -1643,9 +1662,11 @@
if len(words) != 2 {
return ctx.newBadExpr(node, "word function should have 2 arguments")
}
- var index uint64 = 0
+ var index = 0
if words[0].Const() {
- index, _ = strconv.ParseUint(strings.TrimSpace(words[0].Strings[0]), 10, 64)
+ if i, err := strconv.Atoi(strings.TrimSpace(words[0].Strings[0])); err == nil {
+ index = i
+ }
}
if index < 1 {
return ctx.newBadExpr(node, "word index should be constant positive integer")
@@ -1653,13 +1674,40 @@
words[1].TrimLeftSpaces()
words[1].TrimRightSpaces()
array := ctx.parseMakeString(node, words[1])
- if xBad, ok := array.(*badExpr); ok {
- return xBad
+ if bad, ok := array.(*badExpr); ok {
+ return bad
}
if array.typ() != starlarkTypeList {
- array = &callExpr{object: array, name: "split", returnType: starlarkTypeList}
+ array = &callExpr{
+ name: baseName + ".words",
+ args: []starlarkExpr{array},
+ returnType: starlarkTypeList,
+ }
}
- return &indexExpr{array, &intLiteralExpr{int(index - 1)}}
+ return &indexExpr{array, &intLiteralExpr{index - 1}}
+}
+
+type wordsCallParser struct{}
+
+func (p *wordsCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+ args.TrimLeftSpaces()
+ args.TrimRightSpaces()
+ array := ctx.parseMakeString(node, args)
+ if bad, ok := array.(*badExpr); ok {
+ return bad
+ }
+ if array.typ() != starlarkTypeList {
+ array = &callExpr{
+ name: baseName + ".words",
+ args: []starlarkExpr{array},
+ returnType: starlarkTypeList,
+ }
+ }
+ return &callExpr{
+ name: "len",
+ args: []starlarkExpr{array},
+ returnType: starlarkTypeInt,
+ }
}
type firstOrLastwordCallParser struct {
@@ -1765,10 +1813,23 @@
}
case *mkparser.Comment:
return []starlarkNode{&commentNode{strings.TrimSpace("#" + n.Comment)}}
+ case *mkparser.Directive:
+ if n.Name == "include" || n.Name == "-include" {
+ return ctx.handleInclude(n)
+ }
+ case *mkparser.Variable:
+ // Technically inherit-product(-if-exists) don't need to be put inside
+ // an eval, but some makefiles do it, presumably because they copy+pasted
+ // from a $(eval include ...)
+ if name, _, ok := ctx.maybeParseFunctionCall(n, n.Name); ok {
+ if name == "inherit-product" || name == "inherit-product-if-exists" {
+ return ctx.handleVariable(n)
+ }
+ }
}
}
- return []starlarkNode{ctx.newBadNode(node, "Eval expression too complex; only assignments and comments are supported")}
+ return []starlarkNode{ctx.newBadNode(node, "Eval expression too complex; only assignments, comments, includes, and inherit-products are supported")}
}
func (ctx *parseContext) parseMakeString(node mkparser.Node, mk *mkparser.MakeString) starlarkExpr {
@@ -1828,7 +1889,7 @@
result = []starlarkNode{res}
}
case "include", "-include":
- result = ctx.handleInclude(node, ctx.parseMakeString(node, x.Args), x.Name[0] != '-')
+ result = ctx.handleInclude(x)
case "ifeq", "ifneq", "ifdef", "ifndef":
result = []starlarkNode{ctx.handleIfBlock(x)}
default:
diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go
index 9485a42..de2dc3c 100644
--- a/mk2rbc/mk2rbc_test.go
+++ b/mk2rbc/mk2rbc_test.go
@@ -117,8 +117,8 @@
def init(g, handle):
cfg = rblf.cfg(handle)
- rblf.mk2rbc_error("product.mk:2", "cannot handle invoking foo1")
- rblf.mk2rbc_error("product.mk:3", "cannot handle invoking foo0")
+ cfg["PRODUCT_NAME"] = rblf.mk2rbc_error("product.mk:2", "cannot handle invoking foo1")
+ cfg["PRODUCT_NAME"] = rblf.mk2rbc_error("product.mk:3", "cannot handle invoking foo0")
`,
},
{
@@ -568,14 +568,18 @@
endif
ifneq (,$(wildcard foo*.mk))
endif
+ifeq (foo1.mk foo2.mk barxyz.mk,$(wildcard foo*.mk bar*.mk))
+endif
`,
expected: `load("//build/make/core:product_config.rbc", "rblf")
def init(g, handle):
cfg = rblf.cfg(handle)
- if not rblf.file_exists("foo.mk"):
+ if not rblf.expand_wildcard("foo.mk"):
pass
- if rblf.file_wildcard_exists("foo*.mk"):
+ if rblf.expand_wildcard("foo*.mk"):
+ pass
+ if rblf.expand_wildcard("foo*.mk bar*.mk") == ["foo1.mk", "foo2.mk", "barxyz.mk"]:
pass
`,
},
@@ -808,6 +812,10 @@
PRODUCT_COPY_FILES := $(addprefix pfx-,a b c)
PRODUCT_COPY_FILES := $(addsuffix .sff, a b c)
PRODUCT_NAME := $(word 1, $(subst ., ,$(TARGET_BOARD_PLATFORM)))
+ifeq (1,$(words $(SOME_UNKNOWN_VARIABLE)))
+endif
+ifeq ($(words $(SOME_OTHER_VARIABLE)),$(SOME_INT_VARIABLE))
+endif
$(info $(patsubst %.pub,$(PRODUCT_NAME)%,$(PRODUCT_ADB_KEYS)))
$(info $$(dir foo/bar): $(dir foo/bar))
$(info $(firstword $(PRODUCT_COPY_FILES)))
@@ -830,7 +838,11 @@
cfg = rblf.cfg(handle)
cfg["PRODUCT_COPY_FILES"] = rblf.addprefix("pfx-", "a b c")
cfg["PRODUCT_COPY_FILES"] = rblf.addsuffix(".sff", "a b c")
- cfg["PRODUCT_NAME"] = ((g.get("TARGET_BOARD_PLATFORM", "")).replace(".", " ")).split()[0]
+ cfg["PRODUCT_NAME"] = rblf.words((g.get("TARGET_BOARD_PLATFORM", "")).replace(".", " "))[0]
+ if len(rblf.words(g.get("SOME_UNKNOWN_VARIABLE", ""))) == 1:
+ pass
+ if ("%d" % (len(rblf.words(g.get("SOME_OTHER_VARIABLE", ""))))) == g.get("SOME_INT_VARIABLE", ""):
+ pass
rblf.mkinfo("product.mk", rblf.mkpatsubst("%.pub", "%s%%" % cfg["PRODUCT_NAME"], g.get("PRODUCT_ADB_KEYS", "")))
rblf.mkinfo("product.mk", "$(dir foo/bar): %s" % rblf.dir("foo/bar"))
rblf.mkinfo("product.mk", cfg["PRODUCT_COPY_FILES"][0])
@@ -975,7 +987,7 @@
rblf.soong_config_namespace(g, "cvd")
rblf.soong_config_set(g, "cvd", "launch_configs", "cvd_config_auto.json")
rblf.soong_config_append(g, "cvd", "grub_config", "grub.cfg")
- rblf.mk2rbc_error("product.mk:7", "SOONG_CONFIG_ variables cannot be referenced, use soong_config_get instead: SOONG_CONFIG_cvd_grub_config")
+ _x = rblf.mk2rbc_error("product.mk:7", "SOONG_CONFIG_ variables cannot be referenced, use soong_config_get instead: SOONG_CONFIG_cvd_grub_config")
`,
}, {
desc: "soong namespace accesses",
@@ -1142,6 +1154,11 @@
MY_PATH:=foo
#RBC# include_top vendor/foo1
$(call inherit-product,$(MY_PATH)/cfg.mk)
+#RBC# include_top vendor/foo1
+$(call inherit-product,$(MY_OTHER_PATH))
+#RBC# include_top vendor/foo1
+$(foreach f,$(MY_MAKEFILES), \
+ $(call inherit-product,$(f)))
`,
expected: `load("//build/make/core:product_config.rbc", "rblf")
load("//vendor/foo1:cfg.star|init", _cfg_init = "init")
@@ -1156,6 +1173,21 @@
if not _varmod_init:
rblf.mkerror("product.mk", "Cannot find %s" % ("%s/cfg.mk" % g["MY_PATH"]))
rblf.inherit(handle, _varmod, _varmod_init)
+ _entry = {
+ "vendor/foo1/cfg.mk": ("vendor/foo1/cfg", _cfg_init),
+ }.get(g.get("MY_OTHER_PATH", ""))
+ (_varmod, _varmod_init) = _entry if _entry else (None, None)
+ if not _varmod_init:
+ rblf.mkerror("product.mk", "Cannot find %s" % (g.get("MY_OTHER_PATH", "")))
+ rblf.inherit(handle, _varmod, _varmod_init)
+ for f in rblf.words(g.get("MY_MAKEFILES", "")):
+ _entry = {
+ "vendor/foo1/cfg.mk": ("vendor/foo1/cfg", _cfg_init),
+ }.get(f)
+ (_varmod, _varmod_init) = _entry if _entry else (None, None)
+ if not _varmod_init:
+ rblf.mkerror("product.mk", "Cannot find %s" % (f))
+ rblf.inherit(handle, _varmod, _varmod_init)
`,
},
{
@@ -1271,6 +1303,7 @@
in: `
ifeq (,$(call foobar))
endif
+my_sources := $(local-generated-sources-dir)
`,
expected: `load("//build/make/core:product_config.rbc", "rblf")
@@ -1278,6 +1311,7 @@
cfg = rblf.cfg(handle)
if rblf.mk2rbc_error("build/product.mk:2", "cannot handle invoking foobar"):
pass
+ _my_sources = rblf.mk2rbc_error("build/product.mk:4", "local-generated-sources-dir is not supported")
`,
},
{
@@ -1511,24 +1545,40 @@
$(eval MY_VAR := foo)
$(eval # This is a test of eval functions)
$(eval $(TOO_COMPLICATED) := bar)
+$(eval include foo/font.mk)
+$(eval $(call inherit-product,vendor/foo1/cfg.mk))
+
$(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))) \
-)
+ $(if $(MY_OTHER_VAR),$(eval PRODUCT_COPY_FILES += $(MY_OTHER_VAR):foo/bar/$(x))))
+$(foreach x,$(MY_LIST_VAR), \
+ $(eval include foo/$(x).mk))
`,
expected: `load("//build/make/core:product_config.rbc", "rblf")
+load("//foo:font.star", _font_init = "init")
+load("//vendor/foo1:cfg.star", _cfg_init = "init")
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")
+ rblf.mk2rbc_error("product.mk:5", "Eval expression too complex; only assignments, comments, includes, and inherit-products are supported")
+ _font_init(g, handle)
+ rblf.inherit(handle, "vendor/foo1/cfg", _cfg_init)
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()
+ for x in rblf.words(g.get("MY_LIST_VAR", "")):
+ _entry = {
+ "foo/font.mk": ("foo/font", _font_init),
+ }.get("foo/%s.mk" % _x)
+ (_varmod, _varmod_init) = _entry if _entry else (None, None)
+ if not _varmod_init:
+ rblf.mkerror("product.mk", "Cannot find %s" % ("foo/%s.mk" % _x))
+ _varmod_init(g, handle)
`,
},
{
diff --git a/mk2rbc/node.go b/mk2rbc/node.go
index 7c39b9e..a01abd8 100644
--- a/mk2rbc/node.go
+++ b/mk2rbc/node.go
@@ -83,7 +83,7 @@
}
type inheritedDynamicModule struct {
- path interpolateExpr
+ path starlarkExpr
candidateModules []*moduleInfo
loadAlways bool
location ErrorLocation
@@ -120,7 +120,7 @@
}
func (i inheritedDynamicModule) pathExpr() starlarkExpr {
- return &i.path
+ return i.path
}
func (i inheritedDynamicModule) needsLoadCheck() bool {
diff --git a/multitree/Android.bp b/multitree/Android.bp
new file mode 100644
index 0000000..9b16d20
--- /dev/null
+++ b/multitree/Android.bp
@@ -0,0 +1,19 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+ name: "soong-multitree",
+ pkgPath: "android/soong/multitree",
+ deps: [
+ "blueprint",
+ "soong-android",
+ ],
+ srcs: [
+ "api_surface.go",
+ "export.go",
+ "metadata.go",
+ "import.go",
+ ],
+ pluginFor: ["soong_build"],
+}
diff --git a/multitree/api_surface.go b/multitree/api_surface.go
new file mode 100644
index 0000000..f739a24
--- /dev/null
+++ b/multitree/api_surface.go
@@ -0,0 +1,119 @@
+// Copyright 2021 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 multitree
+
+import (
+ "android/soong/android"
+ "fmt"
+
+ "github.com/google/blueprint"
+)
+
+var (
+ pctx = android.NewPackageContext("android/soong/multitree")
+)
+
+func init() {
+ RegisterApiSurfaceBuildComponents(android.InitRegistrationContext)
+}
+
+var PrepareForTestWithApiSurface = android.FixtureRegisterWithContext(RegisterApiSurfaceBuildComponents)
+
+func RegisterApiSurfaceBuildComponents(ctx android.RegistrationContext) {
+ ctx.RegisterModuleType("api_surface", ApiSurfaceFactory)
+}
+
+type ApiSurface struct {
+ android.ModuleBase
+ ExportableModuleBase
+ properties apiSurfaceProperties
+
+ allOutputs android.Paths
+ taggedOutputs map[string]android.Paths
+}
+
+type apiSurfaceProperties struct {
+ Contributions []string
+}
+
+func ApiSurfaceFactory() android.Module {
+ module := &ApiSurface{}
+ module.AddProperties(&module.properties)
+ android.InitAndroidModule(module)
+ InitExportableModule(module)
+ return module
+}
+
+func (surface *ApiSurface) DepsMutator(ctx android.BottomUpMutatorContext) {
+ if surface.properties.Contributions != nil {
+ ctx.AddVariationDependencies(nil, nil, surface.properties.Contributions...)
+ }
+
+}
+func (surface *ApiSurface) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ contributionFiles := make(map[string]android.Paths)
+ var allOutputs android.Paths
+ ctx.WalkDeps(func(child, parent android.Module) bool {
+ if contribution, ok := child.(ApiContribution); ok {
+ copied := contribution.CopyFilesWithTag(ctx)
+ for tag, files := range copied {
+ contributionFiles[child.Name()+"#"+tag] = files
+ }
+ for _, paths := range copied {
+ allOutputs = append(allOutputs, paths...)
+ }
+ return false // no transitive dependencies
+ }
+ return false
+ })
+
+ // phony target
+ ctx.Build(pctx, android.BuildParams{
+ Rule: blueprint.Phony,
+ Output: android.PathForPhony(ctx, ctx.ModuleName()),
+ Inputs: allOutputs,
+ })
+
+ surface.allOutputs = allOutputs
+ surface.taggedOutputs = contributionFiles
+}
+
+func (surface *ApiSurface) OutputFiles(tag string) (android.Paths, error) {
+ if tag != "" {
+ return nil, fmt.Errorf("unknown tag: %q", tag)
+ }
+ return surface.allOutputs, nil
+}
+
+func (surface *ApiSurface) TaggedOutputs() map[string]android.Paths {
+ return surface.taggedOutputs
+}
+
+func (surface *ApiSurface) Exportable() bool {
+ return true
+}
+
+var _ android.OutputFileProducer = (*ApiSurface)(nil)
+var _ Exportable = (*ApiSurface)(nil)
+
+type ApiContribution interface {
+ // copy files necessaryt to construct an API surface
+ // For C, it will be map.txt and .h files
+ // For Java, it will be api.txt
+ CopyFilesWithTag(ctx android.ModuleContext) map[string]android.Paths // output paths
+
+ // Generate Android.bp in out/ to use the exported .txt files
+ // GenerateBuildFiles(ctx ModuleContext) Paths //output paths
+}
diff --git a/multitree/export.go b/multitree/export.go
new file mode 100644
index 0000000..aecade5
--- /dev/null
+++ b/multitree/export.go
@@ -0,0 +1,67 @@
+// 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 multitree
+
+import (
+ "android/soong/android"
+
+ "github.com/google/blueprint/proptools"
+)
+
+type moduleExportProperty struct {
+ // True if the module is exported to the other components in a multi-tree.
+ // Any components in the multi-tree can import this module to use.
+ Export *bool
+}
+
+type ExportableModuleBase struct {
+ properties moduleExportProperty
+}
+
+type Exportable interface {
+ // Properties for the exporable module.
+ exportableModuleProps() *moduleExportProperty
+
+ // Check if this module can be exported.
+ // If this returns false, the module will not be exported regardless of the 'export' value.
+ Exportable() bool
+
+ // Returns 'true' if this module has 'export: true'
+ // This module will not be exported if it returns 'false' to 'Exportable()' interface even if
+ // it has 'export: true'.
+ IsExported() bool
+
+ // Map from tags to outputs.
+ // Each module can tag their outputs for convenience.
+ TaggedOutputs() map[string]android.Paths
+}
+
+type ExportableModule interface {
+ android.Module
+ android.OutputFileProducer
+ Exportable
+}
+
+func InitExportableModule(module ExportableModule) {
+ module.AddProperties(module.exportableModuleProps())
+}
+
+func (m *ExportableModuleBase) exportableModuleProps() *moduleExportProperty {
+ return &m.properties
+}
+
+func (m *ExportableModuleBase) IsExported() bool {
+ return proptools.Bool(m.properties.Export)
+}
diff --git a/multitree/import.go b/multitree/import.go
new file mode 100644
index 0000000..1e5c421
--- /dev/null
+++ b/multitree/import.go
@@ -0,0 +1,96 @@
+// 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 multitree
+
+import (
+ "android/soong/android"
+)
+
+var (
+ nameSuffix = ".imported"
+)
+
+type MultitreeImportedModuleInterface interface {
+ GetMultitreeImportedModuleName() string
+}
+
+func init() {
+ android.RegisterModuleType("imported_filegroup", importedFileGroupFactory)
+
+ android.PreArchMutators(RegisterMultitreePreArchMutators)
+}
+
+type importedFileGroupProperties struct {
+ // Imported modules from the other components in a multi-tree
+ Imported []string
+}
+
+type importedFileGroup struct {
+ android.ModuleBase
+
+ properties importedFileGroupProperties
+ srcs android.Paths
+}
+
+func (ifg *importedFileGroup) Name() string {
+ return ifg.BaseModuleName() + nameSuffix
+}
+
+func importedFileGroupFactory() android.Module {
+ module := &importedFileGroup{}
+ module.AddProperties(&module.properties)
+
+ android.InitAndroidModule(module)
+ return module
+}
+
+var _ MultitreeImportedModuleInterface = (*importedFileGroup)(nil)
+
+func (ifg *importedFileGroup) GetMultitreeImportedModuleName() string {
+ // The base module name of the imported filegroup is used as the imported module name
+ return ifg.BaseModuleName()
+}
+
+var _ android.SourceFileProducer = (*importedFileGroup)(nil)
+
+func (ifg *importedFileGroup) Srcs() android.Paths {
+ return ifg.srcs
+}
+
+func (ifg *importedFileGroup) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ // srcs from this module must not be used. Adding a dot path to avoid the empty
+ // source failure. Still soong returns error when a module wants to build against
+ // this source, which is intended.
+ ifg.srcs = android.PathsForModuleSrc(ctx, []string{"."})
+}
+
+func RegisterMultitreePreArchMutators(ctx android.RegisterMutatorsContext) {
+ ctx.BottomUp("multitree_imported_rename", MultitreeImportedRenameMutator).Parallel()
+}
+
+func MultitreeImportedRenameMutator(ctx android.BottomUpMutatorContext) {
+ if m, ok := ctx.Module().(MultitreeImportedModuleInterface); ok {
+ name := m.GetMultitreeImportedModuleName()
+ if !ctx.OtherModuleExists(name) {
+ // Provide an empty filegroup not to break the build while updating the metadata.
+ // In other cases, soong will report an error to guide users to run 'm update-meta'
+ // first.
+ if !ctx.Config().TargetMultitreeUpdateMeta() {
+ ctx.ModuleErrorf("\"%s\" filegroup must be imported.\nRun 'm update-meta' first to import the filegroup.", name)
+ }
+ ctx.Rename(name)
+ }
+ }
+}
diff --git a/multitree/metadata.go b/multitree/metadata.go
new file mode 100644
index 0000000..3fd7215
--- /dev/null
+++ b/multitree/metadata.go
@@ -0,0 +1,74 @@
+// 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 multitree
+
+import (
+ "android/soong/android"
+ "encoding/json"
+)
+
+func init() {
+ android.RegisterSingletonType("update-meta", UpdateMetaSingleton)
+}
+
+func UpdateMetaSingleton() android.Singleton {
+ return &updateMetaSingleton{}
+}
+
+type jsonImported struct {
+ FileGroups map[string][]string `json:",omitempty"`
+}
+
+type metadataJsonFlags struct {
+ Imported jsonImported `json:",omitempty"`
+ Exported map[string][]string `json:",omitempty"`
+}
+
+type updateMetaSingleton struct {
+ importedModules []string
+ generatedMetadataFile android.OutputPath
+}
+
+func (s *updateMetaSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+ metadata := metadataJsonFlags{
+ Imported: jsonImported{
+ FileGroups: make(map[string][]string),
+ },
+ Exported: make(map[string][]string),
+ }
+ ctx.VisitAllModules(func(module android.Module) {
+ if ifg, ok := module.(*importedFileGroup); ok {
+ metadata.Imported.FileGroups[ifg.BaseModuleName()] = ifg.properties.Imported
+ }
+ if e, ok := module.(ExportableModule); ok {
+ if e.IsExported() && e.Exportable() {
+ for tag, files := range e.TaggedOutputs() {
+ // TODO(b/219846705): refactor this to a dictionary
+ metadata.Exported[e.Name()+":"+tag] = append(metadata.Exported[e.Name()+":"+tag], files.Strings()...)
+ }
+ }
+ }
+ })
+ jsonStr, err := json.Marshal(metadata)
+ if err != nil {
+ ctx.Errorf(err.Error())
+ }
+ s.generatedMetadataFile = android.PathForOutput(ctx, "multitree", "metadata.json")
+ android.WriteFileRule(ctx, s.generatedMetadataFile, string(jsonStr))
+}
+
+func (s *updateMetaSingleton) MakeVars(ctx android.MakeVarsContext) {
+ ctx.Strict("MULTITREE_METADATA", s.generatedMetadataFile.String())
+}
diff --git a/rust/test.go b/rust/test.go
index 250b765..6e53935 100644
--- a/rust/test.go
+++ b/rust/test.go
@@ -15,6 +15,8 @@
package rust
import (
+ "path/filepath"
+
"github.com/google/blueprint/proptools"
"android/soong/android"
@@ -151,9 +153,15 @@
ctx.ModuleErrorf("data_lib %q is not a linkable module", depName)
}
if linkableDep.OutputFile().Valid() {
+ // Copy the output in "lib[64]" so that it's compatible with
+ // the default rpath values.
+ libDir := "lib"
+ if linkableDep.Target().Arch.ArchType.Multilib == "lib64" {
+ libDir = "lib64"
+ }
test.data = append(test.data,
android.DataPath{SrcPath: linkableDep.OutputFile().Path(),
- RelativeInstallPath: linkableDep.RelativeInstallPath()})
+ RelativeInstallPath: filepath.Join(libDir, linkableDep.RelativeInstallPath())})
}
})
diff --git a/rust/test_test.go b/rust/test_test.go
index 1124176..8906f1c 100644
--- a/rust/test_test.go
+++ b/rust/test_test.go
@@ -187,12 +187,12 @@
t.Errorf("expected test output file to be 'main_test', but was '%s'", outputPath)
}
entries := android.AndroidMkEntriesForTest(t, ctx, module)[0]
- if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][0], ":test_lib.so:foo/bar/baz") {
- t.Errorf("expected LOCAL_TEST_DATA to end with `:test_lib.so:foo/bar/baz`,"+
+ if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][0], ":test_lib.so:lib64/foo/bar/baz") {
+ t.Errorf("expected LOCAL_TEST_DATA to end with `:test_lib.so:lib64/foo/bar/baz`,"+
" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][0])
}
- if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][1], ":librust_test_lib.so:foo/bar/baz") {
- t.Errorf("expected LOCAL_TEST_DATA to end with `:librust_test_lib.so:foo/bar/baz`,"+
+ if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][1], ":librust_test_lib.so:lib64/foo/bar/baz") {
+ t.Errorf("expected LOCAL_TEST_DATA to end with `:librust_test_lib.so:lib64/foo/bar/baz`,"+
" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][1])
}
if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][2], ":rusty:foo/bar/baz") {
diff --git a/ui/build/config.go b/ui/build/config.go
index e271bfc..0092ff1 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -1223,6 +1223,21 @@
return "RBE_use_application_default_credentials", "true"
}
+func (c *configImpl) IsGooglerEnvironment() bool {
+ cf := "ANDROID_BUILD_ENVIRONMENT_CONFIG"
+ if v, ok := c.environ.Get(cf); ok {
+ return v == "googler"
+ }
+ return false
+}
+
+func (c *configImpl) GoogleProdCredsExist() bool {
+ if _, err := exec.Command("/usr/bin/prodcertstatus", "--simple_output", "--nocheck_loas").Output(); err != nil {
+ return false
+ }
+ return true
+}
+
func (c *configImpl) UseRemoteBuild() bool {
return c.UseGoma() || c.UseRBE()
}
diff --git a/ui/build/rbe.go b/ui/build/rbe.go
index 8f9a699..78d37b4 100644
--- a/ui/build/rbe.go
+++ b/ui/build/rbe.go
@@ -119,6 +119,7 @@
}
func stopRBE(ctx Context, config Config) {
+ defer checkProdCreds(ctx, config)
cmd := Command(ctx, config, "stopRBE bootstrap", rbeCommand(ctx, config, bootstrapCmd), "-shutdown")
output, err := cmd.CombinedOutput()
if err != nil {
@@ -131,6 +132,15 @@
}
}
+func checkProdCreds(ctx Context, config Config) {
+ if !config.IsGooglerEnvironment() || config.GoogleProdCredsExist() {
+ return
+ }
+ fmt.Fprintln(ctx.Writer, "")
+ fmt.Fprintln(ctx.Writer, "\033[33mWARNING: Missing LOAS credentials, please run `gcert`. This will result in failing RBE builds in the future, see go/build-fast#authentication.\033[0m")
+ fmt.Fprintln(ctx.Writer, "")
+}
+
// DumpRBEMetrics creates a metrics protobuf file containing RBE related metrics.
// The protobuf file is created if RBE is enabled and the proxy service has
// started. The proxy service is shutdown in order to dump the RBE metrics to the