github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/modindex/build.go (about) 1 // Copyright 2011 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 // This file is a lightly modified copy go/build/build.go with unused parts 6 // removed. 7 8 package modindex 9 10 import ( 11 "bytes" 12 "errors" 13 "fmt" 14 "go/ast" 15 "go/build" 16 "go/build/constraint" 17 "go/token" 18 "io" 19 "io/fs" 20 "path/filepath" 21 "sort" 22 "strings" 23 "unicode" 24 "unicode/utf8" 25 26 "github.com/go-asm/go/cmd/go/fsys" 27 "github.com/go-asm/go/cmd/go/str" 28 ) 29 30 // A Context specifies the supporting context for a build. 31 type Context struct { 32 GOARCH string // target architecture 33 GOOS string // target operating system 34 GOROOT string // Go root 35 GOPATH string // Go paths 36 37 // Dir is the caller's working directory, or the empty string to use 38 // the current directory of the running process. In module mode, this is used 39 // to locate the main module. 40 // 41 // If Dir is non-empty, directories passed to Import and ImportDir must 42 // be absolute. 43 Dir string 44 45 CgoEnabled bool // whether cgo files are included 46 UseAllFiles bool // use files regardless of //go:build lines, file names 47 Compiler string // compiler to assume when computing target paths 48 49 // The build, tool, and release tags specify build constraints 50 // that should be considered satisfied when processing +build lines. 51 // Clients creating a new context may customize BuildTags, which 52 // defaults to empty, but it is usually an error to customize ToolTags or ReleaseTags. 53 // ToolTags defaults to build tags appropriate to the current Go toolchain configuration. 54 // ReleaseTags defaults to the list of Go releases the current release is compatible with. 55 // BuildTags is not set for the Default build Context. 56 // In addition to the BuildTags, ToolTags, and ReleaseTags, build constraints 57 // consider the values of GOARCH and GOOS as satisfied tags. 58 // The last element in ReleaseTags is assumed to be the current release. 59 BuildTags []string 60 ToolTags []string 61 ReleaseTags []string 62 63 // The install suffix specifies a suffix to use in the name of the installation 64 // directory. By default it is empty, but custom builds that need to keep 65 // their outputs separate can set InstallSuffix to do so. For example, when 66 // using the race detector, the go command uses InstallSuffix = "race", so 67 // that on a Linux/386 system, packages are written to a directory named 68 // "linux_386_race" instead of the usual "linux_386". 69 InstallSuffix string 70 71 // By default, Import uses the operating system's file system calls 72 // to read directories and files. To read from other sources, 73 // callers can set the following functions. They all have default 74 // behaviors that use the local file system, so clients need only set 75 // the functions whose behaviors they wish to change. 76 77 // JoinPath joins the sequence of path fragments into a single path. 78 // If JoinPath is nil, Import uses filepath.Join. 79 JoinPath func(elem ...string) string 80 81 // SplitPathList splits the path list into a slice of individual paths. 82 // If SplitPathList is nil, Import uses filepath.SplitList. 83 SplitPathList func(list string) []string 84 85 // IsAbsPath reports whether path is an absolute path. 86 // If IsAbsPath is nil, Import uses filepath.IsAbs. 87 IsAbsPath func(path string) bool 88 89 // IsDir reports whether the path names a directory. 90 // If IsDir is nil, Import calls os.Stat and uses the result's IsDir method. 91 IsDir func(path string) bool 92 93 // HasSubdir reports whether dir is lexically a subdirectory of 94 // root, perhaps multiple levels below. It does not try to check 95 // whether dir exists. 96 // If so, HasSubdir sets rel to a slash-separated path that 97 // can be joined to root to produce a path equivalent to dir. 98 // If HasSubdir is nil, Import uses an implementation built on 99 // filepath.EvalSymlinks. 100 HasSubdir func(root, dir string) (rel string, ok bool) 101 102 // ReadDir returns a slice of fs.FileInfo, sorted by Name, 103 // describing the content of the named directory. 104 // If ReadDir is nil, Import uses ioutil.ReadDir. 105 ReadDir func(dir string) ([]fs.FileInfo, error) 106 107 // OpenFile opens a file (not a directory) for reading. 108 // If OpenFile is nil, Import uses os.Open. 109 OpenFile func(path string) (io.ReadCloser, error) 110 } 111 112 // joinPath calls ctxt.JoinPath (if not nil) or else filepath.Join. 113 func (ctxt *Context) joinPath(elem ...string) string { 114 if f := ctxt.JoinPath; f != nil { 115 return f(elem...) 116 } 117 return filepath.Join(elem...) 118 } 119 120 // splitPathList calls ctxt.SplitPathList (if not nil) or else filepath.SplitList. 121 func (ctxt *Context) splitPathList(s string) []string { 122 if f := ctxt.SplitPathList; f != nil { 123 return f(s) 124 } 125 return filepath.SplitList(s) 126 } 127 128 // isAbsPath calls ctxt.IsAbsPath (if not nil) or else filepath.IsAbs. 129 func (ctxt *Context) isAbsPath(path string) bool { 130 if f := ctxt.IsAbsPath; f != nil { 131 return f(path) 132 } 133 return filepath.IsAbs(path) 134 } 135 136 // isDir calls ctxt.IsDir (if not nil) or else uses fsys.Stat. 137 func isDir(path string) bool { 138 fi, err := fsys.Stat(path) 139 return err == nil && fi.IsDir() 140 } 141 142 // hasSubdir calls ctxt.HasSubdir (if not nil) or else uses 143 // the local file system to answer the question. 144 func (ctxt *Context) hasSubdir(root, dir string) (rel string, ok bool) { 145 if f := ctxt.HasSubdir; f != nil { 146 return f(root, dir) 147 } 148 149 // Try using paths we received. 150 if rel, ok = hasSubdir(root, dir); ok { 151 return 152 } 153 154 // Try expanding symlinks and comparing 155 // expanded against unexpanded and 156 // expanded against expanded. 157 rootSym, _ := filepath.EvalSymlinks(root) 158 dirSym, _ := filepath.EvalSymlinks(dir) 159 160 if rel, ok = hasSubdir(rootSym, dir); ok { 161 return 162 } 163 if rel, ok = hasSubdir(root, dirSym); ok { 164 return 165 } 166 return hasSubdir(rootSym, dirSym) 167 } 168 169 // hasSubdir reports if dir is within root by performing lexical analysis only. 170 func hasSubdir(root, dir string) (rel string, ok bool) { 171 root = str.WithFilePathSeparator(filepath.Clean(root)) 172 dir = filepath.Clean(dir) 173 if !strings.HasPrefix(dir, root) { 174 return "", false 175 } 176 return filepath.ToSlash(dir[len(root):]), true 177 } 178 179 // gopath returns the list of Go path directories. 180 func (ctxt *Context) gopath() []string { 181 var all []string 182 for _, p := range ctxt.splitPathList(ctxt.GOPATH) { 183 if p == "" || p == ctxt.GOROOT { 184 // Empty paths are uninteresting. 185 // If the path is the GOROOT, ignore it. 186 // People sometimes set GOPATH=$GOROOT. 187 // Do not get confused by this common mistake. 188 continue 189 } 190 if strings.HasPrefix(p, "~") { 191 // Path segments starting with ~ on Unix are almost always 192 // users who have incorrectly quoted ~ while setting GOPATH, 193 // preventing it from expanding to $HOME. 194 // The situation is made more confusing by the fact that 195 // bash allows quoted ~ in $PATH (most shells do not). 196 // Do not get confused by this, and do not try to use the path. 197 // It does not exist, and printing errors about it confuses 198 // those users even more, because they think "sure ~ exists!". 199 // The go command diagnoses this situation and prints a 200 // useful error. 201 // On Windows, ~ is used in short names, such as c:\progra~1 202 // for c:\program files. 203 continue 204 } 205 all = append(all, p) 206 } 207 return all 208 } 209 210 var defaultToolTags, defaultReleaseTags []string 211 212 // NoGoError is the error used by Import to describe a directory 213 // containing no buildable Go source files. (It may still contain 214 // test files, files hidden by build tags, and so on.) 215 type NoGoError struct { 216 Dir string 217 } 218 219 func (e *NoGoError) Error() string { 220 return "no buildable Go source files in " + e.Dir 221 } 222 223 // MultiplePackageError describes a directory containing 224 // multiple buildable Go source files for multiple packages. 225 type MultiplePackageError struct { 226 Dir string // directory containing files 227 Packages []string // package names found 228 Files []string // corresponding files: Files[i] declares package Packages[i] 229 } 230 231 func (e *MultiplePackageError) Error() string { 232 // Error string limited to two entries for compatibility. 233 return fmt.Sprintf("found packages %s (%s) and %s (%s) in %s", e.Packages[0], e.Files[0], e.Packages[1], e.Files[1], e.Dir) 234 } 235 236 func nameExt(name string) string { 237 i := strings.LastIndex(name, ".") 238 if i < 0 { 239 return "" 240 } 241 return name[i:] 242 } 243 244 func fileListForExt(p *build.Package, ext string) *[]string { 245 switch ext { 246 case ".c": 247 return &p.CFiles 248 case ".cc", ".cpp", ".cxx": 249 return &p.CXXFiles 250 case ".m": 251 return &p.MFiles 252 case ".h", ".hh", ".hpp", ".hxx": 253 return &p.HFiles 254 case ".f", ".F", ".for", ".f90": 255 return &p.FFiles 256 case ".s", ".S", ".sx": 257 return &p.SFiles 258 case ".swig": 259 return &p.SwigFiles 260 case ".swigcxx": 261 return &p.SwigCXXFiles 262 case ".syso": 263 return &p.SysoFiles 264 } 265 return nil 266 } 267 268 var errNoModules = errors.New("not using modules") 269 270 func findImportComment(data []byte) (s string, line int) { 271 // expect keyword package 272 word, data := parseWord(data) 273 if string(word) != "package" { 274 return "", 0 275 } 276 277 // expect package name 278 _, data = parseWord(data) 279 280 // now ready for import comment, a // or /* */ comment 281 // beginning and ending on the current line. 282 for len(data) > 0 && (data[0] == ' ' || data[0] == '\t' || data[0] == '\r') { 283 data = data[1:] 284 } 285 286 var comment []byte 287 switch { 288 case bytes.HasPrefix(data, slashSlash): 289 comment, _, _ = bytes.Cut(data[2:], newline) 290 case bytes.HasPrefix(data, slashStar): 291 var ok bool 292 comment, _, ok = bytes.Cut(data[2:], starSlash) 293 if !ok { 294 // malformed comment 295 return "", 0 296 } 297 if bytes.Contains(comment, newline) { 298 return "", 0 299 } 300 } 301 comment = bytes.TrimSpace(comment) 302 303 // split comment into `import`, `"pkg"` 304 word, arg := parseWord(comment) 305 if string(word) != "import" { 306 return "", 0 307 } 308 309 line = 1 + bytes.Count(data[:cap(data)-cap(arg)], newline) 310 return strings.TrimSpace(string(arg)), line 311 } 312 313 var ( 314 slashSlash = []byte("//") 315 slashStar = []byte("/*") 316 starSlash = []byte("*/") 317 newline = []byte("\n") 318 ) 319 320 // skipSpaceOrComment returns data with any leading spaces or comments removed. 321 func skipSpaceOrComment(data []byte) []byte { 322 for len(data) > 0 { 323 switch data[0] { 324 case ' ', '\t', '\r', '\n': 325 data = data[1:] 326 continue 327 case '/': 328 if bytes.HasPrefix(data, slashSlash) { 329 i := bytes.Index(data, newline) 330 if i < 0 { 331 return nil 332 } 333 data = data[i+1:] 334 continue 335 } 336 if bytes.HasPrefix(data, slashStar) { 337 data = data[2:] 338 i := bytes.Index(data, starSlash) 339 if i < 0 { 340 return nil 341 } 342 data = data[i+2:] 343 continue 344 } 345 } 346 break 347 } 348 return data 349 } 350 351 // parseWord skips any leading spaces or comments in data 352 // and then parses the beginning of data as an identifier or keyword, 353 // returning that word and what remains after the word. 354 func parseWord(data []byte) (word, rest []byte) { 355 data = skipSpaceOrComment(data) 356 357 // Parse past leading word characters. 358 rest = data 359 for { 360 r, size := utf8.DecodeRune(rest) 361 if unicode.IsLetter(r) || '0' <= r && r <= '9' || r == '_' { 362 rest = rest[size:] 363 continue 364 } 365 break 366 } 367 368 word = data[:len(data)-len(rest)] 369 if len(word) == 0 { 370 return nil, nil 371 } 372 373 return word, rest 374 } 375 376 var dummyPkg build.Package 377 378 // fileInfo records information learned about a file included in a build. 379 type fileInfo struct { 380 name string // full name including dir 381 header []byte 382 fset *token.FileSet 383 parsed *ast.File 384 parseErr error 385 imports []fileImport 386 embeds []fileEmbed 387 directives []build.Directive 388 389 // Additional fields added to go/build's fileinfo for the purposes of the modindex package. 390 binaryOnly bool 391 goBuildConstraint string 392 plusBuildConstraints []string 393 } 394 395 type fileImport struct { 396 path string 397 pos token.Pos 398 doc *ast.CommentGroup 399 } 400 401 type fileEmbed struct { 402 pattern string 403 pos token.Position 404 } 405 406 var errNonSource = errors.New("non source file") 407 408 // getFileInfo extracts the information needed from each go file for the module 409 // index. 410 // 411 // If Name denotes a Go program, matchFile reads until the end of the 412 // Imports and returns that section of the file in the FileInfo's Header field, 413 // even though it only considers text until the first non-comment 414 // for +build lines. 415 // 416 // getFileInfo will return errNonSource if the file is not a source or object 417 // file and shouldn't even be added to IgnoredFiles. 418 func getFileInfo(dir, name string, fset *token.FileSet) (*fileInfo, error) { 419 if strings.HasPrefix(name, "_") || 420 strings.HasPrefix(name, ".") { 421 return nil, nil 422 } 423 424 i := strings.LastIndex(name, ".") 425 if i < 0 { 426 i = len(name) 427 } 428 ext := name[i:] 429 430 if ext != ".go" && fileListForExt(&dummyPkg, ext) == nil { 431 // skip 432 return nil, errNonSource 433 } 434 435 info := &fileInfo{name: filepath.Join(dir, name), fset: fset} 436 if ext == ".syso" { 437 // binary, no reading 438 return info, nil 439 } 440 441 f, err := fsys.Open(info.name) 442 if err != nil { 443 return nil, err 444 } 445 446 // TODO(matloob) should we decide whether to ignore binary only here or earlier 447 // when we create the index file? 448 var ignoreBinaryOnly bool 449 if strings.HasSuffix(name, ".go") { 450 err = readGoInfo(f, info) 451 if strings.HasSuffix(name, "_test.go") { 452 ignoreBinaryOnly = true // ignore //go:binary-only-package comments in _test.go files 453 } 454 } else { 455 info.header, err = readComments(f) 456 } 457 f.Close() 458 if err != nil { 459 return nil, fmt.Errorf("read %s: %v", info.name, err) 460 } 461 462 // Look for +build comments to accept or reject the file. 463 info.goBuildConstraint, info.plusBuildConstraints, info.binaryOnly, err = getConstraints(info.header) 464 if err != nil { 465 return nil, fmt.Errorf("%s: %v", name, err) 466 } 467 468 if ignoreBinaryOnly && info.binaryOnly { 469 info.binaryOnly = false // override info.binaryOnly 470 } 471 472 return info, nil 473 } 474 475 func cleanDecls(m map[string][]token.Position) ([]string, map[string][]token.Position) { 476 all := make([]string, 0, len(m)) 477 for path := range m { 478 all = append(all, path) 479 } 480 sort.Strings(all) 481 return all, m 482 } 483 484 var ( 485 bSlashSlash = []byte(slashSlash) 486 bStarSlash = []byte(starSlash) 487 bSlashStar = []byte(slashStar) 488 bPlusBuild = []byte("+build") 489 490 goBuildComment = []byte("//go:build") 491 492 errMultipleGoBuild = errors.New("multiple //go:build comments") 493 ) 494 495 func isGoBuildComment(line []byte) bool { 496 if !bytes.HasPrefix(line, goBuildComment) { 497 return false 498 } 499 line = bytes.TrimSpace(line) 500 rest := line[len(goBuildComment):] 501 return len(rest) == 0 || len(bytes.TrimSpace(rest)) < len(rest) 502 } 503 504 // Special comment denoting a binary-only package. 505 // See https://golang.org/design/2775-binary-only-packages 506 // for more about the design of binary-only packages. 507 var binaryOnlyComment = []byte("//go:binary-only-package") 508 509 func getConstraints(content []byte) (goBuild string, plusBuild []string, binaryOnly bool, err error) { 510 // Identify leading run of // comments and blank lines, 511 // which must be followed by a blank line. 512 // Also identify any //go:build comments. 513 content, goBuildBytes, sawBinaryOnly, err := parseFileHeader(content) 514 if err != nil { 515 return "", nil, false, err 516 } 517 518 // If //go:build line is present, it controls, so no need to look for +build . 519 // Otherwise, get plusBuild constraints. 520 if goBuildBytes == nil { 521 p := content 522 for len(p) > 0 { 523 line := p 524 if i := bytes.IndexByte(line, '\n'); i >= 0 { 525 line, p = line[:i], p[i+1:] 526 } else { 527 p = p[len(p):] 528 } 529 line = bytes.TrimSpace(line) 530 if !bytes.HasPrefix(line, bSlashSlash) || !bytes.Contains(line, bPlusBuild) { 531 continue 532 } 533 text := string(line) 534 if !constraint.IsPlusBuild(text) { 535 continue 536 } 537 plusBuild = append(plusBuild, text) 538 } 539 } 540 541 return string(goBuildBytes), plusBuild, sawBinaryOnly, nil 542 } 543 544 func parseFileHeader(content []byte) (trimmed, goBuild []byte, sawBinaryOnly bool, err error) { 545 end := 0 546 p := content 547 ended := false // found non-blank, non-// line, so stopped accepting // +build lines 548 inSlashStar := false // in /* */ comment 549 550 Lines: 551 for len(p) > 0 { 552 line := p 553 if i := bytes.IndexByte(line, '\n'); i >= 0 { 554 line, p = line[:i], p[i+1:] 555 } else { 556 p = p[len(p):] 557 } 558 line = bytes.TrimSpace(line) 559 if len(line) == 0 && !ended { // Blank line 560 // Remember position of most recent blank line. 561 // When we find the first non-blank, non-// line, 562 // this "end" position marks the latest file position 563 // where a // +build line can appear. 564 // (It must appear _before_ a blank line before the non-blank, non-// line. 565 // Yes, that's confusing, which is part of why we moved to //go:build lines.) 566 // Note that ended==false here means that inSlashStar==false, 567 // since seeing a /* would have set ended==true. 568 end = len(content) - len(p) 569 continue Lines 570 } 571 if !bytes.HasPrefix(line, slashSlash) { // Not comment line 572 ended = true 573 } 574 575 if !inSlashStar && isGoBuildComment(line) { 576 if goBuild != nil { 577 return nil, nil, false, errMultipleGoBuild 578 } 579 goBuild = line 580 } 581 if !inSlashStar && bytes.Equal(line, binaryOnlyComment) { 582 sawBinaryOnly = true 583 } 584 585 Comments: 586 for len(line) > 0 { 587 if inSlashStar { 588 if i := bytes.Index(line, starSlash); i >= 0 { 589 inSlashStar = false 590 line = bytes.TrimSpace(line[i+len(starSlash):]) 591 continue Comments 592 } 593 continue Lines 594 } 595 if bytes.HasPrefix(line, bSlashSlash) { 596 continue Lines 597 } 598 if bytes.HasPrefix(line, bSlashStar) { 599 inSlashStar = true 600 line = bytes.TrimSpace(line[len(bSlashStar):]) 601 continue Comments 602 } 603 // Found non-comment text. 604 break Lines 605 } 606 } 607 608 return content[:end], goBuild, sawBinaryOnly, nil 609 } 610 611 // saveCgo saves the information from the #cgo lines in the import "C" comment. 612 // These lines set CFLAGS, CPPFLAGS, CXXFLAGS and LDFLAGS and pkg-config directives 613 // that affect the way cgo's C code is built. 614 func (ctxt *Context) saveCgo(filename string, di *build.Package, text string) error { 615 for _, line := range strings.Split(text, "\n") { 616 orig := line 617 618 // Line is 619 // #cgo [GOOS/GOARCH...] LDFLAGS: stuff 620 // 621 line = strings.TrimSpace(line) 622 if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') { 623 continue 624 } 625 626 // #cgo (nocallback|noescape) <function name> 627 if fields := strings.Fields(line); len(fields) == 3 && (fields[1] == "nocallback" || fields[1] == "noescape") { 628 continue 629 } 630 631 // Split at colon. 632 line, argstr, ok := strings.Cut(strings.TrimSpace(line[4:]), ":") 633 if !ok { 634 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) 635 } 636 637 // Parse GOOS/GOARCH stuff. 638 f := strings.Fields(line) 639 if len(f) < 1 { 640 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) 641 } 642 643 cond, verb := f[:len(f)-1], f[len(f)-1] 644 if len(cond) > 0 { 645 ok := false 646 for _, c := range cond { 647 if ctxt.matchAuto(c, nil) { 648 ok = true 649 break 650 } 651 } 652 if !ok { 653 continue 654 } 655 } 656 657 args, err := splitQuoted(argstr) 658 if err != nil { 659 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) 660 } 661 for i, arg := range args { 662 if arg, ok = expandSrcDir(arg, di.Dir); !ok { 663 return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg) 664 } 665 args[i] = arg 666 } 667 668 switch verb { 669 case "CFLAGS", "CPPFLAGS", "CXXFLAGS", "FFLAGS", "LDFLAGS": 670 // Change relative paths to absolute. 671 ctxt.makePathsAbsolute(args, di.Dir) 672 } 673 674 switch verb { 675 case "CFLAGS": 676 di.CgoCFLAGS = append(di.CgoCFLAGS, args...) 677 case "CPPFLAGS": 678 di.CgoCPPFLAGS = append(di.CgoCPPFLAGS, args...) 679 case "CXXFLAGS": 680 di.CgoCXXFLAGS = append(di.CgoCXXFLAGS, args...) 681 case "FFLAGS": 682 di.CgoFFLAGS = append(di.CgoFFLAGS, args...) 683 case "LDFLAGS": 684 di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...) 685 case "pkg-config": 686 di.CgoPkgConfig = append(di.CgoPkgConfig, args...) 687 default: 688 return fmt.Errorf("%s: invalid #cgo verb: %s", filename, orig) 689 } 690 } 691 return nil 692 } 693 694 // expandSrcDir expands any occurrence of ${SRCDIR}, making sure 695 // the result is safe for the shell. 696 func expandSrcDir(str string, srcdir string) (string, bool) { 697 // "\" delimited paths cause safeCgoName to fail 698 // so convert native paths with a different delimiter 699 // to "/" before starting (eg: on windows). 700 srcdir = filepath.ToSlash(srcdir) 701 702 chunks := strings.Split(str, "${SRCDIR}") 703 if len(chunks) < 2 { 704 return str, safeCgoName(str) 705 } 706 ok := true 707 for _, chunk := range chunks { 708 ok = ok && (chunk == "" || safeCgoName(chunk)) 709 } 710 ok = ok && (srcdir == "" || safeCgoName(srcdir)) 711 res := strings.Join(chunks, srcdir) 712 return res, ok && res != "" 713 } 714 715 // makePathsAbsolute looks for compiler options that take paths and 716 // makes them absolute. We do this because through the 1.8 release we 717 // ran the compiler in the package directory, so any relative -I or -L 718 // options would be relative to that directory. In 1.9 we changed to 719 // running the compiler in the build directory, to get consistent 720 // build results (issue #19964). To keep builds working, we change any 721 // relative -I or -L options to be absolute. 722 // 723 // Using filepath.IsAbs and filepath.Join here means the results will be 724 // different on different systems, but that's OK: -I and -L options are 725 // inherently system-dependent. 726 func (ctxt *Context) makePathsAbsolute(args []string, srcDir string) { 727 nextPath := false 728 for i, arg := range args { 729 if nextPath { 730 if !filepath.IsAbs(arg) { 731 args[i] = filepath.Join(srcDir, arg) 732 } 733 nextPath = false 734 } else if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") { 735 if len(arg) == 2 { 736 nextPath = true 737 } else { 738 if !filepath.IsAbs(arg[2:]) { 739 args[i] = arg[:2] + filepath.Join(srcDir, arg[2:]) 740 } 741 } 742 } 743 } 744 } 745 746 // NOTE: $ is not safe for the shell, but it is allowed here because of linker options like -Wl,$ORIGIN. 747 // We never pass these arguments to a shell (just to programs we construct argv for), so this should be okay. 748 // See golang.org/issue/6038. 749 // The @ is for OS X. See golang.org/issue/13720. 750 // The % is for Jenkins. See golang.org/issue/16959. 751 // The ! is because module paths may use them. See golang.org/issue/26716. 752 // The ~ and ^ are for sr.ht. See golang.org/issue/32260. 753 const safeString = "+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:$@%! ~^" 754 755 func safeCgoName(s string) bool { 756 if s == "" { 757 return false 758 } 759 for i := 0; i < len(s); i++ { 760 if c := s[i]; c < utf8.RuneSelf && strings.IndexByte(safeString, c) < 0 { 761 return false 762 } 763 } 764 return true 765 } 766 767 // splitQuoted splits the string s around each instance of one or more consecutive 768 // white space characters while taking into account quotes and escaping, and 769 // returns an array of substrings of s or an empty list if s contains only white space. 770 // Single quotes and double quotes are recognized to prevent splitting within the 771 // quoted region, and are removed from the resulting substrings. If a quote in s 772 // isn't closed err will be set and r will have the unclosed argument as the 773 // last element. The backslash is used for escaping. 774 // 775 // For example, the following string: 776 // 777 // a b:"c d" 'e''f' "g\"" 778 // 779 // Would be parsed as: 780 // 781 // []string{"a", "b:c d", "ef", `g"`} 782 func splitQuoted(s string) (r []string, err error) { 783 var args []string 784 arg := make([]rune, len(s)) 785 escaped := false 786 quoted := false 787 quote := '\x00' 788 i := 0 789 for _, rune := range s { 790 switch { 791 case escaped: 792 escaped = false 793 case rune == '\\': 794 escaped = true 795 continue 796 case quote != '\x00': 797 if rune == quote { 798 quote = '\x00' 799 continue 800 } 801 case rune == '"' || rune == '\'': 802 quoted = true 803 quote = rune 804 continue 805 case unicode.IsSpace(rune): 806 if quoted || i > 0 { 807 quoted = false 808 args = append(args, string(arg[:i])) 809 i = 0 810 } 811 continue 812 } 813 arg[i] = rune 814 i++ 815 } 816 if quoted || i > 0 { 817 args = append(args, string(arg[:i])) 818 } 819 if quote != 0 { 820 err = errors.New("unclosed quote") 821 } else if escaped { 822 err = errors.New("unfinished escaping") 823 } 824 return args, err 825 } 826 827 // matchAuto interprets text as either a +build or //go:build expression (whichever works), 828 // reporting whether the expression matches the build context. 829 // 830 // matchAuto is only used for testing of tag evaluation 831 // and in #cgo lines, which accept either syntax. 832 func (ctxt *Context) matchAuto(text string, allTags map[string]bool) bool { 833 if strings.ContainsAny(text, "&|()") { 834 text = "//go:build " + text 835 } else { 836 text = "// +build " + text 837 } 838 x, err := constraint.Parse(text) 839 if err != nil { 840 return false 841 } 842 return ctxt.eval(x, allTags) 843 } 844 845 func (ctxt *Context) eval(x constraint.Expr, allTags map[string]bool) bool { 846 return x.Eval(func(tag string) bool { return ctxt.matchTag(tag, allTags) }) 847 } 848 849 // matchTag reports whether the name is one of: 850 // 851 // cgo (if cgo is enabled) 852 // $GOOS 853 // $GOARCH 854 // boringcrypto 855 // ctxt.Compiler 856 // linux (if GOOS == android) 857 // solaris (if GOOS == illumos) 858 // tag (if tag is listed in ctxt.BuildTags or ctxt.ReleaseTags) 859 // 860 // It records all consulted tags in allTags. 861 func (ctxt *Context) matchTag(name string, allTags map[string]bool) bool { 862 if allTags != nil { 863 allTags[name] = true 864 } 865 866 // special tags 867 if ctxt.CgoEnabled && name == "cgo" { 868 return true 869 } 870 if name == ctxt.GOOS || name == ctxt.GOARCH || name == ctxt.Compiler { 871 return true 872 } 873 if ctxt.GOOS == "android" && name == "linux" { 874 return true 875 } 876 if ctxt.GOOS == "illumos" && name == "solaris" { 877 return true 878 } 879 if ctxt.GOOS == "ios" && name == "darwin" { 880 return true 881 } 882 if name == "unix" && unixOS[ctxt.GOOS] { 883 return true 884 } 885 if name == "boringcrypto" { 886 name = "goexperiment.boringcrypto" // boringcrypto is an old name for goexperiment.boringcrypto 887 } 888 889 // other tags 890 for _, tag := range ctxt.BuildTags { 891 if tag == name { 892 return true 893 } 894 } 895 for _, tag := range ctxt.ToolTags { 896 if tag == name { 897 return true 898 } 899 } 900 for _, tag := range ctxt.ReleaseTags { 901 if tag == name { 902 return true 903 } 904 } 905 906 return false 907 } 908 909 // goodOSArchFile returns false if the name contains a $GOOS or $GOARCH 910 // suffix which does not match the current system. 911 // The recognized name formats are: 912 // 913 // name_$(GOOS).* 914 // name_$(GOARCH).* 915 // name_$(GOOS)_$(GOARCH).* 916 // name_$(GOOS)_test.* 917 // name_$(GOARCH)_test.* 918 // name_$(GOOS)_$(GOARCH)_test.* 919 // 920 // Exceptions: 921 // if GOOS=android, then files with GOOS=linux are also matched. 922 // if GOOS=illumos, then files with GOOS=solaris are also matched. 923 // if GOOS=ios, then files with GOOS=darwin are also matched. 924 func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool { 925 name, _, _ = strings.Cut(name, ".") 926 927 // Before Go 1.4, a file called "linux.go" would be equivalent to having a 928 // build tag "linux" in that file. For Go 1.4 and beyond, we require this 929 // auto-tagging to apply only to files with a non-empty prefix, so 930 // "foo_linux.go" is tagged but "linux.go" is not. This allows new operating 931 // systems, such as android, to arrive without breaking existing code with 932 // innocuous source code in "android.go". The easiest fix: cut everything 933 // in the name before the initial _. 934 i := strings.Index(name, "_") 935 if i < 0 { 936 return true 937 } 938 name = name[i:] // ignore everything before first _ 939 940 l := strings.Split(name, "_") 941 if n := len(l); n > 0 && l[n-1] == "test" { 942 l = l[:n-1] 943 } 944 n := len(l) 945 if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] { 946 if allTags != nil { 947 // In case we short-circuit on l[n-1]. 948 allTags[l[n-2]] = true 949 } 950 return ctxt.matchTag(l[n-1], allTags) && ctxt.matchTag(l[n-2], allTags) 951 } 952 if n >= 1 && (knownOS[l[n-1]] || knownArch[l[n-1]]) { 953 return ctxt.matchTag(l[n-1], allTags) 954 } 955 return true 956 }