github.com/bir3/gocompiler@v0.3.205/src/cmd/gocmd/internal/modget/query.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 modget 6 7 import ( 8 "fmt" 9 "path/filepath" 10 "regexp" 11 "strings" 12 "sync" 13 14 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/base" 15 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/modload" 16 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/search" 17 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/str" 18 "github.com/bir3/gocompiler/src/cmd/internal/pkgpattern" 19 20 "github.com/bir3/gocompiler/src/xvendor/golang.org/x/mod/module" 21 ) 22 23 // A query describes a command-line argument and the modules and/or packages 24 // to which that argument may resolve.. 25 type query struct { 26 // raw is the original argument, to be printed in error messages. 27 raw string 28 29 // rawVersion is the portion of raw corresponding to version, if any 30 rawVersion string 31 32 // pattern is the part of the argument before "@" (or the whole argument 33 // if there is no "@"), which may match either packages (preferred) or 34 // modules (if no matching packages). 35 // 36 // The pattern may also be "-u", for the synthetic query representing the -u 37 // (“upgrade”)flag. 38 pattern string 39 40 // patternIsLocal indicates whether pattern is restricted to match only paths 41 // local to the main module, such as absolute filesystem paths or paths 42 // beginning with './'. 43 // 44 // A local pattern must resolve to one or more packages in the main module. 45 patternIsLocal bool 46 47 // version is the part of the argument after "@", or an implied 48 // "upgrade" or "patch" if there is no "@". version specifies the 49 // module version to get. 50 version string 51 52 // matchWildcard, if non-nil, reports whether pattern, which must be a 53 // wildcard (with the substring "..."), matches the given package or module 54 // path. 55 matchWildcard func(path string) bool 56 57 // canMatchWildcard, if non-nil, reports whether the module with the given 58 // path could lexically contain a package matching pattern, which must be a 59 // wildcard. 60 canMatchWildcardInModule func(mPath string) bool 61 62 // conflict is the first query identified as incompatible with this one. 63 // conflict forces one or more of the modules matching this query to a 64 // version that does not match version. 65 conflict *query 66 67 // candidates is a list of sets of alternatives for a path that matches (or 68 // contains packages that match) the pattern. The query can be resolved by 69 // choosing exactly one alternative from each set in the list. 70 // 71 // A path-literal query results in only one set: the path itself, which 72 // may resolve to either a package path or a module path. 73 // 74 // A wildcard query results in one set for each matching module path, each 75 // module for which the matching version contains at least one matching 76 // package, and (if no other modules match) one candidate set for the pattern 77 // overall if no existing match is identified in the build list. 78 // 79 // A query for pattern "all" results in one set for each package transitively 80 // imported by the main module. 81 // 82 // The special query for the "-u" flag results in one set for each 83 // otherwise-unconstrained package that has available upgrades. 84 candidates []pathSet 85 candidatesMu sync.Mutex 86 87 // pathSeen ensures that only one pathSet is added to the query per 88 // unique path. 89 pathSeen sync.Map 90 91 // resolved contains the set of modules whose versions have been determined by 92 // this query, in the order in which they were determined. 93 // 94 // The resolver examines the candidate sets for each query, resolving one 95 // module per candidate set in a way that attempts to avoid obvious conflicts 96 // between the versions resolved by different queries. 97 resolved []module.Version 98 99 // matchesPackages is true if the resolved modules provide at least one 100 // package mathcing q.pattern. 101 matchesPackages bool 102 } 103 104 // A pathSet describes the possible options for resolving a specific path 105 // to a package and/or module. 106 type pathSet struct { 107 // path is a package (if "all" or "-u" or a non-wildcard) or module (if 108 // wildcard) path that could be resolved by adding any of the modules in this 109 // set. For a wildcard pattern that so far matches no packages, the path is 110 // the wildcard pattern itself. 111 // 112 // Each path must occur only once in a query's candidate sets, and the path is 113 // added implicitly to each pathSet returned to pathOnce. 114 path string 115 116 // pkgMods is a set of zero or more modules, each of which contains the 117 // package with the indicated path. Due to the requirement that imports be 118 // unambiguous, only one such module can be in the build list, and all others 119 // must be excluded. 120 pkgMods []module.Version 121 122 // mod is either the zero Version, or a module that does not contain any 123 // packages matching the query but for which the module path itself 124 // matches the query pattern. 125 // 126 // We track this module separately from pkgMods because, all else equal, we 127 // prefer to match a query to a package rather than just a module. Also, 128 // unlike the modules in pkgMods, this module does not inherently exclude 129 // any other module in pkgMods. 130 mod module.Version 131 132 err error 133 } 134 135 // errSet returns a pathSet containing the given error. 136 func errSet(err error) pathSet { return pathSet{err: err} } 137 138 // newQuery returns a new query parsed from the raw argument, 139 // which must be either path or path@version. 140 func newQuery(raw string) (*query, error) { 141 pattern, rawVers, found := strings.Cut(raw, "@") 142 if found && (strings.Contains(rawVers, "@") || rawVers == "") { 143 return nil, fmt.Errorf("invalid module version syntax %q", raw) 144 } 145 146 // If no version suffix is specified, assume @upgrade. 147 // If -u=patch was specified, assume @patch instead. 148 version := rawVers 149 if version == "" { 150 if getU.version == "" { 151 version = "upgrade" 152 } else { 153 version = getU.version 154 } 155 } 156 157 q := &query{ 158 raw: raw, 159 rawVersion: rawVers, 160 pattern: pattern, 161 patternIsLocal: filepath.IsAbs(pattern) || search.IsRelativePath(pattern), 162 version: version, 163 } 164 if strings.Contains(q.pattern, "...") { 165 q.matchWildcard = pkgpattern.MatchPattern(q.pattern) 166 q.canMatchWildcardInModule = pkgpattern.TreeCanMatchPattern(q.pattern) 167 } 168 if err := q.validate(); err != nil { 169 return q, err 170 } 171 return q, nil 172 } 173 174 // validate reports a non-nil error if q is not sensible and well-formed. 175 func (q *query) validate() error { 176 if q.patternIsLocal { 177 if q.rawVersion != "" { 178 return fmt.Errorf("can't request explicit version %q of path %q in main module", q.rawVersion, q.pattern) 179 } 180 return nil 181 } 182 183 if q.pattern == "all" { 184 // If there is no main module, "all" is not meaningful. 185 if !modload.HasModRoot() { 186 return fmt.Errorf(`cannot match "all": %v`, modload.ErrNoModRoot) 187 } 188 if !versionOkForMainModule(q.version) { 189 // TODO(bcmills): "all@none" seems like a totally reasonable way to 190 // request that we remove all module requirements, leaving only the main 191 // module and standard library. Perhaps we should implement that someday. 192 return &modload.QueryUpgradesAllError{ 193 MainModules: modload.MainModules.Versions(), 194 Query: q.version, 195 } 196 } 197 } 198 199 if search.IsMetaPackage(q.pattern) && q.pattern != "all" { 200 if q.pattern != q.raw { 201 return fmt.Errorf("can't request explicit version of standard-library pattern %q", q.pattern) 202 } 203 } 204 205 return nil 206 } 207 208 // String returns the original argument from which q was parsed. 209 func (q *query) String() string { return q.raw } 210 211 // ResolvedString returns a string describing m as a resolved match for q. 212 func (q *query) ResolvedString(m module.Version) string { 213 if m.Path != q.pattern { 214 if m.Version != q.version { 215 return fmt.Sprintf("%v (matching %s@%s)", m, q.pattern, q.version) 216 } 217 return fmt.Sprintf("%v (matching %v)", m, q) 218 } 219 if m.Version != q.version { 220 return fmt.Sprintf("%s@%s (%s)", q.pattern, q.version, m.Version) 221 } 222 return q.String() 223 } 224 225 // isWildcard reports whether q is a pattern that can match multiple paths. 226 func (q *query) isWildcard() bool { 227 return q.matchWildcard != nil || (q.patternIsLocal && strings.Contains(q.pattern, "...")) 228 } 229 230 // matchesPath reports whether the given path matches q.pattern. 231 func (q *query) matchesPath(path string) bool { 232 if q.matchWildcard != nil { 233 return q.matchWildcard(path) 234 } 235 return path == q.pattern 236 } 237 238 // canMatchInModule reports whether the given module path can potentially 239 // contain q.pattern. 240 func (q *query) canMatchInModule(mPath string) bool { 241 if q.canMatchWildcardInModule != nil { 242 return q.canMatchWildcardInModule(mPath) 243 } 244 return str.HasPathPrefix(q.pattern, mPath) 245 } 246 247 // pathOnce invokes f to generate the pathSet for the given path, 248 // if one is still needed. 249 // 250 // Note that, unlike sync.Once, pathOnce does not guarantee that a concurrent 251 // call to f for the given path has completed on return. 252 // 253 // pathOnce is safe for concurrent use by multiple goroutines, but note that 254 // multiple concurrent calls will result in the sets being added in 255 // nondeterministic order. 256 func (q *query) pathOnce(path string, f func() pathSet) { 257 if _, dup := q.pathSeen.LoadOrStore(path, nil); dup { 258 return 259 } 260 261 cs := f() 262 263 if len(cs.pkgMods) > 0 || cs.mod != (module.Version{}) || cs.err != nil { 264 cs.path = path 265 q.candidatesMu.Lock() 266 q.candidates = append(q.candidates, cs) 267 q.candidatesMu.Unlock() 268 } 269 } 270 271 // reportError logs err concisely using base.Errorf. 272 func reportError(q *query, err error) { 273 errStr := err.Error() 274 275 // If err already mentions all of the relevant parts of q, just log err to 276 // reduce stutter. Otherwise, log both q and err. 277 // 278 // TODO(bcmills): Use errors.As to unpack these errors instead of parsing 279 // strings with regular expressions. 280 281 patternRE := regexp.MustCompile("(?m)(?:[ \t(\"`]|^)" + regexp.QuoteMeta(q.pattern) + "(?:[ @:;)\"`]|$)") 282 if patternRE.MatchString(errStr) { 283 if q.rawVersion == "" { 284 base.Errorf("go: %s", errStr) 285 return 286 } 287 288 versionRE := regexp.MustCompile("(?m)(?:[ @(\"`]|^)" + regexp.QuoteMeta(q.version) + "(?:[ :;)\"`]|$)") 289 if versionRE.MatchString(errStr) { 290 base.Errorf("go: %s", errStr) 291 return 292 } 293 } 294 295 if qs := q.String(); qs != "" { 296 base.Errorf("go: %s: %s", qs, errStr) 297 } else { 298 base.Errorf("go: %s", errStr) 299 } 300 } 301 302 func reportConflict(pq *query, m module.Version, conflict versionReason) { 303 if pq.conflict != nil { 304 // We've already reported a conflict for the proposed query. 305 // Don't report it again, even if it has other conflicts. 306 return 307 } 308 pq.conflict = conflict.reason 309 310 proposed := versionReason{ 311 version: m.Version, 312 reason: pq, 313 } 314 if pq.isWildcard() && !conflict.reason.isWildcard() { 315 // Prefer to report the specific path first and the wildcard second. 316 proposed, conflict = conflict, proposed 317 } 318 reportError(pq, &conflictError{ 319 mPath: m.Path, 320 proposed: proposed, 321 conflict: conflict, 322 }) 323 } 324 325 type conflictError struct { 326 mPath string 327 proposed versionReason 328 conflict versionReason 329 } 330 331 func (e *conflictError) Error() string { 332 argStr := func(q *query, v string) string { 333 if v != q.version { 334 return fmt.Sprintf("%s@%s (%s)", q.pattern, q.version, v) 335 } 336 return q.String() 337 } 338 339 pq := e.proposed.reason 340 rq := e.conflict.reason 341 modDetail := "" 342 if e.mPath != pq.pattern { 343 modDetail = fmt.Sprintf("for module %s, ", e.mPath) 344 } 345 346 return fmt.Sprintf("%s%s conflicts with %s", 347 modDetail, 348 argStr(pq, e.proposed.version), 349 argStr(rq, e.conflict.version)) 350 } 351 352 func versionOkForMainModule(version string) bool { 353 return version == "upgrade" || version == "patch" 354 }