Loading android/bazel_handler.go +4 −0 Original line number Diff line number Diff line Loading @@ -756,6 +756,10 @@ func (c *bazelSingleton) GenerateBuildActions(ctx SingletonContext) { cmd.ImplicitDepFile(PathForBazelOut(ctx, *depfile)) } for _, symlinkPath := range buildStatement.SymlinkPaths { cmd.ImplicitSymlinkOutput(PathForBazelOut(ctx, symlinkPath)) } // 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 Loading bazel/aquery.go +30 −11 Original line number Diff line number Diff line Loading @@ -77,6 +77,7 @@ type BuildStatement struct { Depfile *string OutputPaths []string InputPaths []string SymlinkPaths []string Env []KeyValuePair Mnemonic string } Loading Loading @@ -234,10 +235,21 @@ func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) { OutputPaths: outputPaths, InputPaths: inputPaths, Env: actionEntry.EnvironmentVariables, Mnemonic: actionEntry.Mnemonic} if len(actionEntry.Arguments) < 1 { Mnemonic: actionEntry.Mnemonic, } 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 := proptools.ShellEscapeIncludingSpaces(inputPaths[0]) buildStatement.Command = fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -rsf %[3]s %[2]s", outDir, out, in) buildStatement.SymlinkPaths = outputPaths[:] } else if len(actionEntry.Arguments) < 1 { return nil, fmt.Errorf("received action with no command: [%v]", buildStatement) continue } buildStatements = append(buildStatements, buildStatement) } Loading @@ -245,9 +257,13 @@ func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) { return buildStatements, nil } func isSymlinkAction(a action) bool { return a.Mnemonic == "Symlink" || a.Mnemonic == "SolibSymlink" } func shouldSkipAction(a action) bool { // TODO(b/180945121): Handle symlink actions. if a.Mnemonic == "Symlink" || a.Mnemonic == "SourceSymlinkManifest" || a.Mnemonic == "SymlinkTree" || a.Mnemonic == "SolibSymlink" { // TODO(b/180945121): Handle complex symlink actions. if a.Mnemonic == "SymlinkTree" || a.Mnemonic == "SourceSymlinkManifest" { return true } // Middleman actions are not handled like other actions; they are handled separately as a Loading Loading @@ -278,6 +294,9 @@ func expandPathFragment(id int, pathFragmentsMap map[int]pathFragment) (string, return "", fmt.Errorf("undefined path fragment id %d", currId) } labels = append([]string{currFragment.Label}, labels...) if currId == currFragment.ParentId { return "", fmt.Errorf("Fragment cannot refer to itself as parent %#v", currFragment) } currId = currFragment.ParentId } return filepath.Join(labels...), nil Loading bazel/aquery_test.go +219 −1 Original line number Diff line number Diff line Loading @@ -805,17 +805,229 @@ func TestMiddlemenAction(t *testing.T) { } } func TestSimpleSymlink(t *testing.T) { const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 3 }, { "id": 2, "pathFragmentId": 5 }], "actions": [{ "targetId": 1, "actionKey": "x", "mnemonic": "Symlink", "inputDepSetIds": [1], "outputIds": [2], "primaryOutputId": 2 }], "depSetOfFiles": [{ "id": 1, "directArtifactIds": [1] }], "pathFragments": [{ "id": 1, "label": "one" }, { "id": 2, "label": "file_subdir", "parentId": 1 }, { "id": 3, "label": "file", "parentId": 2 }, { "id": 4, "label": "symlink_subdir", "parentId": 1 }, { "id": 5, "label": "symlink", "parentId": 4 }] }` actual, err := AqueryBuildStatements([]byte(inputString)) if err != nil { t.Errorf("Unexpected error %q", err) } expectedBuildStatements := []BuildStatement{ BuildStatement{ Command: "mkdir -p one/symlink_subdir && " + "rm -f one/symlink_subdir/symlink && " + "ln -rsf one/file_subdir/file one/symlink_subdir/symlink", InputPaths: []string{"one/file_subdir/file"}, OutputPaths: []string{"one/symlink_subdir/symlink"}, SymlinkPaths: []string{"one/symlink_subdir/symlink"}, Mnemonic: "Symlink", }, } assertBuildStatements(t, actual, expectedBuildStatements) } func TestSymlinkQuotesPaths(t *testing.T) { const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 3 }, { "id": 2, "pathFragmentId": 5 }], "actions": [{ "targetId": 1, "actionKey": "x", "mnemonic": "SolibSymlink", "inputDepSetIds": [1], "outputIds": [2], "primaryOutputId": 2 }], "depSetOfFiles": [{ "id": 1, "directArtifactIds": [1] }], "pathFragments": [{ "id": 1, "label": "one" }, { "id": 2, "label": "file subdir", "parentId": 1 }, { "id": 3, "label": "file", "parentId": 2 }, { "id": 4, "label": "symlink subdir", "parentId": 1 }, { "id": 5, "label": "symlink", "parentId": 4 }] }` actual, err := AqueryBuildStatements([]byte(inputString)) if err != nil { t.Errorf("Unexpected error %q", err) } expectedBuildStatements := []BuildStatement{ BuildStatement{ Command: "mkdir -p 'one/symlink subdir' && " + "rm -f 'one/symlink subdir/symlink' && " + "ln -rsf 'one/file subdir/file' 'one/symlink subdir/symlink'", InputPaths: []string{"one/file subdir/file"}, OutputPaths: []string{"one/symlink subdir/symlink"}, SymlinkPaths: []string{"one/symlink subdir/symlink"}, Mnemonic: "SolibSymlink", }, } assertBuildStatements(t, actual, expectedBuildStatements) } func TestSymlinkMultipleInputs(t *testing.T) { const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 1 }, { "id": 2, "pathFragmentId": 2 }, { "id": 3, "pathFragmentId": 3 }], "actions": [{ "targetId": 1, "actionKey": "x", "mnemonic": "Symlink", "inputDepSetIds": [1], "outputIds": [3], "primaryOutputId": 3 }], "depSetOfFiles": [{ "id": 1, "directArtifactIds": [1,2] }], "pathFragments": [{ "id": 1, "label": "file" }, { "id": 2, "label": "other_file" }, { "id": 3, "label": "symlink" }] }` _, err := AqueryBuildStatements([]byte(inputString)) assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file" "other_file"], output ["symlink"]`) } func TestSymlinkMultipleOutputs(t *testing.T) { const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 1 }, { "id": 2, "pathFragmentId": 2 }, { "id": 3, "pathFragmentId": 3 }], "actions": [{ "targetId": 1, "actionKey": "x", "mnemonic": "Symlink", "inputDepSetIds": [1], "outputIds": [2,3], "primaryOutputId": 2 }], "depSetOfFiles": [{ "id": 1, "directArtifactIds": [1] }], "pathFragments": [{ "id": 1, "label": "file" }, { "id": 2, "label": "symlink" }, { "id": 3, "label": "other_symlink" }] }` _, err := AqueryBuildStatements([]byte(inputString)) assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file"], output ["symlink" "other_symlink"]`) } func assertError(t *testing.T, err error, expected string) { t.Helper() if err == nil { t.Errorf("expected error '%s', but got no error", expected) } else if err.Error() != expected { t.Errorf("expected error '%s', but got: %s", expected, err.Error()) t.Errorf("expected error:\n\t'%s', but got:\n\t'%s'", expected, err.Error()) } } // Asserts that the given actual build statements match the given expected build statements. // Build statement equivalence is determined using buildStatementEquals. func assertBuildStatements(t *testing.T, expected []BuildStatement, actual []BuildStatement) { t.Helper() if len(expected) != len(actual) { t.Errorf("expected %d build statements, but got %d,\n expected: %v,\n actual: %v", len(expected), len(actual), expected, actual) Loading Loading @@ -852,6 +1064,12 @@ func buildStatementEquals(first BuildStatement, second BuildStatement) bool { if !reflect.DeepEqual(stringSet(first.OutputPaths), stringSet(second.OutputPaths)) { return false } if !reflect.DeepEqual(stringSet(first.SymlinkPaths), stringSet(second.SymlinkPaths)) { return false } if first.Depfile != second.Depfile { return false } return true } Loading Loading
android/bazel_handler.go +4 −0 Original line number Diff line number Diff line Loading @@ -756,6 +756,10 @@ func (c *bazelSingleton) GenerateBuildActions(ctx SingletonContext) { cmd.ImplicitDepFile(PathForBazelOut(ctx, *depfile)) } for _, symlinkPath := range buildStatement.SymlinkPaths { cmd.ImplicitSymlinkOutput(PathForBazelOut(ctx, symlinkPath)) } // 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 Loading
bazel/aquery.go +30 −11 Original line number Diff line number Diff line Loading @@ -77,6 +77,7 @@ type BuildStatement struct { Depfile *string OutputPaths []string InputPaths []string SymlinkPaths []string Env []KeyValuePair Mnemonic string } Loading Loading @@ -234,10 +235,21 @@ func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) { OutputPaths: outputPaths, InputPaths: inputPaths, Env: actionEntry.EnvironmentVariables, Mnemonic: actionEntry.Mnemonic} if len(actionEntry.Arguments) < 1 { Mnemonic: actionEntry.Mnemonic, } 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 := proptools.ShellEscapeIncludingSpaces(inputPaths[0]) buildStatement.Command = fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -rsf %[3]s %[2]s", outDir, out, in) buildStatement.SymlinkPaths = outputPaths[:] } else if len(actionEntry.Arguments) < 1 { return nil, fmt.Errorf("received action with no command: [%v]", buildStatement) continue } buildStatements = append(buildStatements, buildStatement) } Loading @@ -245,9 +257,13 @@ func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) { return buildStatements, nil } func isSymlinkAction(a action) bool { return a.Mnemonic == "Symlink" || a.Mnemonic == "SolibSymlink" } func shouldSkipAction(a action) bool { // TODO(b/180945121): Handle symlink actions. if a.Mnemonic == "Symlink" || a.Mnemonic == "SourceSymlinkManifest" || a.Mnemonic == "SymlinkTree" || a.Mnemonic == "SolibSymlink" { // TODO(b/180945121): Handle complex symlink actions. if a.Mnemonic == "SymlinkTree" || a.Mnemonic == "SourceSymlinkManifest" { return true } // Middleman actions are not handled like other actions; they are handled separately as a Loading Loading @@ -278,6 +294,9 @@ func expandPathFragment(id int, pathFragmentsMap map[int]pathFragment) (string, return "", fmt.Errorf("undefined path fragment id %d", currId) } labels = append([]string{currFragment.Label}, labels...) if currId == currFragment.ParentId { return "", fmt.Errorf("Fragment cannot refer to itself as parent %#v", currFragment) } currId = currFragment.ParentId } return filepath.Join(labels...), nil Loading
bazel/aquery_test.go +219 −1 Original line number Diff line number Diff line Loading @@ -805,17 +805,229 @@ func TestMiddlemenAction(t *testing.T) { } } func TestSimpleSymlink(t *testing.T) { const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 3 }, { "id": 2, "pathFragmentId": 5 }], "actions": [{ "targetId": 1, "actionKey": "x", "mnemonic": "Symlink", "inputDepSetIds": [1], "outputIds": [2], "primaryOutputId": 2 }], "depSetOfFiles": [{ "id": 1, "directArtifactIds": [1] }], "pathFragments": [{ "id": 1, "label": "one" }, { "id": 2, "label": "file_subdir", "parentId": 1 }, { "id": 3, "label": "file", "parentId": 2 }, { "id": 4, "label": "symlink_subdir", "parentId": 1 }, { "id": 5, "label": "symlink", "parentId": 4 }] }` actual, err := AqueryBuildStatements([]byte(inputString)) if err != nil { t.Errorf("Unexpected error %q", err) } expectedBuildStatements := []BuildStatement{ BuildStatement{ Command: "mkdir -p one/symlink_subdir && " + "rm -f one/symlink_subdir/symlink && " + "ln -rsf one/file_subdir/file one/symlink_subdir/symlink", InputPaths: []string{"one/file_subdir/file"}, OutputPaths: []string{"one/symlink_subdir/symlink"}, SymlinkPaths: []string{"one/symlink_subdir/symlink"}, Mnemonic: "Symlink", }, } assertBuildStatements(t, actual, expectedBuildStatements) } func TestSymlinkQuotesPaths(t *testing.T) { const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 3 }, { "id": 2, "pathFragmentId": 5 }], "actions": [{ "targetId": 1, "actionKey": "x", "mnemonic": "SolibSymlink", "inputDepSetIds": [1], "outputIds": [2], "primaryOutputId": 2 }], "depSetOfFiles": [{ "id": 1, "directArtifactIds": [1] }], "pathFragments": [{ "id": 1, "label": "one" }, { "id": 2, "label": "file subdir", "parentId": 1 }, { "id": 3, "label": "file", "parentId": 2 }, { "id": 4, "label": "symlink subdir", "parentId": 1 }, { "id": 5, "label": "symlink", "parentId": 4 }] }` actual, err := AqueryBuildStatements([]byte(inputString)) if err != nil { t.Errorf("Unexpected error %q", err) } expectedBuildStatements := []BuildStatement{ BuildStatement{ Command: "mkdir -p 'one/symlink subdir' && " + "rm -f 'one/symlink subdir/symlink' && " + "ln -rsf 'one/file subdir/file' 'one/symlink subdir/symlink'", InputPaths: []string{"one/file subdir/file"}, OutputPaths: []string{"one/symlink subdir/symlink"}, SymlinkPaths: []string{"one/symlink subdir/symlink"}, Mnemonic: "SolibSymlink", }, } assertBuildStatements(t, actual, expectedBuildStatements) } func TestSymlinkMultipleInputs(t *testing.T) { const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 1 }, { "id": 2, "pathFragmentId": 2 }, { "id": 3, "pathFragmentId": 3 }], "actions": [{ "targetId": 1, "actionKey": "x", "mnemonic": "Symlink", "inputDepSetIds": [1], "outputIds": [3], "primaryOutputId": 3 }], "depSetOfFiles": [{ "id": 1, "directArtifactIds": [1,2] }], "pathFragments": [{ "id": 1, "label": "file" }, { "id": 2, "label": "other_file" }, { "id": 3, "label": "symlink" }] }` _, err := AqueryBuildStatements([]byte(inputString)) assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file" "other_file"], output ["symlink"]`) } func TestSymlinkMultipleOutputs(t *testing.T) { const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 1 }, { "id": 2, "pathFragmentId": 2 }, { "id": 3, "pathFragmentId": 3 }], "actions": [{ "targetId": 1, "actionKey": "x", "mnemonic": "Symlink", "inputDepSetIds": [1], "outputIds": [2,3], "primaryOutputId": 2 }], "depSetOfFiles": [{ "id": 1, "directArtifactIds": [1] }], "pathFragments": [{ "id": 1, "label": "file" }, { "id": 2, "label": "symlink" }, { "id": 3, "label": "other_symlink" }] }` _, err := AqueryBuildStatements([]byte(inputString)) assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file"], output ["symlink" "other_symlink"]`) } func assertError(t *testing.T, err error, expected string) { t.Helper() if err == nil { t.Errorf("expected error '%s', but got no error", expected) } else if err.Error() != expected { t.Errorf("expected error '%s', but got: %s", expected, err.Error()) t.Errorf("expected error:\n\t'%s', but got:\n\t'%s'", expected, err.Error()) } } // Asserts that the given actual build statements match the given expected build statements. // Build statement equivalence is determined using buildStatementEquals. func assertBuildStatements(t *testing.T, expected []BuildStatement, actual []BuildStatement) { t.Helper() if len(expected) != len(actual) { t.Errorf("expected %d build statements, but got %d,\n expected: %v,\n actual: %v", len(expected), len(actual), expected, actual) Loading Loading @@ -852,6 +1064,12 @@ func buildStatementEquals(first BuildStatement, second BuildStatement) bool { if !reflect.DeepEqual(stringSet(first.OutputPaths), stringSet(second.OutputPaths)) { return false } if !reflect.DeepEqual(stringSet(first.SymlinkPaths), stringSet(second.SymlinkPaths)) { return false } if first.Depfile != second.Depfile { return false } return true } Loading