]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/go/internal/modindex/build.go
cmd/go, go/build: parse directives in file headers
[gostls13.git] / src / cmd / go / internal / modindex / build.go
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         "cmd/go/internal/fsys"
13         "cmd/go/internal/str"
14         "errors"
15         "fmt"
16         "go/ast"
17         "go/build"
18         "go/build/constraint"
19         "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 +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                 // Split at colon.
626                 line, argstr, ok := strings.Cut(strings.TrimSpace(line[4:]), ":")
627                 if !ok {
628                         return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
629                 }
630
631                 // Parse GOOS/GOARCH stuff.
632                 f := strings.Fields(line)
633                 if len(f) < 1 {
634                         return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
635                 }
636
637                 cond, verb := f[:len(f)-1], f[len(f)-1]
638                 if len(cond) > 0 {
639                         ok := false
640                         for _, c := range cond {
641                                 if ctxt.matchAuto(c, nil) {
642                                         ok = true
643                                         break
644                                 }
645                         }
646                         if !ok {
647                                 continue
648                         }
649                 }
650
651                 args, err := splitQuoted(argstr)
652                 if err != nil {
653                         return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
654                 }
655                 for i, arg := range args {
656                         if arg, ok = expandSrcDir(arg, di.Dir); !ok {
657                                 return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg)
658                         }
659                         args[i] = arg
660                 }
661
662                 switch verb {
663                 case "CFLAGS", "CPPFLAGS", "CXXFLAGS", "FFLAGS", "LDFLAGS":
664                         // Change relative paths to absolute.
665                         ctxt.makePathsAbsolute(args, di.Dir)
666                 }
667
668                 switch verb {
669                 case "CFLAGS":
670                         di.CgoCFLAGS = append(di.CgoCFLAGS, args...)
671                 case "CPPFLAGS":
672                         di.CgoCPPFLAGS = append(di.CgoCPPFLAGS, args...)
673                 case "CXXFLAGS":
674                         di.CgoCXXFLAGS = append(di.CgoCXXFLAGS, args...)
675                 case "FFLAGS":
676                         di.CgoFFLAGS = append(di.CgoFFLAGS, args...)
677                 case "LDFLAGS":
678                         di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...)
679                 case "pkg-config":
680                         di.CgoPkgConfig = append(di.CgoPkgConfig, args...)
681                 default:
682                         return fmt.Errorf("%s: invalid #cgo verb: %s", filename, orig)
683                 }
684         }
685         return nil
686 }
687
688 // expandSrcDir expands any occurrence of ${SRCDIR}, making sure
689 // the result is safe for the shell.
690 func expandSrcDir(str string, srcdir string) (string, bool) {
691         // "\" delimited paths cause safeCgoName to fail
692         // so convert native paths with a different delimiter
693         // to "/" before starting (eg: on windows).
694         srcdir = filepath.ToSlash(srcdir)
695
696         chunks := strings.Split(str, "${SRCDIR}")
697         if len(chunks) < 2 {
698                 return str, safeCgoName(str)
699         }
700         ok := true
701         for _, chunk := range chunks {
702                 ok = ok && (chunk == "" || safeCgoName(chunk))
703         }
704         ok = ok && (srcdir == "" || safeCgoName(srcdir))
705         res := strings.Join(chunks, srcdir)
706         return res, ok && res != ""
707 }
708
709 // makePathsAbsolute looks for compiler options that take paths and
710 // makes them absolute. We do this because through the 1.8 release we
711 // ran the compiler in the package directory, so any relative -I or -L
712 // options would be relative to that directory. In 1.9 we changed to
713 // running the compiler in the build directory, to get consistent
714 // build results (issue #19964). To keep builds working, we change any
715 // relative -I or -L options to be absolute.
716 //
717 // Using filepath.IsAbs and filepath.Join here means the results will be
718 // different on different systems, but that's OK: -I and -L options are
719 // inherently system-dependent.
720 func (ctxt *Context) makePathsAbsolute(args []string, srcDir string) {
721         nextPath := false
722         for i, arg := range args {
723                 if nextPath {
724                         if !filepath.IsAbs(arg) {
725                                 args[i] = filepath.Join(srcDir, arg)
726                         }
727                         nextPath = false
728                 } else if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") {
729                         if len(arg) == 2 {
730                                 nextPath = true
731                         } else {
732                                 if !filepath.IsAbs(arg[2:]) {
733                                         args[i] = arg[:2] + filepath.Join(srcDir, arg[2:])
734                                 }
735                         }
736                 }
737         }
738 }
739
740 // NOTE: $ is not safe for the shell, but it is allowed here because of linker options like -Wl,$ORIGIN.
741 // We never pass these arguments to a shell (just to programs we construct argv for), so this should be okay.
742 // See golang.org/issue/6038.
743 // The @ is for OS X. See golang.org/issue/13720.
744 // The % is for Jenkins. See golang.org/issue/16959.
745 // The ! is because module paths may use them. See golang.org/issue/26716.
746 // The ~ and ^ are for sr.ht. See golang.org/issue/32260.
747 const safeString = "+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:$@%! ~^"
748
749 func safeCgoName(s string) bool {
750         if s == "" {
751                 return false
752         }
753         for i := 0; i < len(s); i++ {
754                 if c := s[i]; c < utf8.RuneSelf && strings.IndexByte(safeString, c) < 0 {
755                         return false
756                 }
757         }
758         return true
759 }
760
761 // splitQuoted splits the string s around each instance of one or more consecutive
762 // white space characters while taking into account quotes and escaping, and
763 // returns an array of substrings of s or an empty list if s contains only white space.
764 // Single quotes and double quotes are recognized to prevent splitting within the
765 // quoted region, and are removed from the resulting substrings. If a quote in s
766 // isn't closed err will be set and r will have the unclosed argument as the
767 // last element. The backslash is used for escaping.
768 //
769 // For example, the following string:
770 //
771 //      a b:"c d" 'e''f'  "g\""
772 //
773 // Would be parsed as:
774 //
775 //      []string{"a", "b:c d", "ef", `g"`}
776 func splitQuoted(s string) (r []string, err error) {
777         var args []string
778         arg := make([]rune, len(s))
779         escaped := false
780         quoted := false
781         quote := '\x00'
782         i := 0
783         for _, rune := range s {
784                 switch {
785                 case escaped:
786                         escaped = false
787                 case rune == '\\':
788                         escaped = true
789                         continue
790                 case quote != '\x00':
791                         if rune == quote {
792                                 quote = '\x00'
793                                 continue
794                         }
795                 case rune == '"' || rune == '\'':
796                         quoted = true
797                         quote = rune
798                         continue
799                 case unicode.IsSpace(rune):
800                         if quoted || i > 0 {
801                                 quoted = false
802                                 args = append(args, string(arg[:i]))
803                                 i = 0
804                         }
805                         continue
806                 }
807                 arg[i] = rune
808                 i++
809         }
810         if quoted || i > 0 {
811                 args = append(args, string(arg[:i]))
812         }
813         if quote != 0 {
814                 err = errors.New("unclosed quote")
815         } else if escaped {
816                 err = errors.New("unfinished escaping")
817         }
818         return args, err
819 }
820
821 // matchAuto interprets text as either a +build or //go:build expression (whichever works),
822 // reporting whether the expression matches the build context.
823 //
824 // matchAuto is only used for testing of tag evaluation
825 // and in #cgo lines, which accept either syntax.
826 func (ctxt *Context) matchAuto(text string, allTags map[string]bool) bool {
827         if strings.ContainsAny(text, "&|()") {
828                 text = "//go:build " + text
829         } else {
830                 text = "// +build " + text
831         }
832         x, err := constraint.Parse(text)
833         if err != nil {
834                 return false
835         }
836         return ctxt.eval(x, allTags)
837 }
838
839 func (ctxt *Context) eval(x constraint.Expr, allTags map[string]bool) bool {
840         return x.Eval(func(tag string) bool { return ctxt.matchTag(tag, allTags) })
841 }
842
843 // matchTag reports whether the name is one of:
844 //
845 //      cgo (if cgo is enabled)
846 //      $GOOS
847 //      $GOARCH
848 //      boringcrypto
849 //      ctxt.Compiler
850 //      linux (if GOOS == android)
851 //      solaris (if GOOS == illumos)
852 //      tag (if tag is listed in ctxt.BuildTags or ctxt.ReleaseTags)
853 //
854 // It records all consulted tags in allTags.
855 func (ctxt *Context) matchTag(name string, allTags map[string]bool) bool {
856         if allTags != nil {
857                 allTags[name] = true
858         }
859
860         // special tags
861         if ctxt.CgoEnabled && name == "cgo" {
862                 return true
863         }
864         if name == ctxt.GOOS || name == ctxt.GOARCH || name == ctxt.Compiler {
865                 return true
866         }
867         if ctxt.GOOS == "android" && name == "linux" {
868                 return true
869         }
870         if ctxt.GOOS == "illumos" && name == "solaris" {
871                 return true
872         }
873         if ctxt.GOOS == "ios" && name == "darwin" {
874                 return true
875         }
876         if name == "unix" && unixOS[ctxt.GOOS] {
877                 return true
878         }
879         if name == "boringcrypto" {
880                 name = "goexperiment.boringcrypto" // boringcrypto is an old name for goexperiment.boringcrypto
881         }
882
883         // other tags
884         for _, tag := range ctxt.BuildTags {
885                 if tag == name {
886                         return true
887                 }
888         }
889         for _, tag := range ctxt.ToolTags {
890                 if tag == name {
891                         return true
892                 }
893         }
894         for _, tag := range ctxt.ReleaseTags {
895                 if tag == name {
896                         return true
897                 }
898         }
899
900         return false
901 }
902
903 // goodOSArchFile returns false if the name contains a $GOOS or $GOARCH
904 // suffix which does not match the current system.
905 // The recognized name formats are:
906 //
907 //      name_$(GOOS).*
908 //      name_$(GOARCH).*
909 //      name_$(GOOS)_$(GOARCH).*
910 //      name_$(GOOS)_test.*
911 //      name_$(GOARCH)_test.*
912 //      name_$(GOOS)_$(GOARCH)_test.*
913 //
914 // Exceptions:
915 // if GOOS=android, then files with GOOS=linux are also matched.
916 // if GOOS=illumos, then files with GOOS=solaris are also matched.
917 // if GOOS=ios, then files with GOOS=darwin are also matched.
918 func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool {
919         name, _, _ = strings.Cut(name, ".")
920
921         // Before Go 1.4, a file called "linux.go" would be equivalent to having a
922         // build tag "linux" in that file. For Go 1.4 and beyond, we require this
923         // auto-tagging to apply only to files with a non-empty prefix, so
924         // "foo_linux.go" is tagged but "linux.go" is not. This allows new operating
925         // systems, such as android, to arrive without breaking existing code with
926         // innocuous source code in "android.go". The easiest fix: cut everything
927         // in the name before the initial _.
928         i := strings.Index(name, "_")
929         if i < 0 {
930                 return true
931         }
932         name = name[i:] // ignore everything before first _
933
934         l := strings.Split(name, "_")
935         if n := len(l); n > 0 && l[n-1] == "test" {
936                 l = l[:n-1]
937         }
938         n := len(l)
939         if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] {
940                 if allTags != nil {
941                         // In case we short-circuit on l[n-1].
942                         allTags[l[n-2]] = true
943                 }
944                 return ctxt.matchTag(l[n-1], allTags) && ctxt.matchTag(l[n-2], allTags)
945         }
946         if n >= 1 && (knownOS[l[n-1]] || knownArch[l[n-1]]) {
947                 return ctxt.matchTag(l[n-1], allTags)
948         }
949         return true
950 }