]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/go/internal/modindex/build.go
ba7e47cf175fd8093588d280f1f7da8c9f0106d0
[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
387         // Additional fields added to go/build's fileinfo for the purposes of the modindex package.
388         binaryOnly           bool
389         goBuildConstraint    string
390         plusBuildConstraints []string
391 }
392
393 type fileImport struct {
394         path string
395         pos  token.Pos
396         doc  *ast.CommentGroup
397 }
398
399 type fileEmbed struct {
400         pattern string
401         pos     token.Position
402 }
403
404 var errNonSource = errors.New("non source file")
405
406 // getFileInfo extracts the information needed from each go file for the module
407 // index.
408 //
409 // If Name denotes a Go program, matchFile reads until the end of the
410 // Imports and returns that section of the file in the FileInfo's Header field,
411 // even though it only considers text until the first non-comment
412 // for +build lines.
413 //
414 // getFileInfo will return errNonSource if the file is not a source or object
415 // file and shouldn't even be added to IgnoredFiles.
416 func getFileInfo(dir, name string, fset *token.FileSet) (*fileInfo, error) {
417         if strings.HasPrefix(name, "_") ||
418                 strings.HasPrefix(name, ".") {
419                 return nil, nil
420         }
421
422         i := strings.LastIndex(name, ".")
423         if i < 0 {
424                 i = len(name)
425         }
426         ext := name[i:]
427
428         if ext != ".go" && fileListForExt(&dummyPkg, ext) == nil {
429                 // skip
430                 return nil, errNonSource
431         }
432
433         info := &fileInfo{name: filepath.Join(dir, name), fset: fset}
434         if ext == ".syso" {
435                 // binary, no reading
436                 return info, nil
437         }
438
439         f, err := fsys.Open(info.name)
440         if err != nil {
441                 return nil, err
442         }
443
444         // TODO(matloob) should we decide whether to ignore binary only here or earlier
445         // when we create the index file?
446         var ignoreBinaryOnly bool
447         if strings.HasSuffix(name, ".go") {
448                 err = readGoInfo(f, info)
449                 if strings.HasSuffix(name, "_test.go") {
450                         ignoreBinaryOnly = true // ignore //go:binary-only-package comments in _test.go files
451                 }
452         } else {
453                 info.header, err = readComments(f)
454         }
455         f.Close()
456         if err != nil {
457                 return nil, fmt.Errorf("read %s: %v", info.name, err)
458         }
459
460         // Look for +build comments to accept or reject the file.
461         info.goBuildConstraint, info.plusBuildConstraints, info.binaryOnly, err = getConstraints(info.header)
462         if err != nil {
463                 return nil, fmt.Errorf("%s: %v", name, err)
464         }
465
466         if ignoreBinaryOnly && info.binaryOnly {
467                 info.binaryOnly = false // override info.binaryOnly
468         }
469
470         return info, nil
471 }
472
473 func cleanDecls(m map[string][]token.Position) ([]string, map[string][]token.Position) {
474         all := make([]string, 0, len(m))
475         for path := range m {
476                 all = append(all, path)
477         }
478         sort.Strings(all)
479         return all, m
480 }
481
482 var (
483         bSlashSlash = []byte(slashSlash)
484         bStarSlash  = []byte(starSlash)
485         bSlashStar  = []byte(slashStar)
486         bPlusBuild  = []byte("+build")
487
488         goBuildComment = []byte("//go:build")
489
490         errMultipleGoBuild = errors.New("multiple //go:build comments")
491 )
492
493 func isGoBuildComment(line []byte) bool {
494         if !bytes.HasPrefix(line, goBuildComment) {
495                 return false
496         }
497         line = bytes.TrimSpace(line)
498         rest := line[len(goBuildComment):]
499         return len(rest) == 0 || len(bytes.TrimSpace(rest)) < len(rest)
500 }
501
502 // Special comment denoting a binary-only package.
503 // See https://golang.org/design/2775-binary-only-packages
504 // for more about the design of binary-only packages.
505 var binaryOnlyComment = []byte("//go:binary-only-package")
506
507 func getConstraints(content []byte) (goBuild string, plusBuild []string, binaryOnly bool, err error) {
508         // Identify leading run of // comments and blank lines,
509         // which must be followed by a blank line.
510         // Also identify any //go:build comments.
511         content, goBuildBytes, sawBinaryOnly, err := parseFileHeader(content)
512         if err != nil {
513                 return "", nil, false, err
514         }
515
516         // If //go:build line is present, it controls, so no need to look for +build .
517         // Otherwise, get plusBuild constraints.
518         if goBuildBytes == nil {
519                 p := content
520                 for len(p) > 0 {
521                         line := p
522                         if i := bytes.IndexByte(line, '\n'); i >= 0 {
523                                 line, p = line[:i], p[i+1:]
524                         } else {
525                                 p = p[len(p):]
526                         }
527                         line = bytes.TrimSpace(line)
528                         if !bytes.HasPrefix(line, bSlashSlash) || !bytes.Contains(line, bPlusBuild) {
529                                 continue
530                         }
531                         text := string(line)
532                         if !constraint.IsPlusBuild(text) {
533                                 continue
534                         }
535                         plusBuild = append(plusBuild, text)
536                 }
537         }
538
539         return string(goBuildBytes), plusBuild, sawBinaryOnly, nil
540 }
541
542 func parseFileHeader(content []byte) (trimmed, goBuild []byte, sawBinaryOnly bool, err error) {
543         end := 0
544         p := content
545         ended := false       // found non-blank, non-// line, so stopped accepting // +build lines
546         inSlashStar := false // in /* */ comment
547
548 Lines:
549         for len(p) > 0 {
550                 line := p
551                 if i := bytes.IndexByte(line, '\n'); i >= 0 {
552                         line, p = line[:i], p[i+1:]
553                 } else {
554                         p = p[len(p):]
555                 }
556                 line = bytes.TrimSpace(line)
557                 if len(line) == 0 && !ended { // Blank line
558                         // Remember position of most recent blank line.
559                         // When we find the first non-blank, non-// line,
560                         // this "end" position marks the latest file position
561                         // where a // +build line can appear.
562                         // (It must appear _before_ a blank line before the non-blank, non-// line.
563                         // Yes, that's confusing, which is part of why we moved to //go:build lines.)
564                         // Note that ended==false here means that inSlashStar==false,
565                         // since seeing a /* would have set ended==true.
566                         end = len(content) - len(p)
567                         continue Lines
568                 }
569                 if !bytes.HasPrefix(line, slashSlash) { // Not comment line
570                         ended = true
571                 }
572
573                 if !inSlashStar && isGoBuildComment(line) {
574                         if goBuild != nil {
575                                 return nil, nil, false, errMultipleGoBuild
576                         }
577                         goBuild = line
578                 }
579                 if !inSlashStar && bytes.Equal(line, binaryOnlyComment) {
580                         sawBinaryOnly = true
581                 }
582
583         Comments:
584                 for len(line) > 0 {
585                         if inSlashStar {
586                                 if i := bytes.Index(line, starSlash); i >= 0 {
587                                         inSlashStar = false
588                                         line = bytes.TrimSpace(line[i+len(starSlash):])
589                                         continue Comments
590                                 }
591                                 continue Lines
592                         }
593                         if bytes.HasPrefix(line, bSlashSlash) {
594                                 continue Lines
595                         }
596                         if bytes.HasPrefix(line, bSlashStar) {
597                                 inSlashStar = true
598                                 line = bytes.TrimSpace(line[len(bSlashStar):])
599                                 continue Comments
600                         }
601                         // Found non-comment text.
602                         break Lines
603                 }
604         }
605
606         return content[:end], goBuild, sawBinaryOnly, nil
607 }
608
609 // saveCgo saves the information from the #cgo lines in the import "C" comment.
610 // These lines set CFLAGS, CPPFLAGS, CXXFLAGS and LDFLAGS and pkg-config directives
611 // that affect the way cgo's C code is built.
612 func (ctxt *Context) saveCgo(filename string, di *build.Package, text string) error {
613         for _, line := range strings.Split(text, "\n") {
614                 orig := line
615
616                 // Line is
617                 //      #cgo [GOOS/GOARCH...] LDFLAGS: stuff
618                 //
619                 line = strings.TrimSpace(line)
620                 if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') {
621                         continue
622                 }
623
624                 // Split at colon.
625                 line, argstr, ok := strings.Cut(strings.TrimSpace(line[4:]), ":")
626                 if !ok {
627                         return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
628                 }
629
630                 // Parse GOOS/GOARCH stuff.
631                 f := strings.Fields(line)
632                 if len(f) < 1 {
633                         return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
634                 }
635
636                 cond, verb := f[:len(f)-1], f[len(f)-1]
637                 if len(cond) > 0 {
638                         ok := false
639                         for _, c := range cond {
640                                 if ctxt.matchAuto(c, nil) {
641                                         ok = true
642                                         break
643                                 }
644                         }
645                         if !ok {
646                                 continue
647                         }
648                 }
649
650                 args, err := splitQuoted(argstr)
651                 if err != nil {
652                         return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
653                 }
654                 for i, arg := range args {
655                         if arg, ok = expandSrcDir(arg, di.Dir); !ok {
656                                 return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg)
657                         }
658                         args[i] = arg
659                 }
660
661                 switch verb {
662                 case "CFLAGS", "CPPFLAGS", "CXXFLAGS", "FFLAGS", "LDFLAGS":
663                         // Change relative paths to absolute.
664                         ctxt.makePathsAbsolute(args, di.Dir)
665                 }
666
667                 switch verb {
668                 case "CFLAGS":
669                         di.CgoCFLAGS = append(di.CgoCFLAGS, args...)
670                 case "CPPFLAGS":
671                         di.CgoCPPFLAGS = append(di.CgoCPPFLAGS, args...)
672                 case "CXXFLAGS":
673                         di.CgoCXXFLAGS = append(di.CgoCXXFLAGS, args...)
674                 case "FFLAGS":
675                         di.CgoFFLAGS = append(di.CgoFFLAGS, args...)
676                 case "LDFLAGS":
677                         di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...)
678                 case "pkg-config":
679                         di.CgoPkgConfig = append(di.CgoPkgConfig, args...)
680                 default:
681                         return fmt.Errorf("%s: invalid #cgo verb: %s", filename, orig)
682                 }
683         }
684         return nil
685 }
686
687 // expandSrcDir expands any occurrence of ${SRCDIR}, making sure
688 // the result is safe for the shell.
689 func expandSrcDir(str string, srcdir string) (string, bool) {
690         // "\" delimited paths cause safeCgoName to fail
691         // so convert native paths with a different delimiter
692         // to "/" before starting (eg: on windows).
693         srcdir = filepath.ToSlash(srcdir)
694
695         chunks := strings.Split(str, "${SRCDIR}")
696         if len(chunks) < 2 {
697                 return str, safeCgoName(str)
698         }
699         ok := true
700         for _, chunk := range chunks {
701                 ok = ok && (chunk == "" || safeCgoName(chunk))
702         }
703         ok = ok && (srcdir == "" || safeCgoName(srcdir))
704         res := strings.Join(chunks, srcdir)
705         return res, ok && res != ""
706 }
707
708 // makePathsAbsolute looks for compiler options that take paths and
709 // makes them absolute. We do this because through the 1.8 release we
710 // ran the compiler in the package directory, so any relative -I or -L
711 // options would be relative to that directory. In 1.9 we changed to
712 // running the compiler in the build directory, to get consistent
713 // build results (issue #19964). To keep builds working, we change any
714 // relative -I or -L options to be absolute.
715 //
716 // Using filepath.IsAbs and filepath.Join here means the results will be
717 // different on different systems, but that's OK: -I and -L options are
718 // inherently system-dependent.
719 func (ctxt *Context) makePathsAbsolute(args []string, srcDir string) {
720         nextPath := false
721         for i, arg := range args {
722                 if nextPath {
723                         if !filepath.IsAbs(arg) {
724                                 args[i] = filepath.Join(srcDir, arg)
725                         }
726                         nextPath = false
727                 } else if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") {
728                         if len(arg) == 2 {
729                                 nextPath = true
730                         } else {
731                                 if !filepath.IsAbs(arg[2:]) {
732                                         args[i] = arg[:2] + filepath.Join(srcDir, arg[2:])
733                                 }
734                         }
735                 }
736         }
737 }
738
739 // NOTE: $ is not safe for the shell, but it is allowed here because of linker options like -Wl,$ORIGIN.
740 // We never pass these arguments to a shell (just to programs we construct argv for), so this should be okay.
741 // See golang.org/issue/6038.
742 // The @ is for OS X. See golang.org/issue/13720.
743 // The % is for Jenkins. See golang.org/issue/16959.
744 // The ! is because module paths may use them. See golang.org/issue/26716.
745 // The ~ and ^ are for sr.ht. See golang.org/issue/32260.
746 const safeString = "+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:$@%! ~^"
747
748 func safeCgoName(s string) bool {
749         if s == "" {
750                 return false
751         }
752         for i := 0; i < len(s); i++ {
753                 if c := s[i]; c < utf8.RuneSelf && strings.IndexByte(safeString, c) < 0 {
754                         return false
755                 }
756         }
757         return true
758 }
759
760 // splitQuoted splits the string s around each instance of one or more consecutive
761 // white space characters while taking into account quotes and escaping, and
762 // returns an array of substrings of s or an empty list if s contains only white space.
763 // Single quotes and double quotes are recognized to prevent splitting within the
764 // quoted region, and are removed from the resulting substrings. If a quote in s
765 // isn't closed err will be set and r will have the unclosed argument as the
766 // last element. The backslash is used for escaping.
767 //
768 // For example, the following string:
769 //
770 //      a b:"c d" 'e''f'  "g\""
771 //
772 // Would be parsed as:
773 //
774 //      []string{"a", "b:c d", "ef", `g"`}
775 func splitQuoted(s string) (r []string, err error) {
776         var args []string
777         arg := make([]rune, len(s))
778         escaped := false
779         quoted := false
780         quote := '\x00'
781         i := 0
782         for _, rune := range s {
783                 switch {
784                 case escaped:
785                         escaped = false
786                 case rune == '\\':
787                         escaped = true
788                         continue
789                 case quote != '\x00':
790                         if rune == quote {
791                                 quote = '\x00'
792                                 continue
793                         }
794                 case rune == '"' || rune == '\'':
795                         quoted = true
796                         quote = rune
797                         continue
798                 case unicode.IsSpace(rune):
799                         if quoted || i > 0 {
800                                 quoted = false
801                                 args = append(args, string(arg[:i]))
802                                 i = 0
803                         }
804                         continue
805                 }
806                 arg[i] = rune
807                 i++
808         }
809         if quoted || i > 0 {
810                 args = append(args, string(arg[:i]))
811         }
812         if quote != 0 {
813                 err = errors.New("unclosed quote")
814         } else if escaped {
815                 err = errors.New("unfinished escaping")
816         }
817         return args, err
818 }
819
820 // matchAuto interprets text as either a +build or //go:build expression (whichever works),
821 // reporting whether the expression matches the build context.
822 //
823 // matchAuto is only used for testing of tag evaluation
824 // and in #cgo lines, which accept either syntax.
825 func (ctxt *Context) matchAuto(text string, allTags map[string]bool) bool {
826         if strings.ContainsAny(text, "&|()") {
827                 text = "//go:build " + text
828         } else {
829                 text = "// +build " + text
830         }
831         x, err := constraint.Parse(text)
832         if err != nil {
833                 return false
834         }
835         return ctxt.eval(x, allTags)
836 }
837
838 func (ctxt *Context) eval(x constraint.Expr, allTags map[string]bool) bool {
839         return x.Eval(func(tag string) bool { return ctxt.matchTag(tag, allTags) })
840 }
841
842 // matchTag reports whether the name is one of:
843 //
844 //      cgo (if cgo is enabled)
845 //      $GOOS
846 //      $GOARCH
847 //      boringcrypto
848 //      ctxt.Compiler
849 //      linux (if GOOS == android)
850 //      solaris (if GOOS == illumos)
851 //      tag (if tag is listed in ctxt.BuildTags or ctxt.ReleaseTags)
852 //
853 // It records all consulted tags in allTags.
854 func (ctxt *Context) matchTag(name string, allTags map[string]bool) bool {
855         if allTags != nil {
856                 allTags[name] = true
857         }
858
859         // special tags
860         if ctxt.CgoEnabled && name == "cgo" {
861                 return true
862         }
863         if name == ctxt.GOOS || name == ctxt.GOARCH || name == ctxt.Compiler {
864                 return true
865         }
866         if ctxt.GOOS == "android" && name == "linux" {
867                 return true
868         }
869         if ctxt.GOOS == "illumos" && name == "solaris" {
870                 return true
871         }
872         if ctxt.GOOS == "ios" && name == "darwin" {
873                 return true
874         }
875         if name == "unix" && unixOS[ctxt.GOOS] {
876                 return true
877         }
878         if name == "boringcrypto" {
879                 name = "goexperiment.boringcrypto" // boringcrypto is an old name for goexperiment.boringcrypto
880         }
881
882         // other tags
883         for _, tag := range ctxt.BuildTags {
884                 if tag == name {
885                         return true
886                 }
887         }
888         for _, tag := range ctxt.ToolTags {
889                 if tag == name {
890                         return true
891                 }
892         }
893         for _, tag := range ctxt.ReleaseTags {
894                 if tag == name {
895                         return true
896                 }
897         }
898
899         return false
900 }
901
902 // goodOSArchFile returns false if the name contains a $GOOS or $GOARCH
903 // suffix which does not match the current system.
904 // The recognized name formats are:
905 //
906 //      name_$(GOOS).*
907 //      name_$(GOARCH).*
908 //      name_$(GOOS)_$(GOARCH).*
909 //      name_$(GOOS)_test.*
910 //      name_$(GOARCH)_test.*
911 //      name_$(GOOS)_$(GOARCH)_test.*
912 //
913 // Exceptions:
914 // if GOOS=android, then files with GOOS=linux are also matched.
915 // if GOOS=illumos, then files with GOOS=solaris are also matched.
916 // if GOOS=ios, then files with GOOS=darwin are also matched.
917 func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool {
918         name, _, _ = strings.Cut(name, ".")
919
920         // Before Go 1.4, a file called "linux.go" would be equivalent to having a
921         // build tag "linux" in that file. For Go 1.4 and beyond, we require this
922         // auto-tagging to apply only to files with a non-empty prefix, so
923         // "foo_linux.go" is tagged but "linux.go" is not. This allows new operating
924         // systems, such as android, to arrive without breaking existing code with
925         // innocuous source code in "android.go". The easiest fix: cut everything
926         // in the name before the initial _.
927         i := strings.Index(name, "_")
928         if i < 0 {
929                 return true
930         }
931         name = name[i:] // ignore everything before first _
932
933         l := strings.Split(name, "_")
934         if n := len(l); n > 0 && l[n-1] == "test" {
935                 l = l[:n-1]
936         }
937         n := len(l)
938         if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] {
939                 if allTags != nil {
940                         // In case we short-circuit on l[n-1].
941                         allTags[l[n-2]] = true
942                 }
943                 return ctxt.matchTag(l[n-1], allTags) && ctxt.matchTag(l[n-2], allTags)
944         }
945         if n >= 1 && (knownOS[l[n-1]] || knownArch[l[n-1]]) {
946                 return ctxt.matchTag(l[n-1], allTags)
947         }
948         return true
949 }