github.com/please-build/go-rules/tools/please_go@v0.0.0-20240319165128-ea27d6f5caba/generate/gomoddeps/gomoddeps.go (about)

     1  // Package gomoddeps parses dependencies and replacements from the host and module go.mod files.
     2  package gomoddeps
     3  
     4  import (
     5  	"errors"
     6  	"fmt"
     7  	"io/fs"
     8  	"os"
     9  
    10  	"golang.org/x/mod/modfile"
    11  )
    12  
    13  // GetCombinedDepsAndReplacements returns dependencies and replacements after inspecting both
    14  // the host and the module go.mod files.
    15  // Module's replacement are only returned if there is no host go.mod file.
    16  func GetCombinedDepsAndReplacements(hostGoModPath, moduleGoModPath string) ([]string, map[string]string, error) {
    17  	var err error
    18  
    19  	hostDeps := []string{}
    20  	hostReplacements := map[string]string{}
    21  	if hostGoModPath != "" {
    22  		hostDeps, hostReplacements, err = getDepsAndReplacements(hostGoModPath, false)
    23  		if err != nil {
    24  			return nil, nil, fmt.Errorf("failed to read host repo go.mod %q: %w", hostGoModPath, err)
    25  		}
    26  	}
    27  
    28  	var moduleDeps []string
    29  	var moduleReplacements = map[string]string{}
    30  	useLaxParsingForModule := true
    31  	if hostGoModPath == "" {
    32  		// If we're only considering the module then we want to extract the replacement's as well (lax mode
    33  		// doesn't parse them).
    34  		useLaxParsingForModule = false
    35  	}
    36  	moduleDeps, moduleReplacements, err = getDepsAndReplacements(moduleGoModPath, useLaxParsingForModule)
    37  	if err != nil {
    38  		if errors.Is(err, fs.ErrNotExist) {
    39  			return hostDeps, hostReplacements, nil
    40  		}
    41  		return nil, nil, fmt.Errorf("failed to read module go.mod %q: %w", moduleGoModPath, err)
    42  	}
    43  
    44  	var replacements map[string]string
    45  	if hostGoModPath == "" {
    46  		replacements = moduleReplacements
    47  	} else {
    48  		replacements = hostReplacements
    49  	}
    50  
    51  	return append(hostDeps, moduleDeps...), replacements, nil
    52  }
    53  
    54  // getDepsAndReplacements parses the go.mod file and returns all the dependencies
    55  // and replacement directives from it.
    56  func getDepsAndReplacements(goModPath string, useLaxParsing bool) ([]string, map[string]string, error) {
    57  	data, err := os.ReadFile(goModPath)
    58  	if err != nil {
    59  		return nil, nil, err
    60  	}
    61  
    62  	var modFile *modfile.File
    63  	if useLaxParsing {
    64  		modFile, err = modfile.ParseLax(goModPath, data, nil)
    65  	} else {
    66  		modFile, err = modfile.Parse(goModPath, data, nil)
    67  	}
    68  	if err != nil {
    69  		return nil, nil, err
    70  	}
    71  
    72  	moduleDeps := make([]string, 0, len(modFile.Require))
    73  	// TODO we could probably validate these are known modules
    74  	for _, req := range modFile.Require {
    75  		moduleDeps = append(moduleDeps, req.Mod.Path)
    76  	}
    77  
    78  	replacements := make(map[string]string, len(modFile.Replace))
    79  	for _, replace := range modFile.Replace {
    80  		replacements[replace.Old.Path] = replace.New.Path
    81  	}
    82  
    83  	return moduleDeps, replacements, nil
    84  }