golang.org/x/tools/gopls@v0.15.3/internal/cache/workspace.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package cache
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"path/filepath"
    12  
    13  	"golang.org/x/mod/modfile"
    14  	"golang.org/x/tools/gopls/internal/file"
    15  	"golang.org/x/tools/gopls/internal/protocol"
    16  )
    17  
    18  // isGoWork reports if uri is a go.work file.
    19  func isGoWork(uri protocol.DocumentURI) bool {
    20  	return filepath.Base(uri.Path()) == "go.work"
    21  }
    22  
    23  // goWorkModules returns the URIs of go.mod files named by the go.work file.
    24  func goWorkModules(ctx context.Context, gowork protocol.DocumentURI, fs file.Source) (map[protocol.DocumentURI]unit, error) {
    25  	fh, err := fs.ReadFile(ctx, gowork)
    26  	if err != nil {
    27  		return nil, err // canceled
    28  	}
    29  	content, err := fh.Content()
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  	filename := gowork.Path()
    34  	dir := filepath.Dir(filename)
    35  	workFile, err := modfile.ParseWork(filename, content, nil)
    36  	if err != nil {
    37  		return nil, fmt.Errorf("parsing go.work: %w", err)
    38  	}
    39  	var usedDirs []string
    40  	for _, use := range workFile.Use {
    41  		usedDirs = append(usedDirs, use.Path)
    42  	}
    43  	return localModFiles(dir, usedDirs), nil
    44  }
    45  
    46  // localModFiles builds a set of local go.mod files referenced by
    47  // goWorkOrModPaths, which is a slice of paths as contained in a go.work 'use'
    48  // directive or go.mod 'replace' directive (and which therefore may use either
    49  // '/' or '\' as a path separator).
    50  func localModFiles(relativeTo string, goWorkOrModPaths []string) map[protocol.DocumentURI]unit {
    51  	modFiles := make(map[protocol.DocumentURI]unit)
    52  	for _, path := range goWorkOrModPaths {
    53  		modDir := filepath.FromSlash(path)
    54  		if !filepath.IsAbs(modDir) {
    55  			modDir = filepath.Join(relativeTo, modDir)
    56  		}
    57  		modURI := protocol.URIFromPath(filepath.Join(modDir, "go.mod"))
    58  		modFiles[modURI] = unit{}
    59  	}
    60  	return modFiles
    61  }
    62  
    63  // isGoMod reports if uri is a go.mod file.
    64  func isGoMod(uri protocol.DocumentURI) bool {
    65  	return filepath.Base(uri.Path()) == "go.mod"
    66  }
    67  
    68  // goModModules returns the URIs of "workspace" go.mod files defined by a
    69  // go.mod file. This set is defined to be the given go.mod file itself, as well
    70  // as the modfiles of any locally replaced modules in the go.mod file.
    71  func goModModules(ctx context.Context, gomod protocol.DocumentURI, fs file.Source) (map[protocol.DocumentURI]unit, error) {
    72  	fh, err := fs.ReadFile(ctx, gomod)
    73  	if err != nil {
    74  		return nil, err // canceled
    75  	}
    76  	content, err := fh.Content()
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	filename := gomod.Path()
    81  	dir := filepath.Dir(filename)
    82  	modFile, err := modfile.Parse(filename, content, nil)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	var localReplaces []string
    87  	for _, replace := range modFile.Replace {
    88  		if modfile.IsDirectoryPath(replace.New.Path) {
    89  			localReplaces = append(localReplaces, replace.New.Path)
    90  		}
    91  	}
    92  	modFiles := localModFiles(dir, localReplaces)
    93  	modFiles[gomod] = unit{}
    94  	return modFiles, nil
    95  }
    96  
    97  // fileExists reports whether the file has a Content (which may be empty).
    98  // An overlay exists even if it is not reflected in the file system.
    99  func fileExists(fh file.Handle) bool {
   100  	_, err := fh.Content()
   101  	return err == nil
   102  }
   103  
   104  // errExhausted is returned by findModules if the file scan limit is reached.
   105  var errExhausted = errors.New("exhausted")
   106  
   107  // Limit go.mod search to 1 million files. As a point of reference,
   108  // Kubernetes has 22K files (as of 2020-11-24).
   109  //
   110  // Note: per golang/go#56496, the previous limit of 1M files was too slow, at
   111  // which point this limit was decreased to 100K.
   112  const fileLimit = 100_000