...

Source file src/github.com/bazelbuild/rules_go/go/tools/bazel_testing/bazel_testing.go

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

     1  // Copyright 2019 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 bazel_testing provides an integration testing framework for
    16  // testing rules_go with Bazel.
    17  //
    18  // Tests may be written by declaring a go_bazel_test target instead of
    19  // a go_test (go_bazel_test is defined in def.bzl here), then calling
    20  // TestMain. Tests are run in a synthetic test workspace. Tests may run
    21  // bazel commands with RunBazel.
    22  package bazel_testing
    23  
    24  import (
    25  	"bytes"
    26  	"flag"
    27  	"fmt"
    28  	"io"
    29  	"io/ioutil"
    30  	"os"
    31  	"os/exec"
    32  	"os/signal"
    33  	"path"
    34  	"path/filepath"
    35  	"regexp"
    36  	"runtime"
    37  	"sort"
    38  	"strings"
    39  	"testing"
    40  	"text/template"
    41  
    42  	"github.com/bazelbuild/rules_go/go/tools/bazel"
    43  	"github.com/bazelbuild/rules_go/go/tools/internal/txtar"
    44  )
    45  
    46  const (
    47  	// Standard Bazel exit codes.
    48  	// A subset of codes in https://cs.opensource.google/bazel/bazel/+/master:src/main/java/com/google/devtools/build/lib/util/ExitCode.java.
    49  	SUCCESS                    = 0
    50  	BUILD_FAILURE              = 1
    51  	COMMAND_LINE_ERROR         = 2
    52  	TESTS_FAILED               = 3
    53  	NO_TESTS_FOUND             = 4
    54  	RUN_FAILURE                = 6
    55  	ANALYSIS_FAILURE           = 7
    56  	INTERRUPTED                = 8
    57  	LOCK_HELD_NOBLOCK_FOR_LOCK = 9
    58  )
    59  
    60  // Args is a list of arguments to TestMain. It's defined as a struct so
    61  // that new optional arguments may be added without breaking compatibility.
    62  type Args struct {
    63  	// Main is a text archive containing files in the main workspace.
    64  	// The text archive format is parsed by
    65  	// //go/tools/internal/txtar:go_default_library, which is copied from
    66  	// cmd/go/internal/txtar. If this archive does not contain a WORKSPACE file,
    67  	// a default file will be synthesized.
    68  	Main string
    69  
    70  	// Nogo is the nogo target to pass to go_register_toolchains. By default,
    71  	// nogo is not used.
    72  	Nogo string
    73  
    74  	// NogoIncludes is the list of targets to include for Nogo linting.
    75  	NogoIncludes []string
    76  
    77  	// NogoExcludes is the list of targets to include for Nogo linting.
    78  	NogoExcludes []string
    79  
    80  	// WorkspaceSuffix is a string that should be appended to the end
    81  	// of the default generated WORKSPACE file.
    82  	WorkspaceSuffix string
    83  
    84  	// ModuleFileSuffix is a string that should be appended to the end of a
    85  	// default generated MODULE.bazel file. If this is empty, no such file is
    86  	// generated.
    87  	ModuleFileSuffix string
    88  
    89  	// SetUp is a function that is executed inside the context of the testing
    90  	// workspace. It is executed once and only once before the beginning of
    91  	// all tests. If SetUp returns a non-nil error, execution is halted and
    92  	// tests cases are not executed.
    93  	SetUp func() error
    94  }
    95  
    96  // debug may be set to make the test print the test workspace path and stop
    97  // instead of running tests.
    98  const debug = false
    99  
   100  // outputUserRoot is set to the directory where Bazel should put its internal files.
   101  // Since Bazel 2.0.0, this needs to be set explicitly to avoid it defaulting to a
   102  // deeply nested directory within the test, which runs into Windows path length limits.
   103  // We try to detect the original value in setupWorkspace and set it to that.
   104  var outputUserRoot string
   105  
   106  // TestMain should be called by tests using this framework from a function named
   107  // "TestMain". For example:
   108  //
   109  //	func TestMain(m *testing.M) {
   110  //	  os.Exit(bazel_testing.TestMain(m, bazel_testing.Args{...}))
   111  //	}
   112  //
   113  // TestMain constructs a set of workspaces and changes the working directory to
   114  // the main workspace.
   115  func TestMain(m *testing.M, args Args) {
   116  	// Defer os.Exit with the correct code. This ensures other deferred cleanup
   117  	// functions are run first.
   118  	code := 1
   119  	defer func() {
   120  		if r := recover(); r != nil {
   121  			fmt.Fprintf(os.Stderr, "panic: %v\n", r)
   122  			code = 1
   123  		}
   124  		os.Exit(code)
   125  	}()
   126  
   127  	files, err := bazel.SpliceDelimitedOSArgs("-begin_files", "-end_files")
   128  	if err != nil {
   129  		fmt.Fprint(os.Stderr, err)
   130  		return
   131  	}
   132  
   133  	flag.Parse()
   134  
   135  	workspaceDir, cleanup, err := setupWorkspace(args, files)
   136  	defer func() {
   137  		if err := cleanup(); err != nil {
   138  			fmt.Fprintf(os.Stderr, "cleanup error: %v\n", err)
   139  			// Don't fail the test on a cleanup error.
   140  			// Some operating systems (windows, maybe also darwin) can't reliably
   141  			// delete executable files after they're run.
   142  		}
   143  	}()
   144  	if err != nil {
   145  		fmt.Fprintf(os.Stderr, "error: %v\n", err)
   146  		return
   147  	}
   148  
   149  	if debug {
   150  		fmt.Fprintf(os.Stderr, "test setup in %s\n", workspaceDir)
   151  		interrupted := make(chan os.Signal)
   152  		signal.Notify(interrupted, os.Interrupt)
   153  		<-interrupted
   154  		return
   155  	}
   156  
   157  	if err := os.Chdir(workspaceDir); err != nil {
   158  		fmt.Fprintf(os.Stderr, "%v\n", err)
   159  		return
   160  	}
   161  	defer exec.Command("bazel", "shutdown").Run()
   162  
   163  	if args.SetUp != nil {
   164  		if err := args.SetUp(); err != nil {
   165  			fmt.Fprintf(os.Stderr, "test provided SetUp method returned error: %v\n", err)
   166  			return
   167  		}
   168  	}
   169  
   170  	code = m.Run()
   171  }
   172  
   173  // BazelCmd prepares a bazel command for execution. It chooses the correct
   174  // bazel binary based on the environment and sanitizes the environment to
   175  // hide that this code is executing inside a bazel test.
   176  func BazelCmd(args ...string) *exec.Cmd {
   177  	cmd := exec.Command("bazel")
   178  	if outputUserRoot != "" {
   179  		cmd.Args = append(cmd.Args,
   180  			"--output_user_root="+outputUserRoot,
   181  			"--nosystem_rc",
   182  			"--nohome_rc",
   183  		)
   184  	}
   185  	cmd.Args = append(cmd.Args, args...)
   186  	for _, e := range os.Environ() {
   187  		// Filter environment variables set by the bazel test wrapper script.
   188  		// These confuse recursive invocations of Bazel.
   189  		if strings.HasPrefix(e, "TEST_") || strings.HasPrefix(e, "RUNFILES_") {
   190  			continue
   191  		}
   192  		cmd.Env = append(cmd.Env, e)
   193  	}
   194  	return cmd
   195  }
   196  
   197  // RunBazel invokes a bazel command with a list of arguments.
   198  //
   199  // If the command starts but exits with a non-zero status, a *StderrExitError
   200  // will be returned which wraps the original *exec.ExitError.
   201  func RunBazel(args ...string) error {
   202  	cmd := BazelCmd(args...)
   203  
   204  	buf := &bytes.Buffer{}
   205  	cmd.Stderr = buf
   206  	err := cmd.Run()
   207  	if eErr, ok := err.(*exec.ExitError); ok {
   208  		eErr.Stderr = buf.Bytes()
   209  		err = &StderrExitError{Err: eErr}
   210  	}
   211  	return err
   212  }
   213  
   214  // BazelOutput invokes a bazel command with a list of arguments and returns
   215  // the content of stdout.
   216  //
   217  // If the command starts but exits with a non-zero status, a *StderrExitError
   218  // will be returned which wraps the original *exec.ExitError.
   219  func BazelOutput(args ...string) ([]byte, error) {
   220  	stdout, _, err := BazelOutputWithInput(nil, args...)
   221  	return stdout, err
   222  }
   223  
   224  // BazelOutputWithInput invokes a bazel command with a list of arguments and
   225  // an input stream and returns the content of stdout.
   226  //
   227  // If the command starts but exits with a non-zero status, a *StderrExitError
   228  // will be returned which wraps the original *exec.ExitError.
   229  func BazelOutputWithInput(stdin io.Reader, args ...string) ([]byte, []byte, error) {
   230  	cmd := BazelCmd(args...)
   231  	stdout := &bytes.Buffer{}
   232  	stderr := &bytes.Buffer{}
   233  	cmd.Stdout = stdout
   234  	cmd.Stderr = stderr
   235  	if stdin != nil {
   236  		cmd.Stdin = stdin
   237  	}
   238  	err := cmd.Run()
   239  	if eErr, ok := err.(*exec.ExitError); ok {
   240  		eErr.Stderr = stderr.Bytes()
   241  		err = &StderrExitError{Err: eErr}
   242  	}
   243  	return stdout.Bytes(), stderr.Bytes(), err
   244  }
   245  
   246  // StderrExitError wraps *exec.ExitError and prints the complete stderr output
   247  // from a command.
   248  type StderrExitError struct {
   249  	Err *exec.ExitError
   250  }
   251  
   252  func (e *StderrExitError) Error() string {
   253  	sb := &strings.Builder{}
   254  	sb.Write(e.Err.Stderr)
   255  	sb.WriteString(e.Err.Error())
   256  	return sb.String()
   257  }
   258  
   259  func (e *StderrExitError) Unwrap() error {
   260  	return e.Err
   261  }
   262  
   263  func setupWorkspace(args Args, files []string) (dir string, cleanup func() error, err error) {
   264  	var cleanups []func() error
   265  	cleanup = func() error {
   266  		var firstErr error
   267  		for i := len(cleanups) - 1; i >= 0; i-- {
   268  			if err := cleanups[i](); err != nil && firstErr == nil {
   269  				firstErr = err
   270  			}
   271  		}
   272  		return firstErr
   273  	}
   274  	defer func() {
   275  		if err != nil {
   276  			cleanup()
   277  			cleanup = func() error { return nil }
   278  		}
   279  	}()
   280  
   281  	// Find a suitable cache directory. We want something persistent where we
   282  	// can store a bazel output base across test runs, even for multiple tests.
   283  	var cacheDir, outBaseDir string
   284  	if tmpDir := os.Getenv("TEST_TMPDIR"); tmpDir != "" {
   285  		// TEST_TMPDIR is set by Bazel's test wrapper. Bazel itself uses this to
   286  		// detect that it's run by a test. When invoked like this, Bazel sets
   287  		// its output base directory to a temporary directory. This wastes a lot
   288  		// of time (a simple test takes 45s instead of 3s). We use TEST_TMPDIR
   289  		// to find a persistent location in the execroot. We won't pass TEST_TMPDIR
   290  		// to bazel in RunBazel.
   291  		tmpDir = filepath.Clean(tmpDir)
   292  		if i := strings.Index(tmpDir, string(os.PathSeparator)+"execroot"+string(os.PathSeparator)); i >= 0 {
   293  			outBaseDir = tmpDir[:i]
   294  			outputUserRoot = filepath.Dir(outBaseDir)
   295  			cacheDir = filepath.Join(outBaseDir, "bazel_testing")
   296  		} else {
   297  			cacheDir = filepath.Join(tmpDir, "bazel_testing")
   298  		}
   299  	} else {
   300  		// The test is not invoked by Bazel, so just use the user's cache.
   301  		cacheDir, err = os.UserCacheDir()
   302  		if err != nil {
   303  			return "", cleanup, err
   304  		}
   305  		cacheDir = filepath.Join(cacheDir, "bazel_testing")
   306  	}
   307  
   308  	// TODO(jayconrod): any other directories needed for caches?
   309  	execDir := filepath.Join(cacheDir, "bazel_go_test")
   310  	if err := os.RemoveAll(execDir); err != nil {
   311  		return "", cleanup, err
   312  	}
   313  	cleanups = append(cleanups, func() error { return os.RemoveAll(execDir) })
   314  
   315  	// Create the workspace directory.
   316  	mainDir := filepath.Join(execDir, "main")
   317  	if err := os.MkdirAll(mainDir, 0777); err != nil {
   318  		return "", cleanup, err
   319  	}
   320  
   321  	// Create a .bazelrc file if GO_BAZEL_TEST_BAZELFLAGS is set.
   322  	// The test can override this with its own .bazelrc or with flags in commands.
   323  	if flags := os.Getenv("GO_BAZEL_TEST_BAZELFLAGS"); flags != "" {
   324  		bazelrcPath := filepath.Join(mainDir, ".bazelrc")
   325  		content := "build " + flags
   326  		if err := ioutil.WriteFile(bazelrcPath, []byte(content), 0666); err != nil {
   327  			return "", cleanup, err
   328  		}
   329  	}
   330  
   331  	// Extract test files for the main workspace.
   332  	if err := extractTxtar(mainDir, args.Main); err != nil {
   333  		return "", cleanup, fmt.Errorf("building main workspace: %v", err)
   334  	}
   335  
   336  	// If some of the path arguments are missing an explicit workspace,
   337  	// read the workspace name from WORKSPACE. We need this to map arguments
   338  	// to runfiles in specific workspaces.
   339  	haveDefaultWorkspace := false
   340  	var defaultWorkspaceName string
   341  	for _, argPath := range files {
   342  		workspace, _, err := parseLocationArg(argPath)
   343  		if err == nil && workspace == "" {
   344  			haveDefaultWorkspace = true
   345  			cleanPath := path.Clean(argPath)
   346  			if cleanPath == "WORKSPACE" {
   347  				defaultWorkspaceName, err = loadWorkspaceName(cleanPath)
   348  				if err != nil {
   349  					return "", cleanup, fmt.Errorf("could not load default workspace name: %v", err)
   350  				}
   351  				break
   352  			}
   353  		}
   354  	}
   355  	if haveDefaultWorkspace && defaultWorkspaceName == "" {
   356  		return "", cleanup, fmt.Errorf("found files from default workspace, but not WORKSPACE")
   357  	}
   358  
   359  	// Index runfiles by workspace and short path. We need this to determine
   360  	// destination paths when we copy or link files.
   361  	runfiles, err := bazel.ListRunfiles()
   362  	if err != nil {
   363  		return "", cleanup, err
   364  	}
   365  
   366  	type runfileKey struct{ workspace, short string }
   367  	runfileMap := make(map[runfileKey]string)
   368  	for _, rf := range runfiles {
   369  		runfileMap[runfileKey{rf.Workspace, rf.ShortPath}] = rf.Path
   370  	}
   371  
   372  	// Copy or link file arguments from runfiles into fake workspace dirctories.
   373  	// Keep track of the workspace names we see, since we'll generate a WORKSPACE
   374  	// with local_repository rules later.
   375  	workspaceNames := make(map[string]bool)
   376  	for _, argPath := range files {
   377  		workspace, shortPath, err := parseLocationArg(argPath)
   378  		if err != nil {
   379  			return "", cleanup, err
   380  		}
   381  		if workspace == "" {
   382  			workspace = defaultWorkspaceName
   383  		}
   384  		workspaceNames[workspace] = true
   385  
   386  		srcPath, ok := runfileMap[runfileKey{workspace, shortPath}]
   387  		if !ok {
   388  			return "", cleanup, fmt.Errorf("unknown runfile: %s", argPath)
   389  		}
   390  		dstPath := filepath.Join(execDir, workspace, shortPath)
   391  		if err := copyOrLink(dstPath, srcPath); err != nil {
   392  			return "", cleanup, err
   393  		}
   394  	}
   395  
   396  	// If there's no WORKSPACE file, create one.
   397  	workspacePath := filepath.Join(mainDir, "WORKSPACE")
   398  	if _, err = os.Stat(workspacePath); os.IsNotExist(err) {
   399  		var w *os.File
   400  		w, err = os.Create(workspacePath)
   401  		if err != nil {
   402  			return "", cleanup, err
   403  		}
   404  		defer func() {
   405  			if cerr := w.Close(); err == nil && cerr != nil {
   406  				err = cerr
   407  			}
   408  		}()
   409  		info := workspaceTemplateInfo{
   410  			Suffix:       args.WorkspaceSuffix,
   411  			Nogo:         args.Nogo,
   412  			NogoIncludes: args.NogoIncludes,
   413  			NogoExcludes: args.NogoExcludes,
   414  		}
   415  		for name := range workspaceNames {
   416  			info.WorkspaceNames = append(info.WorkspaceNames, name)
   417  		}
   418  		sort.Strings(info.WorkspaceNames)
   419  		if outBaseDir != "" {
   420  			goSDKPath := filepath.Join(outBaseDir, "external", "go_sdk")
   421  			rel, err := filepath.Rel(mainDir, goSDKPath)
   422  			if err != nil {
   423  				return "", cleanup, fmt.Errorf("could not find relative path from %q to %q for go_sdk", mainDir, goSDKPath)
   424  			}
   425  			rel = filepath.ToSlash(rel)
   426  			info.GoSDKPath = rel
   427  		}
   428  		if err := defaultWorkspaceTpl.Execute(w, info); err != nil {
   429  			return "", cleanup, err
   430  		}
   431  	}
   432  
   433  	// If a MODULE.bazel file is requested, create one.
   434  	if args.ModuleFileSuffix != "" {
   435  		moduleBazelPath := filepath.Join(mainDir, "MODULE.bazel")
   436  		if _, err = os.Stat(moduleBazelPath); err == nil {
   437  			return "", cleanup, fmt.Errorf("ModuleFileSuffix set but MODULE.bazel exists")
   438  		}
   439  		var w *os.File
   440  		w, err = os.Create(moduleBazelPath)
   441  		if err != nil {
   442  			return "", cleanup, err
   443  		}
   444  		defer func() {
   445  			if cerr := w.Close(); err == nil && cerr != nil {
   446  				err = cerr
   447  			}
   448  		}()
   449  		rulesGoAbsPath := filepath.Join(execDir, "io_bazel_rules_go")
   450  		rulesGoPath, err := filepath.Rel(mainDir, rulesGoAbsPath)
   451  		if err != nil {
   452  			return "", cleanup, fmt.Errorf("could not find relative path from %q to %q for io_bazel_rules_go", mainDir, rulesGoAbsPath)
   453  		}
   454  		rulesGoPath = filepath.ToSlash(rulesGoPath)
   455  		info := moduleFileTemplateInfo{
   456  			RulesGoPath: rulesGoPath,
   457  			Suffix:      args.ModuleFileSuffix,
   458  		}
   459  		if err := defaultModuleBazelTpl.Execute(w, info); err != nil {
   460  			return "", cleanup, err
   461  		}
   462  
   463  		// Enable Bzlmod.
   464  		bazelrcPath := filepath.Join(mainDir, ".bazelrc")
   465  		if _, err = os.Stat(bazelrcPath); os.IsNotExist(err) {
   466  			err = os.WriteFile(bazelrcPath, []byte("common --enable_bzlmod"), 0666)
   467  			if err != nil {
   468  				return "", cleanup, err
   469  			}
   470  		}
   471  	}
   472  
   473  	return mainDir, cleanup, nil
   474  }
   475  
   476  func extractTxtar(dir, txt string) error {
   477  	ar := txtar.Parse([]byte(txt))
   478  	for _, f := range ar.Files {
   479  		if parentDir := filepath.Dir(f.Name); parentDir != "." {
   480  			if err := os.MkdirAll(filepath.Join(dir, parentDir), 0777); err != nil {
   481  				return err
   482  			}
   483  		}
   484  		if err := ioutil.WriteFile(filepath.Join(dir, f.Name), f.Data, 0666); err != nil {
   485  			return err
   486  		}
   487  	}
   488  	return nil
   489  }
   490  
   491  func parseLocationArg(arg string) (workspace, shortPath string, err error) {
   492  	cleanPath := path.Clean(arg)
   493  	// Support both states of --legacy_external_runfiles.
   494  	if !strings.HasPrefix(cleanPath, "../") && !strings.HasPrefix(cleanPath, "external/") {
   495  		return "", cleanPath, nil
   496  	}
   497  	var trimmedPath string
   498  	if strings.HasPrefix(cleanPath, "../") {
   499  		trimmedPath = cleanPath[len("../"):]
   500  	} else {
   501  		trimmedPath = cleanPath[len("external/"):]
   502  	}
   503  	i := strings.IndexByte(trimmedPath, '/')
   504  	if i < 0 {
   505  		return "", "", fmt.Errorf("unexpected file (missing / after ../): %s", arg)
   506  	}
   507  	workspace = trimmedPath[:i]
   508  	shortPath = trimmedPath[i+1:]
   509  	return workspace, shortPath, nil
   510  }
   511  
   512  func loadWorkspaceName(workspacePath string) (string, error) {
   513  	runfilePath, err := bazel.Runfile(workspacePath)
   514  	if err == nil {
   515  		workspacePath = runfilePath
   516  	}
   517  	workspaceData, err := ioutil.ReadFile(workspacePath)
   518  	if err != nil {
   519  		return "", err
   520  	}
   521  	nameRe := regexp.MustCompile(`(?m)^workspace\(\s*name\s*=\s*("[^"]*"|'[^']*')\s*,?\s*\)\s*$`)
   522  	match := nameRe.FindSubmatchIndex(workspaceData)
   523  	if match == nil {
   524  		return "", fmt.Errorf("%s: workspace name not set", workspacePath)
   525  	}
   526  	name := string(workspaceData[match[2]+1 : match[3]-1])
   527  	if name == "" {
   528  		return "", fmt.Errorf("%s: workspace name is empty", workspacePath)
   529  	}
   530  	return name, nil
   531  }
   532  
   533  type workspaceTemplateInfo struct {
   534  	WorkspaceNames []string
   535  	GoSDKPath      string
   536  	Nogo           string
   537  	NogoIncludes   []string
   538  	NogoExcludes   []string
   539  	Suffix         string
   540  }
   541  
   542  var defaultWorkspaceTpl = template.Must(template.New("").Parse(`
   543  {{range .WorkspaceNames}}
   544  local_repository(
   545      name = "{{.}}",
   546      path = "../{{.}}",
   547  )
   548  {{end}}
   549  
   550  {{if not .GoSDKPath}}
   551  load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies", "go_register_toolchains")
   552  
   553  go_rules_dependencies()
   554  
   555  go_register_toolchains(go_version = "host")
   556  {{else}}
   557  local_repository(
   558      name = "local_go_sdk",
   559      path = "{{.GoSDKPath}}",
   560  )
   561  
   562  load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies", "go_register_toolchains", "go_wrap_sdk", "go_register_nogo")
   563  
   564  go_rules_dependencies()
   565  
   566  go_wrap_sdk(
   567      name = "go_sdk",
   568      root_file = "@local_go_sdk//:ROOT",
   569  )
   570  
   571  go_register_toolchains()
   572  
   573  {{if .Nogo}}
   574  go_register_nogo(
   575  	nogo = "{{.Nogo}}",
   576  	{{ if .NogoIncludes }}
   577  	includes = [
   578  	{{range .NogoIncludes }}
   579  		"{{ . }}",
   580  	{{ end }}
   581  	],
   582  	{{ else }}
   583  	includes = ["all"],
   584  	{{ end}}
   585  	{{ if .NogoExcludes }}
   586  	excludes = [
   587  	{{range .NogoExcludes }}
   588  		"{{ . }}",
   589  	{{ end }}
   590  	],
   591  	{{ else }}
   592  	excludes = None,
   593  	{{ end}}
   594  )
   595  {{end}}
   596  {{end}}
   597  {{.Suffix}}
   598  `))
   599  
   600  type moduleFileTemplateInfo struct {
   601  	RulesGoPath string
   602  	Suffix      string
   603  }
   604  
   605  var defaultModuleBazelTpl = template.Must(template.New("").Parse(`
   606  bazel_dep(name = "rules_go", version = "", repo_name = "io_bazel_rules_go")
   607  local_path_override(
   608      module_name = "rules_go",
   609      path = "{{.RulesGoPath}}",
   610  )
   611  {{.Suffix}}
   612  `))
   613  
   614  func copyOrLink(dstPath, srcPath string) error {
   615  	if err := os.MkdirAll(filepath.Dir(dstPath), 0777); err != nil {
   616  		return err
   617  	}
   618  
   619  	copy := func(dstPath, srcPath string) (err error) {
   620  		src, err := os.Open(srcPath)
   621  		if err != nil {
   622  			return err
   623  		}
   624  		defer src.Close()
   625  
   626  		dst, err := os.Create(dstPath)
   627  		if err != nil {
   628  			return err
   629  		}
   630  		defer func() {
   631  			if cerr := dst.Close(); err == nil && cerr != nil {
   632  				err = cerr
   633  			}
   634  		}()
   635  
   636  		_, err = io.Copy(dst, src)
   637  		return err
   638  	}
   639  
   640  	if runtime.GOOS == "windows" {
   641  		return copy(dstPath, srcPath)
   642  	}
   643  	absSrcPath, err := filepath.Abs(srcPath)
   644  	if err != nil {
   645  		return err
   646  	}
   647  	return os.Symlink(absSrcPath, dstPath)
   648  }
   649  

View as plain text