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.
5 // This file is a lightly modified copy go/build/build.go with unused parts
12 "cmd/go/internal/fsys"
29 // A Context specifies the supporting context for a build.
31 GOARCH string // target architecture
32 GOOS string // target operating system
33 GOROOT string // Go root
34 GOPATH string // Go paths
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.
40 // If Dir is non-empty, directories passed to Import and ImportDir must
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
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.
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".
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.
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
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
84 // IsAbsPath reports whether path is an absolute path.
85 // If IsAbsPath is nil, Import uses filepath.IsAbs.
86 IsAbsPath func(path string) bool
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
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)
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)
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)
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 {
116 return filepath.Join(elem...)
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 {
124 return filepath.SplitList(s)
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 {
132 return filepath.IsAbs(path)
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()
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 {
148 // Try using paths we received.
149 if rel, ok = hasSubdir(root, dir); ok {
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)
159 if rel, ok = hasSubdir(rootSym, dir); ok {
162 if rel, ok = hasSubdir(root, dirSym); ok {
165 return hasSubdir(rootSym, dirSym)
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) {
175 return filepath.ToSlash(dir[len(root):]), true
178 // gopath returns the list of Go path directories.
179 func (ctxt *Context) gopath() []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.
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
200 // On Windows, ~ is used in short names, such as c:\progra~1
201 // for c:\program files.
209 var defaultToolTags, defaultReleaseTags []string
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 {
218 func (e *NoGoError) Error() string {
219 return "no buildable Go source files in " + e.Dir
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]
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)
235 func nameExt(name string) string {
236 i := strings.LastIndex(name, ".")
243 func fileListForExt(p *build.Package, ext string) *[]string {
247 case ".cc", ".cpp", ".cxx":
251 case ".h", ".hh", ".hpp", ".hxx":
253 case ".f", ".F", ".for", ".f90":
255 case ".s", ".S", ".sx":
260 return &p.SwigCXXFiles
267 var errNoModules = errors.New("not using modules")
269 func findImportComment(data []byte) (s string, line int) {
270 // expect keyword package
271 word, data := parseWord(data)
272 if string(word) != "package" {
276 // expect package name
277 _, data = parseWord(data)
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') {
287 case bytes.HasPrefix(data, slashSlash):
288 comment, _, _ = bytes.Cut(data[2:], newline)
289 case bytes.HasPrefix(data, slashStar):
291 comment, _, ok = bytes.Cut(data[2:], starSlash)
296 if bytes.Contains(comment, newline) {
300 comment = bytes.TrimSpace(comment)
302 // split comment into `import`, `"pkg"`
303 word, arg := parseWord(comment)
304 if string(word) != "import" {
308 line = 1 + bytes.Count(data[:cap(data)-cap(arg)], newline)
309 return strings.TrimSpace(string(arg)), line
313 slashSlash = []byte("//")
314 slashStar = []byte("/*")
315 starSlash = []byte("*/")
316 newline = []byte("\n")
319 // skipSpaceOrComment returns data with any leading spaces or comments removed.
320 func skipSpaceOrComment(data []byte) []byte {
323 case ' ', '\t', '\r', '\n':
327 if bytes.HasPrefix(data, slashSlash) {
328 i := bytes.Index(data, newline)
335 if bytes.HasPrefix(data, slashStar) {
337 i := bytes.Index(data, starSlash)
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)
356 // Parse past leading word characters.
359 r, size := utf8.DecodeRune(rest)
360 if unicode.IsLetter(r) || '0' <= r && r <= '9' || r == '_' {
367 word = data[:len(data)-len(rest)]
375 var dummyPkg build.Package
377 // fileInfo records information learned about a file included in a build.
378 type fileInfo struct {
379 name string // full name including dir
386 directives []build.Directive
388 // Additional fields added to go/build's fileinfo for the purposes of the modindex package.
390 goBuildConstraint string
391 plusBuildConstraints []string
394 type fileImport struct {
397 doc *ast.CommentGroup
400 type fileEmbed struct {
405 var errNonSource = errors.New("non source file")
407 // getFileInfo extracts the information needed from each go file for the module
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
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, ".") {
423 i := strings.LastIndex(name, ".")
429 if ext != ".go" && fileListForExt(&dummyPkg, ext) == nil {
431 return nil, errNonSource
434 info := &fileInfo{name: filepath.Join(dir, name), fset: fset}
436 // binary, no reading
440 f, err := fsys.Open(info.name)
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
454 info.header, err = readComments(f)
458 return nil, fmt.Errorf("read %s: %v", info.name, err)
461 // Look for +build comments to accept or reject the file.
462 info.goBuildConstraint, info.plusBuildConstraints, info.binaryOnly, err = getConstraints(info.header)
464 return nil, fmt.Errorf("%s: %v", name, err)
467 if ignoreBinaryOnly && info.binaryOnly {
468 info.binaryOnly = false // override info.binaryOnly
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)
484 bSlashSlash = []byte(slashSlash)
485 bStarSlash = []byte(starSlash)
486 bSlashStar = []byte(slashStar)
487 bPlusBuild = []byte("+build")
489 goBuildComment = []byte("//go:build")
491 errMultipleGoBuild = errors.New("multiple //go:build comments")
494 func isGoBuildComment(line []byte) bool {
495 if !bytes.HasPrefix(line, goBuildComment) {
498 line = bytes.TrimSpace(line)
499 rest := line[len(goBuildComment):]
500 return len(rest) == 0 || len(bytes.TrimSpace(rest)) < len(rest)
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")
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)
514 return "", nil, false, err
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 {
523 if i := bytes.IndexByte(line, '\n'); i >= 0 {
524 line, p = line[:i], p[i+1:]
528 line = bytes.TrimSpace(line)
529 if !bytes.HasPrefix(line, bSlashSlash) || !bytes.Contains(line, bPlusBuild) {
533 if !constraint.IsPlusBuild(text) {
536 plusBuild = append(plusBuild, text)
540 return string(goBuildBytes), plusBuild, sawBinaryOnly, nil
543 func parseFileHeader(content []byte) (trimmed, goBuild []byte, sawBinaryOnly bool, err error) {
546 ended := false // found non-blank, non-// line, so stopped accepting // +build lines
547 inSlashStar := false // in /* */ comment
552 if i := bytes.IndexByte(line, '\n'); i >= 0 {
553 line, p = line[:i], p[i+1:]
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)
570 if !bytes.HasPrefix(line, slashSlash) { // Not comment line
574 if !inSlashStar && isGoBuildComment(line) {
576 return nil, nil, false, errMultipleGoBuild
580 if !inSlashStar && bytes.Equal(line, binaryOnlyComment) {
587 if i := bytes.Index(line, starSlash); i >= 0 {
589 line = bytes.TrimSpace(line[i+len(starSlash):])
594 if bytes.HasPrefix(line, bSlashSlash) {
597 if bytes.HasPrefix(line, bSlashStar) {
599 line = bytes.TrimSpace(line[len(bSlashStar):])
602 // Found non-comment text.
607 return content[:end], goBuild, sawBinaryOnly, nil
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") {
618 // #cgo [GOOS/GOARCH...] LDFLAGS: stuff
620 line = strings.TrimSpace(line)
621 if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') {
626 line, argstr, ok := strings.Cut(strings.TrimSpace(line[4:]), ":")
628 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
631 // Parse GOOS/GOARCH stuff.
632 f := strings.Fields(line)
634 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
637 cond, verb := f[:len(f)-1], f[len(f)-1]
640 for _, c := range cond {
641 if ctxt.matchAuto(c, nil) {
651 args, err := splitQuoted(argstr)
653 return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig)
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)
663 case "CFLAGS", "CPPFLAGS", "CXXFLAGS", "FFLAGS", "LDFLAGS":
664 // Change relative paths to absolute.
665 ctxt.makePathsAbsolute(args, di.Dir)
670 di.CgoCFLAGS = append(di.CgoCFLAGS, args...)
672 di.CgoCPPFLAGS = append(di.CgoCPPFLAGS, args...)
674 di.CgoCXXFLAGS = append(di.CgoCXXFLAGS, args...)
676 di.CgoFFLAGS = append(di.CgoFFLAGS, args...)
678 di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...)
680 di.CgoPkgConfig = append(di.CgoPkgConfig, args...)
682 return fmt.Errorf("%s: invalid #cgo verb: %s", filename, orig)
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)
696 chunks := strings.Split(str, "${SRCDIR}")
698 return str, safeCgoName(str)
701 for _, chunk := range chunks {
702 ok = ok && (chunk == "" || safeCgoName(chunk))
704 ok = ok && (srcdir == "" || safeCgoName(srcdir))
705 res := strings.Join(chunks, srcdir)
706 return res, ok && res != ""
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.
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) {
722 for i, arg := range args {
724 if !filepath.IsAbs(arg) {
725 args[i] = filepath.Join(srcDir, arg)
728 } else if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") {
732 if !filepath.IsAbs(arg[2:]) {
733 args[i] = arg[:2] + filepath.Join(srcDir, arg[2:])
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:$@%! ~^"
749 func safeCgoName(s string) bool {
753 for i := 0; i < len(s); i++ {
754 if c := s[i]; c < utf8.RuneSelf && strings.IndexByte(safeString, c) < 0 {
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.
769 // For example, the following string:
771 // a b:"c d" 'e''f' "g\""
773 // Would be parsed as:
775 // []string{"a", "b:c d", "ef", `g"`}
776 func splitQuoted(s string) (r []string, err error) {
778 arg := make([]rune, len(s))
783 for _, rune := range s {
790 case quote != '\x00':
795 case rune == '"' || rune == '\'':
799 case unicode.IsSpace(rune):
802 args = append(args, string(arg[:i]))
811 args = append(args, string(arg[:i]))
814 err = errors.New("unclosed quote")
816 err = errors.New("unfinished escaping")
821 // matchAuto interprets text as either a +build or //go:build expression (whichever works),
822 // reporting whether the expression matches the build context.
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
830 text = "// +build " + text
832 x, err := constraint.Parse(text)
836 return ctxt.eval(x, allTags)
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) })
843 // matchTag reports whether the name is one of:
845 // cgo (if cgo is enabled)
850 // linux (if GOOS == android)
851 // solaris (if GOOS == illumos)
852 // tag (if tag is listed in ctxt.BuildTags or ctxt.ReleaseTags)
854 // It records all consulted tags in allTags.
855 func (ctxt *Context) matchTag(name string, allTags map[string]bool) bool {
861 if ctxt.CgoEnabled && name == "cgo" {
864 if name == ctxt.GOOS || name == ctxt.GOARCH || name == ctxt.Compiler {
867 if ctxt.GOOS == "android" && name == "linux" {
870 if ctxt.GOOS == "illumos" && name == "solaris" {
873 if ctxt.GOOS == "ios" && name == "darwin" {
876 if name == "unix" && unixOS[ctxt.GOOS] {
879 if name == "boringcrypto" {
880 name = "goexperiment.boringcrypto" // boringcrypto is an old name for goexperiment.boringcrypto
884 for _, tag := range ctxt.BuildTags {
889 for _, tag := range ctxt.ToolTags {
894 for _, tag := range ctxt.ReleaseTags {
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:
909 // name_$(GOOS)_$(GOARCH).*
910 // name_$(GOOS)_test.*
911 // name_$(GOARCH)_test.*
912 // name_$(GOOS)_$(GOARCH)_test.*
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, ".")
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, "_")
932 name = name[i:] // ignore everything before first _
934 l := strings.Split(name, "_")
935 if n := len(l); n > 0 && l[n-1] == "test" {
939 if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] {
941 // In case we short-circuit on l[n-1].
942 allTags[l[n-2]] = true
944 return ctxt.matchTag(l[n-1], allTags) && ctxt.matchTag(l[n-2], allTags)
946 if n >= 1 && (knownOS[l[n-1]] || knownArch[l[n-1]]) {
947 return ctxt.matchTag(l[n-1], allTags)