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