...

Source file src/github.com/bazelbuild/rules_go/go/tools/gopackagesdriver/bazel_json_builder.go

Documentation: github.com/bazelbuild/rules_go/go/tools/gopackagesdriver

     1  // Copyright 2021 The Bazel Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package main
    16  
    17  import (
    18  	"bufio"
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"path"
    25  	"path/filepath"
    26  	"regexp"
    27  	"runtime"
    28  	"strings"
    29  )
    30  
    31  type BazelJSONBuilder struct {
    32  	bazel        *Bazel
    33  	includeTests bool
    34  }
    35  
    36  var RulesGoStdlibLabel = rulesGoRepositoryName + "//:stdlib"
    37  
    38  var _defaultKinds = []string{"go_library", "go_test", "go_binary"}
    39  
    40  var externalRe = regexp.MustCompile(".*\\/external\\/([^\\/]+)(\\/(.*))?\\/([^\\/]+.go)")
    41  
    42  func (b *BazelJSONBuilder) fileQuery(filename string) string {
    43  	label := filename
    44  
    45  	if filepath.IsAbs(filename) {
    46  		label, _ = filepath.Rel(b.bazel.WorkspaceRoot(), filename)
    47  	} else if strings.HasPrefix(filename, "./") {
    48  		label = strings.TrimPrefix(filename, "./")
    49  	}
    50  
    51  	if matches := externalRe.FindStringSubmatch(filename); len(matches) == 5 {
    52  		// if filepath is for a third party lib, we need to know, what external
    53  		// library this file is part of.
    54  		matches = append(matches[:2], matches[3:]...)
    55  		label = fmt.Sprintf("@%s//%s", matches[1], strings.Join(matches[2:], ":"))
    56  	}
    57  
    58  	relToBin, err := filepath.Rel(b.bazel.info["output_path"], filename)
    59  	if err == nil && !strings.HasPrefix(relToBin, "../") {
    60  		parts := strings.SplitN(relToBin, string(filepath.Separator), 3)
    61  		relToBin = parts[2]
    62  		// We've effectively converted filename from bazel-bin/some/path.go to some/path.go;
    63  		// Check if a BUILD.bazel files exists under this dir, if not walk up and repeat.
    64  		relToBin = filepath.Dir(relToBin)
    65  		_, err = os.Stat(filepath.Join(b.bazel.WorkspaceRoot(), relToBin, "BUILD.bazel"))
    66  		for errors.Is(err, os.ErrNotExist) && relToBin != "." {
    67  			relToBin = filepath.Dir(relToBin)
    68  			_, err = os.Stat(filepath.Join(b.bazel.WorkspaceRoot(), relToBin, "BUILD.bazel"))
    69  		}
    70  
    71  		if err == nil {
    72  			// return package path found and build all targets (codegen doesn't fall under go_library)
    73  			// Otherwise fallback to default
    74  			if relToBin == "." {
    75  				relToBin = ""
    76  			}
    77  			label = fmt.Sprintf("//%s:all", relToBin)
    78  			additionalKinds = append(additionalKinds, "go_.*")
    79  		}
    80  	}
    81  
    82  	kinds := append(_defaultKinds, additionalKinds...)
    83  	return fmt.Sprintf(`kind("%s", same_pkg_direct_rdeps("%s"))`, strings.Join(kinds, "|"), label)
    84  }
    85  
    86  func (b *BazelJSONBuilder) getKind() string {
    87  	kinds := []string{"go_library"}
    88  	if b.includeTests {
    89  		kinds = append(kinds, "go_test")
    90  	}
    91  
    92  	return strings.Join(kinds, "|")
    93  }
    94  
    95  func (b *BazelJSONBuilder) localQuery(request string) string {
    96  	request = path.Clean(request)
    97  	if filepath.IsAbs(request) {
    98  		if relPath, err := filepath.Rel(workspaceRoot, request); err == nil {
    99  			request = relPath
   100  		}
   101  	}
   102  
   103  	if !strings.HasSuffix(request, "...") {
   104  		request = fmt.Sprintf("%s:*", request)
   105  	}
   106  
   107  	return fmt.Sprintf(`kind("%s", %s)`, b.getKind(), request)
   108  }
   109  
   110  func (b *BazelJSONBuilder) packageQuery(importPath string) string {
   111  	if strings.HasSuffix(importPath, "/...") {
   112  		importPath = fmt.Sprintf(`^%s(/.+)?$`, strings.TrimSuffix(importPath, "/..."))
   113  	}
   114  
   115  	return fmt.Sprintf(
   116  		`kind("%s", attr(importpath, "%s", deps(%s)))`,
   117  		b.getKind(),
   118  		importPath,
   119  		bazelQueryScope)
   120  }
   121  
   122  func (b *BazelJSONBuilder) queryFromRequests(requests ...string) string {
   123  	ret := make([]string, 0, len(requests))
   124  	for _, request := range requests {
   125  		result := ""
   126  		if strings.HasSuffix(request, ".go") {
   127  			f := strings.TrimPrefix(request, "file=")
   128  			result = b.fileQuery(f)
   129  		} else if bazelQueryScope != "" {
   130  			result = b.packageQuery(request)
   131  		} else if isLocalPattern(request) {
   132  			result = b.localQuery(request)
   133  		} else if request == "builtin" || request == "std" {
   134  			result = fmt.Sprintf(RulesGoStdlibLabel)
   135  		}
   136  
   137  		if result != "" {
   138  			ret = append(ret, result)
   139  		}
   140  	}
   141  	if len(ret) == 0 {
   142  		return RulesGoStdlibLabel
   143  	}
   144  	return strings.Join(ret, " union ")
   145  }
   146  
   147  func NewBazelJSONBuilder(bazel *Bazel, includeTests bool) (*BazelJSONBuilder, error) {
   148  	return &BazelJSONBuilder{
   149  		bazel:        bazel,
   150  		includeTests: includeTests,
   151  	}, nil
   152  }
   153  
   154  func (b *BazelJSONBuilder) outputGroupsForMode(mode LoadMode) string {
   155  	og := "go_pkg_driver_json_file,go_pkg_driver_stdlib_json_file,go_pkg_driver_srcs"
   156  	if mode&NeedExportsFile != 0 {
   157  		og += ",go_pkg_driver_export_file"
   158  	}
   159  	return og
   160  }
   161  
   162  func (b *BazelJSONBuilder) query(ctx context.Context, query string) ([]string, error) {
   163  	var bzlmodQueryFlags []string
   164  	if strings.HasPrefix(rulesGoRepositoryName, "@@") {
   165  		// Requires Bazel 6.4.0.
   166  		bzlmodQueryFlags = []string{"--consistent_labels"}
   167  	}
   168  	queryArgs := concatStringsArrays(bazelQueryFlags, bzlmodQueryFlags, []string{
   169  		"--ui_event_filters=-info,-stderr",
   170  		"--noshow_progress",
   171  		"--order_output=no",
   172  		"--output=label",
   173  		"--nodep_deps",
   174  		"--noimplicit_deps",
   175  		"--notool_deps",
   176  		query,
   177  	})
   178  	labels, err := b.bazel.Query(ctx, queryArgs...)
   179  	if err != nil {
   180  		return nil, fmt.Errorf("unable to query: %w", err)
   181  	}
   182  
   183  	return labels, nil
   184  }
   185  
   186  func (b *BazelJSONBuilder) Labels(ctx context.Context, requests []string) ([]string, error) {
   187  	labels, err := b.query(ctx, b.queryFromRequests(requests...))
   188  	if err != nil {
   189  		return nil, fmt.Errorf("query failed: %w", err)
   190  	}
   191  
   192  	if len(labels) == 0 {
   193  		return nil, fmt.Errorf("found no labels matching the requests")
   194  	}
   195  
   196  	return labels, nil
   197  }
   198  
   199  func (b *BazelJSONBuilder) Build(ctx context.Context, labels []string, mode LoadMode) ([]string, error) {
   200  	aspects := append(additionalAspects, goDefaultAspect)
   201  
   202  	buildArgs := concatStringsArrays([]string{
   203  		"--experimental_convenience_symlinks=ignore",
   204  		"--ui_event_filters=-info,-stderr",
   205  		"--noshow_progress",
   206  		"--aspects=" + strings.Join(aspects, ","),
   207  		"--output_groups=" + b.outputGroupsForMode(mode),
   208  		"--keep_going", // Build all possible packages
   209  	}, bazelBuildFlags)
   210  
   211  	if len(labels) < 100 {
   212  		buildArgs = append(buildArgs, labels...)
   213  	} else {
   214  		// To avoid hitting MAX_ARGS length, write labels to a file and use `--target_pattern_file`
   215  		targetsFile, err := ioutil.TempFile("", "gopackagesdriver_targets_")
   216  		if err != nil {
   217  			return nil, fmt.Errorf("unable to create target pattern file: %w", err)
   218  		}
   219  		writer := bufio.NewWriter(targetsFile)
   220  		defer writer.Flush()
   221  		for _, l := range labels {
   222  			writer.WriteString(l + "\n")
   223  		}
   224  		if err := writer.Flush(); err != nil {
   225  			return nil, fmt.Errorf("unable to flush data to target pattern file: %w", err)
   226  		}
   227  		defer func() {
   228  			targetsFile.Close()
   229  			os.Remove(targetsFile.Name())
   230  		}()
   231  
   232  		buildArgs = append(buildArgs, "--target_pattern_file="+targetsFile.Name())
   233  	}
   234  	files, err := b.bazel.Build(ctx, buildArgs...)
   235  	if err != nil {
   236  		return nil, fmt.Errorf("unable to bazel build %v: %w", buildArgs, err)
   237  	}
   238  
   239  	ret := []string{}
   240  	for _, f := range files {
   241  		if strings.HasSuffix(f, ".pkg.json") {
   242  			ret = append(ret, cleanPath(f))
   243  		}
   244  	}
   245  
   246  	return ret, nil
   247  }
   248  
   249  func (b *BazelJSONBuilder) PathResolver() PathResolverFunc {
   250  	return func(p string) string {
   251  		p = strings.Replace(p, "__BAZEL_EXECROOT__", b.bazel.ExecutionRoot(), 1)
   252  		p = strings.Replace(p, "__BAZEL_WORKSPACE__", b.bazel.WorkspaceRoot(), 1)
   253  		p = strings.Replace(p, "__BAZEL_OUTPUT_BASE__", b.bazel.OutputBase(), 1)
   254  		return p
   255  	}
   256  }
   257  
   258  func cleanPath(p string) string {
   259  	// On Windows the paths may contain a starting `\`, this would make them not resolve
   260  	if runtime.GOOS == "windows" && p[0] == '\\' {
   261  		return p[1:]
   262  	}
   263  
   264  	return p
   265  }
   266  

View as plain text