From de116856fbf6a8d28c5e1febbf7f4c94aa1b61a4 Mon Sep 17 00:00:00 2001 From: Liz Kammer Date: Thu, 25 Mar 2021 16:42:37 -0400 Subject: [PATCH] Add depfile handling for bazel_handler. Test: go test soong tests Test: TODO mixed build change header, mixed build Change-Id: I7c51faf2d5b1a8717cbab6bb0b3eb75c307fcd85 --- android/bazel_handler.go | 4 ++ bazel/aquery.go | 16 +++++- bazel/aquery_test.go | 107 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 123 insertions(+), 4 deletions(-) diff --git a/android/bazel_handler.go b/android/bazel_handler.go index 0595d68a1..74178a244 100644 --- a/android/bazel_handler.go +++ b/android/bazel_handler.go @@ -746,6 +746,10 @@ func (c *bazelSingleton) GenerateBuildActions(ctx SingletonContext) { cmd.Implicit(PathForBazelOut(ctx, inputPath)) } + if depfile := buildStatement.Depfile; depfile != nil { + cmd.ImplicitDepFile(PathForBazelOut(ctx, *depfile)) + } + // This is required to silence warnings pertaining to unexpected timestamps. Particularly, // some Bazel builtins (such as files in the bazel_tools directory) have far-future // timestamps. Without restat, Ninja would emit warnings that the input files of a diff --git a/bazel/aquery.go b/bazel/aquery.go index c82b464ad..555f1dcb4 100644 --- a/bazel/aquery.go +++ b/bazel/aquery.go @@ -74,6 +74,7 @@ type actionGraphContainer struct { // with a Bazel action from Bazel's action graph. type BuildStatement struct { Command string + Depfile *string OutputPaths []string InputPaths []string Env []KeyValuePair @@ -133,12 +134,22 @@ func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) { continue } outputPaths := []string{} + var depfile *string for _, outputId := range actionEntry.OutputIds { outputPath, exists := artifactIdToPath[outputId] if !exists { return nil, fmt.Errorf("undefined outputId %d", outputId) } - outputPaths = append(outputPaths, outputPath) + 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 := []string{} for _, inputDepSetId := range actionEntry.InputDepSetIds { @@ -161,12 +172,13 @@ func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) { } buildStatement := BuildStatement{ Command: strings.Join(proptools.ShellEscapeList(actionEntry.Arguments), " "), + Depfile: depfile, OutputPaths: outputPaths, InputPaths: inputPaths, Env: actionEntry.EnvironmentVariables, Mnemonic: actionEntry.Mnemonic} if len(actionEntry.Arguments) < 1 { - return nil, fmt.Errorf("received action with no command: [%s]", buildStatement) + return nil, fmt.Errorf("received action with no command: [%v]", buildStatement) continue } buildStatements = append(buildStatements, buildStatement) diff --git a/bazel/aquery_test.go b/bazel/aquery_test.go index a48e0834a..fa8810f0d 100644 --- a/bazel/aquery_test.go +++ b/bazel/aquery_test.go @@ -393,6 +393,109 @@ func TestInvalidPathFragmentId(t *testing.T) { assertError(t, err, "undefined path fragment id 3") } +func TestDepfiles(t *testing.T) { + const inputString = ` +{ + "artifacts": [{ + "id": 1, + "pathFragmentId": 1 + }, { + "id": 2, + "pathFragmentId": 2 + }, { + "id": 3, + "pathFragmentId": 3 + }], + "actions": [{ + "targetId": 1, + "actionKey": "x", + "mnemonic": "x", + "arguments": ["touch", "foo"], + "inputDepSetIds": [1], + "outputIds": [2, 3], + "primaryOutputId": 2 + }], + "depSetOfFiles": [{ + "id": 1, + "directArtifactIds": [1, 2, 3] + }], + "pathFragments": [{ + "id": 1, + "label": "one" + }, { + "id": 2, + "label": "two" + }, { + "id": 3, + "label": "two.d" + }] +}` + + actual, 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)) + } + + bs := actual[0] + expectedDepfile := "two.d" + if bs.Depfile == nil { + t.Errorf("Expected depfile %q, but there was none found", expectedDepfile) + } else if *bs.Depfile != expectedDepfile { + t.Errorf("Expected depfile %q, but got %q", expectedDepfile, *bs.Depfile) + } +} + +func TestMultipleDepfiles(t *testing.T) { + const inputString = ` +{ + "artifacts": [{ + "id": 1, + "pathFragmentId": 1 + }, { + "id": 2, + "pathFragmentId": 2 + }, { + "id": 3, + "pathFragmentId": 3 + }, { + "id": 4, + "pathFragmentId": 4 + }], + "actions": [{ + "targetId": 1, + "actionKey": "x", + "mnemonic": "x", + "arguments": ["touch", "foo"], + "inputDepSetIds": [1], + "outputIds": [2,3,4], + "primaryOutputId": 2 + }], + "depSetOfFiles": [{ + "id": 1, + "directArtifactIds": [1, 2, 3, 4] + }], + "pathFragments": [{ + "id": 1, + "label": "one" + }, { + "id": 2, + "label": "two" + }, { + "id": 3, + "label": "two.d" + }, { + "id": 4, + "label": "other.d" + }] +}` + + _, err := AqueryBuildStatements([]byte(inputString)) + assertError(t, err, `found multiple potential depfiles "two.d", "other.d"`) +} + func TestTransitiveInputDepsets(t *testing.T) { // The input aquery for this test comes from a proof-of-concept starlark rule which registers // a single action with many inputs given via a deep depset. @@ -627,7 +730,7 @@ func assertError(t *testing.T, err error, expected string) { // Build statement equivalence is determined using buildStatementEquals. func assertBuildStatements(t *testing.T, expected []BuildStatement, actual []BuildStatement) { if len(expected) != len(actual) { - t.Errorf("expected %d build statements, but got %d,\n expected: %s,\n actual: %s", + t.Errorf("expected %d build statements, but got %d,\n expected: %v,\n actual: %v", len(expected), len(actual), expected, actual) return } @@ -638,7 +741,7 @@ ACTUAL_LOOP: continue ACTUAL_LOOP } } - t.Errorf("unexpected build statement %s.\n expected: %s", + t.Errorf("unexpected build statement %v.\n expected: %v", actualStatement, expected) return }