Skip to content

Commit 91e44ae

Browse files
tobiaskohlbauachew22
authored andcommitted
use cquery for resolving files and dependencies
This takes antoher stub at #594 by introducing cquery back in. In difference to the previous attempt this splits up the resolving of buildfiles. CQuery does not support buildfiles but without cquery bazel-watcher could watch files which are not reflected in the build for e.g. dependencies behind a select statement. In order to avoid depending on such cases first a cquery is issued which resolves dependencies of selected targets. These dependencies are then injected into the buildfiles query in order to get dependent build files. Revival of #672. Co-authored-by: Tobias Kohlbau <tobias@kohlbau.de>
1 parent adcee9a commit 91e44ae

3 files changed

Lines changed: 212 additions & 50 deletions

File tree

internal/bazel/testing/mock.go

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ package testing
1616

1717
import (
1818
"bytes"
19+
"flag"
1920
"fmt"
21+
"io"
22+
"os"
2023
"os/exec"
2124
"regexp"
2225
"testing"
@@ -32,6 +35,7 @@ type MockBazel struct {
3235
cqueryResponse map[string]*analysis.CqueryResult
3336
args []string
3437
startupArgs []string
38+
info map[string]string
3539

3640
buildError error
3741
waitError error
@@ -51,6 +55,9 @@ func (b *MockBazel) SetStartupArgs(args []string) {
5155
b.startupArgs = args
5256
}
5357

58+
func (b *MockBazel) SetInfo(info map[string]string) {
59+
b.info = info
60+
}
5461
func (b *MockBazel) WriteToStderr(v bool) {
5562
b.actions = append(b.actions, []string{"WriteToStderr", fmt.Sprint(v)})
5663
}
@@ -59,7 +66,7 @@ func (b *MockBazel) WriteToStdout(v bool) {
5966
}
6067
func (b *MockBazel) Info() (map[string]string, *bytes.Buffer, error) {
6168
b.actions = append(b.actions, []string{"Info"})
62-
return map[string]string{}, nil, nil
69+
return b.info, nil, nil
6370
}
6471
func (b *MockBazel) DumpRepoMapping(canonicalRepoName string) (map[string]string, *bytes.Buffer, error) {
6572
b.actions = append(b.actions, []string{"DumpRepoMapping", canonicalRepoName})
@@ -73,9 +80,12 @@ func (b *MockBazel) AddQueryResponse(query string, res *blaze_query.QueryResult)
7380
}
7481
func (b *MockBazel) Query(args ...string) (*blaze_query.QueryResult, error) {
7582
b.actions = append(b.actions, append([]string{"Query"}, args...))
76-
query := args[0]
83+
queryArgs, err := parseQueryArgs(args)
84+
if err != nil {
85+
panic(fmt.Sprintf("failed to parse query args: %v", err))
86+
}
87+
query := queryArgs[0]
7788
res, ok := b.queryResponse[query]
78-
7989
if !ok {
8090
var candidates []string
8191
for candidate := range b.queryResponse {
@@ -161,9 +171,19 @@ func (b *MockBazel) AssertActions(t *testing.T, expected [][]string) {
161171
}
162172
}
163173

164-
func max(a, b int) int {
165-
if a > b {
166-
return a
174+
func parseQueryArgs(args []string) ([]string, error) {
175+
fs := flag.NewFlagSet("", flag.ContinueOnError)
176+
fs.SetOutput(io.Discard)
177+
queryFile := fs.String("query_file", "", "")
178+
if err := fs.Parse(args); err != nil {
179+
return args, nil
180+
}
181+
if *queryFile == "" {
182+
return args, nil
183+
}
184+
contents, err := os.ReadFile(*queryFile)
185+
if err != nil {
186+
return nil, fmt.Errorf("failed to read query file: %w", err)
167187
}
168-
return b
188+
return []string{string(contents)}, nil
169189
}

internal/ibazel/ibazel.go

Lines changed: 116 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ const (
6161
)
6262

6363
const sourceQuery = "kind('source file', deps(set(%s)))"
64-
const buildQuery = "buildfiles(deps(set(%s)))"
64+
const targetQuery = "deps(set(%s))"
65+
const buildQuery = "buildfiles(set(%s))"
6566

6667
type IBazel struct {
6768
debounceDuration time.Duration
@@ -311,8 +312,21 @@ func (i *IBazel) iteration(command string, commandToRun runnableCommand, targets
311312
case QUERY:
312313
// Query for which files to watch.
313314
log.Logf("Querying for files to watch...")
314-
i.watchFiles(fmt.Sprintf(buildQuery, joinedTargets), i.buildFileWatcher)
315-
i.watchFiles(fmt.Sprintf(sourceQuery, joinedTargets), i.sourceFileWatcher)
315+
316+
toWatchBuildFiles, err := i.queryForBuildFiles(joinedTargets)
317+
if err != nil {
318+
log.Errorf("Error querying for build files: %v", err)
319+
} else {
320+
i.watchFiles(toWatchBuildFiles, i.buildFileWatcher)
321+
}
322+
323+
toWatchSourceFiles, err := i.queryForSourceFiles(joinedTargets)
324+
if err != nil {
325+
log.Errorf("Error querying for source files: %v", err)
326+
} else {
327+
i.watchFiles(toWatchSourceFiles, i.sourceFileWatcher)
328+
}
329+
316330
i.state = RUN
317331
case DEBOUNCE_RUN:
318332
select {
@@ -492,63 +506,96 @@ func (i *IBazel) dumpRootRepoMapping() (map[string]string, *bytes.Buffer, error)
492506
return res, stderr, nil
493507
}
494508

495-
func (i *IBazel) queryForSourceFiles(query string) ([]string, error) {
509+
func (i *IBazel) queryForSourceFiles(targets string) ([]string, error) {
496510
b := i.newBazel()
497511

498-
localRepositories, err := i.realLocalRepositoryPaths()
512+
res, err := b.CQuery(i.cQueryArgs(fmt.Sprintf(sourceQuery, targets))...)
499513
if err != nil {
514+
log.Errorf("Bazel cquery failed: %v", err)
500515
return nil, err
501516
}
502517

503-
res, err := b.Query(i.queryArgs(query)...)
518+
labels := make([]string, 0, len(res.Results))
519+
for _, target := range res.Results {
520+
switch *target.Target.Type {
521+
case blaze_query.Target_SOURCE_FILE:
522+
label := target.Target.SourceFile.GetName()
523+
labels = append(labels, label)
524+
default:
525+
log.Errorf("%v\n", target)
526+
}
527+
}
528+
529+
return i.labelsToWatch(labels)
530+
}
531+
532+
func (i *IBazel) queryForBuildFiles(targets string) ([]string, error) {
533+
b := i.newBazel()
534+
535+
targetRes, err := b.CQuery(i.cQueryArgs(fmt.Sprintf(targetQuery, targets))...)
504536
if err != nil {
505-
log.Errorf("Bazel query failed: %v", err)
537+
log.Errorf("Bazel target query failed: %v", err)
506538
return nil, err
507539
}
508540

509-
workspacePath, err := i.workspaceFinder.FindWorkspace()
541+
localRepositories, err := i.realLocalRepositoryPaths()
510542
if err != nil {
511-
log.Errorf("Error finding workspace: %v", err)
512543
return nil, err
513544
}
514-
515-
toWatch := make([]string, 0, len(res.GetTarget()))
516-
for _, target := range res.GetTarget() {
517-
switch *target.Type {
518-
case blaze_query.Target_SOURCE_FILE:
519-
label := target.GetSourceFile().GetName()
545+
quotedBuildTargets := make([]string, 0, len(targetRes.Results))
546+
for _, configuredTarget := range targetRes.Results {
547+
target := configuredTarget.GetTarget()
548+
if *target.Type == blaze_query.Target_RULE {
549+
label := target.GetRule().GetName()
520550
if strings.HasPrefix(label, "@") {
521-
repo, target := parseTarget(label)
522-
if realPath, ok := localRepositories[repo]; ok {
523-
label = strings.Replace(target, ":", string(filepath.Separator), 1)
524-
toWatch = append(toWatch, filepath.Join(realPath, label))
525-
break
551+
repo, _ := parseTarget(label)
552+
if _, ok := localRepositories[repo]; !ok {
553+
continue
526554
}
527-
continue
528555
}
529556
if strings.HasPrefix(label, "//external") {
530557
continue
531558
}
559+
// Build targets may contain characters that shells don't like.
560+
// Quote them to guarantee the correct behavior.
561+
quotedBuildTargets = append(quotedBuildTargets, fmt.Sprintf("%q", label))
562+
}
563+
}
564+
565+
f, err := os.CreateTemp("", "query")
566+
if err != nil {
567+
return nil, fmt.Errorf("failed to create query file: %w", err)
568+
}
569+
defer func() {
570+
f.Close()
571+
os.Remove(f.Name())
572+
}()
573+
_, err = f.WriteString(fmt.Sprintf(buildQuery, strings.Join(quotedBuildTargets, " ")))
574+
if err != nil {
575+
return nil, fmt.Errorf("failed to write query file: %w", err)
576+
}
577+
578+
res, err := b.Query(i.queryArgs(fmt.Sprintf("--query_file=%s", f.Name()))...)
579+
if err != nil {
580+
log.Errorf("Bazel query failed: %v", err)
581+
return nil, err
582+
}
532583

533-
label = strings.Replace(strings.TrimPrefix(label, "//"), ":", string(filepath.Separator), 1)
534-
toWatch = append(toWatch, filepath.Join(workspacePath, label))
535-
break
584+
labels := make([]string, 0, len(res.GetTarget()))
585+
for _, target := range res.GetTarget() {
586+
switch *target.Type {
587+
case blaze_query.Target_SOURCE_FILE:
588+
label := target.GetSourceFile().GetName()
589+
labels = append(labels, label)
536590
default:
537591
log.Errorf("%v\n", target)
538592
}
539593
}
540594

541-
return toWatch, nil
595+
return i.labelsToWatch(labels)
542596
}
543597

544-
func (i *IBazel) watchFiles(query string, watcher common.Watcher) {
545-
toWatch, err := i.queryForSourceFiles(query)
546-
if err != nil {
547-
// If the query fails, just keep watching the same files as before
548-
log.Errorf("Error querying for source files: %v", err)
549-
return
550-
}
551-
598+
func (i *IBazel) watchFiles(toWatch []string, watcher common.Watcher) {
552599
filesWatched := map[string]struct{}{}
553600
uniqueDirectories := map[string]struct{}{}
554601

@@ -569,18 +616,52 @@ func (i *IBazel) watchFiles(query string, watcher common.Watcher) {
569616
}
570617

571618
watchList := keys(uniqueDirectories)
572-
err = watcher.UpdateAll(watchList)
619+
err := watcher.UpdateAll(watchList)
573620
if err != nil {
574621
log.Errorf("Error(s) updating watch list:\n %v", err)
575622
}
576623

577624
if len(filesWatched) == 0 {
578-
log.Errorf("Didn't find any files to watch from query %s", query)
625+
log.Errorf("Didn't find any files to watch for")
579626
}
580627

581628
i.filesWatched[watcher] = filesWatched
582629
}
583630

631+
func (i *IBazel) labelsToWatch(labels []string) ([]string, error) {
632+
localRepositories, err := i.realLocalRepositoryPaths()
633+
if err != nil {
634+
return nil, err
635+
}
636+
637+
workspacePath, err := i.workspaceFinder.FindWorkspace()
638+
if err != nil {
639+
log.Errorf("Error finding workspace: %v", err)
640+
return nil, err
641+
}
642+
643+
toWatch := make([]string, 0, len(labels))
644+
for _, label := range labels {
645+
if strings.HasPrefix(label, "@") {
646+
repo, target := parseTarget(label)
647+
if realPath, ok := localRepositories[repo]; ok {
648+
label = strings.Replace(target, ":", string(filepath.Separator), 1)
649+
toWatch = append(toWatch, filepath.Join(realPath, label))
650+
break
651+
}
652+
continue
653+
}
654+
if strings.HasPrefix(label, "//external") {
655+
continue
656+
}
657+
658+
label = strings.Replace(strings.TrimPrefix(label, "//"), ":", string(filepath.Separator), 1)
659+
toWatch = append(toWatch, filepath.Join(workspacePath, label))
660+
}
661+
662+
return toWatch, nil
663+
}
664+
584665
func (i *IBazel) queryArgs(args ...string) []string {
585666
queryArgs := append([]string(nil), args...)
586667

0 commit comments

Comments
 (0)