Black Lives Matter. Support the Equal Justice Initiative.

Source file src/cmd/go/internal/modload/buildlist.go

Documentation: cmd/go/internal/modload

     1  // Copyright 2018 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 modload
     6  
     7  import (
     8  	"cmd/go/internal/base"
     9  	"cmd/go/internal/cfg"
    10  	"cmd/go/internal/imports"
    11  	"cmd/go/internal/mvs"
    12  	"context"
    13  	"fmt"
    14  	"os"
    15  	"strings"
    16  
    17  	"golang.org/x/mod/module"
    18  )
    19  
    20  // buildList is the list of modules to use for building packages.
    21  // It is initialized by calling LoadPackages or ImportFromFiles,
    22  // each of which uses loaded.load.
    23  //
    24  // Ideally, exactly ONE of those functions would be called,
    25  // and exactly once. Most of the time, that's true.
    26  // During "go get" it may not be. TODO(rsc): Figure out if
    27  // that restriction can be established, or else document why not.
    28  //
    29  var buildList []module.Version
    30  
    31  // additionalExplicitRequirements is a list of modules paths for which
    32  // WriteGoMod should record explicit requirements, even if they would be
    33  // selected without those requirements. Each path must also appear in buildList.
    34  var additionalExplicitRequirements []string
    35  
    36  // capVersionSlice returns s with its cap reduced to its length.
    37  func capVersionSlice(s []module.Version) []module.Version {
    38  	return s[:len(s):len(s)]
    39  }
    40  
    41  // LoadAllModules loads and returns the list of modules matching the "all"
    42  // module pattern, starting with the Target module and in a deterministic
    43  // (stable) order, without loading any packages.
    44  //
    45  // Modules are loaded automatically (and lazily) in LoadPackages:
    46  // LoadAllModules need only be called if LoadPackages is not,
    47  // typically in commands that care about modules but no particular package.
    48  //
    49  // The caller must not modify the returned list, but may append to it.
    50  func LoadAllModules(ctx context.Context) []module.Version {
    51  	LoadModFile(ctx)
    52  	ReloadBuildList()
    53  	WriteGoMod()
    54  	return capVersionSlice(buildList)
    55  }
    56  
    57  // Selected returns the selected version of the module with the given path, or
    58  // the empty string if the given module has no selected version
    59  // (either because it is not required or because it is the Target module).
    60  func Selected(path string) (version string) {
    61  	if path == Target.Path {
    62  		return ""
    63  	}
    64  	for _, m := range buildList {
    65  		if m.Path == path {
    66  			return m.Version
    67  		}
    68  	}
    69  	return ""
    70  }
    71  
    72  // EditBuildList edits the global build list by first adding every module in add
    73  // to the existing build list, then adjusting versions (and adding or removing
    74  // requirements as needed) until every module in mustSelect is selected at the
    75  // given version.
    76  //
    77  // (Note that the newly-added modules might not be selected in the resulting
    78  // build list: they could be lower than existing requirements or conflict with
    79  // versions in mustSelect.)
    80  //
    81  // If the versions listed in mustSelect are mutually incompatible (due to one of
    82  // the listed modules requiring a higher version of another), EditBuildList
    83  // returns a *ConstraintError and leaves the build list in its previous state.
    84  func EditBuildList(ctx context.Context, add, mustSelect []module.Version) error {
    85  	var upgraded = capVersionSlice(buildList)
    86  	if len(add) > 0 {
    87  		// First, upgrade the build list with any additions.
    88  		// In theory we could just append the additions to the build list and let
    89  		// mvs.Downgrade take care of resolving the upgrades too, but the
    90  		// diagnostics from Upgrade are currently much better in case of errors.
    91  		var err error
    92  		upgraded, err = mvs.Upgrade(Target, &mvsReqs{buildList: upgraded}, add...)
    93  		if err != nil {
    94  			return err
    95  		}
    96  	}
    97  
    98  	downgraded, err := mvs.Downgrade(Target, &mvsReqs{buildList: append(upgraded, mustSelect...)}, mustSelect...)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	final, err := mvs.Upgrade(Target, &mvsReqs{buildList: downgraded}, mustSelect...)
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	selected := make(map[string]module.Version, len(final))
   109  	for _, m := range final {
   110  		selected[m.Path] = m
   111  	}
   112  	inconsistent := false
   113  	for _, m := range mustSelect {
   114  		s, ok := selected[m.Path]
   115  		if !ok {
   116  			if m.Version != "none" {
   117  				panic(fmt.Sprintf("internal error: mvs.BuildList lost %v", m))
   118  			}
   119  			continue
   120  		}
   121  		if s.Version != m.Version {
   122  			inconsistent = true
   123  			break
   124  		}
   125  	}
   126  
   127  	if !inconsistent {
   128  		buildList = final
   129  		additionalExplicitRequirements = make([]string, 0, len(mustSelect))
   130  		for _, m := range mustSelect {
   131  			if m.Version != "none" {
   132  				additionalExplicitRequirements = append(additionalExplicitRequirements, m.Path)
   133  			}
   134  		}
   135  		return nil
   136  	}
   137  
   138  	// We overshot one or more of the modules in mustSelected, which means that
   139  	// Downgrade removed something in mustSelect because it conflicted with
   140  	// something else in mustSelect.
   141  	//
   142  	// Walk the requirement graph to find the conflict.
   143  	//
   144  	// TODO(bcmills): Ideally, mvs.Downgrade (or a replacement for it) would do
   145  	// this directly.
   146  
   147  	reqs := &mvsReqs{buildList: final}
   148  	reason := map[module.Version]module.Version{}
   149  	for _, m := range mustSelect {
   150  		reason[m] = m
   151  	}
   152  	queue := mustSelect[:len(mustSelect):len(mustSelect)]
   153  	for len(queue) > 0 {
   154  		var m module.Version
   155  		m, queue = queue[0], queue[1:]
   156  		required, err := reqs.Required(m)
   157  		if err != nil {
   158  			return err
   159  		}
   160  		for _, r := range required {
   161  			if _, ok := reason[r]; !ok {
   162  				reason[r] = reason[m]
   163  				queue = append(queue, r)
   164  			}
   165  		}
   166  	}
   167  
   168  	var conflicts []Conflict
   169  	for _, m := range mustSelect {
   170  		s, ok := selected[m.Path]
   171  		if !ok {
   172  			if m.Version != "none" {
   173  				panic(fmt.Sprintf("internal error: mvs.BuildList lost %v", m))
   174  			}
   175  			continue
   176  		}
   177  		if s.Version != m.Version {
   178  			conflicts = append(conflicts, Conflict{
   179  				Source:     reason[s],
   180  				Dep:        s,
   181  				Constraint: m,
   182  			})
   183  		}
   184  	}
   185  
   186  	return &ConstraintError{
   187  		Conflicts: conflicts,
   188  	}
   189  }
   190  
   191  // A ConstraintError describes inconsistent constraints in EditBuildList
   192  type ConstraintError struct {
   193  	// Conflict lists the source of the conflict for each version in mustSelect
   194  	// that could not be selected due to the requirements of some other version in
   195  	// mustSelect.
   196  	Conflicts []Conflict
   197  }
   198  
   199  func (e *ConstraintError) Error() string {
   200  	b := new(strings.Builder)
   201  	b.WriteString("version constraints conflict:")
   202  	for _, c := range e.Conflicts {
   203  		fmt.Fprintf(b, "\n\t%v requires %v, but %v is requested", c.Source, c.Dep, c.Constraint)
   204  	}
   205  	return b.String()
   206  }
   207  
   208  // A Conflict documents that Source requires Dep, which conflicts with Constraint.
   209  // (That is, Dep has the same module path as Constraint but a higher version.)
   210  type Conflict struct {
   211  	Source     module.Version
   212  	Dep        module.Version
   213  	Constraint module.Version
   214  }
   215  
   216  // ReloadBuildList resets the state of loaded packages, then loads and returns
   217  // the build list set by EditBuildList.
   218  func ReloadBuildList() []module.Version {
   219  	loaded = loadFromRoots(loaderParams{
   220  		PackageOpts: PackageOpts{
   221  			Tags: imports.Tags(),
   222  		},
   223  		listRoots:          func() []string { return nil },
   224  		allClosesOverTests: index.allPatternClosesOverTests(), // but doesn't matter because the root list is empty.
   225  	})
   226  	return capVersionSlice(buildList)
   227  }
   228  
   229  // TidyBuildList trims the build list to the minimal requirements needed to
   230  // retain the same versions of all packages from the preceding call to
   231  // LoadPackages.
   232  func TidyBuildList() {
   233  	used := map[module.Version]bool{Target: true}
   234  	for _, pkg := range loaded.pkgs {
   235  		used[pkg.mod] = true
   236  	}
   237  
   238  	keep := []module.Version{Target}
   239  	var direct []string
   240  	for _, m := range buildList[1:] {
   241  		if used[m] {
   242  			keep = append(keep, m)
   243  			if loaded.direct[m.Path] {
   244  				direct = append(direct, m.Path)
   245  			}
   246  		} else if cfg.BuildV {
   247  			if _, ok := index.require[m]; ok {
   248  				fmt.Fprintf(os.Stderr, "unused %s\n", m.Path)
   249  			}
   250  		}
   251  	}
   252  
   253  	min, err := mvs.Req(Target, direct, &mvsReqs{buildList: keep})
   254  	if err != nil {
   255  		base.Fatalf("go: %v", err)
   256  	}
   257  	buildList = append([]module.Version{Target}, min...)
   258  }
   259  
   260  // checkMultiplePaths verifies that a given module path is used as itself
   261  // or as a replacement for another module, but not both at the same time.
   262  //
   263  // (See https://golang.org/issue/26607 and https://golang.org/issue/34650.)
   264  func checkMultiplePaths() {
   265  	firstPath := make(map[module.Version]string, len(buildList))
   266  	for _, mod := range buildList {
   267  		src := mod
   268  		if rep := Replacement(mod); rep.Path != "" {
   269  			src = rep
   270  		}
   271  		if prev, ok := firstPath[src]; !ok {
   272  			firstPath[src] = mod.Path
   273  		} else if prev != mod.Path {
   274  			base.Errorf("go: %s@%s used for two different module paths (%s and %s)", src.Path, src.Version, prev, mod.Path)
   275  		}
   276  	}
   277  	base.ExitIfErrors()
   278  }
   279  

View as plain text