gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/constraintutil/constraintutil.go (about) 1 // Copyright 2021 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package constraintutil provides utilities for working with Go build 16 // constraints. 17 package constraintutil 18 19 import ( 20 "bufio" 21 "bytes" 22 "fmt" 23 "go/build/constraint" 24 "io" 25 "os" 26 "strings" 27 ) 28 29 // FromReader extracts the build constraint from the Go source or assembly file 30 // whose contents are read by r. 31 func FromReader(r io.Reader) (constraint.Expr, error) { 32 // See go/build.parseFileHeader() for the "official" logic that this is 33 // derived from. 34 const ( 35 slashStar = "/*" 36 starSlash = "*/" 37 gobuildPrefix = "//go:build" 38 ) 39 s := bufio.NewScanner(r) 40 var ( 41 inSlashStar = false // between /* and */ 42 haveGobuild = false 43 e constraint.Expr 44 ) 45 Lines: 46 for s.Scan() { 47 line := bytes.TrimSpace(s.Bytes()) 48 if !inSlashStar && constraint.IsGoBuild(string(line)) { 49 if haveGobuild { 50 return nil, fmt.Errorf("multiple go:build directives") 51 } 52 haveGobuild = true 53 var err error 54 e, err = constraint.Parse(string(line)) 55 if err != nil { 56 return nil, err 57 } 58 } 59 ThisLine: 60 for len(line) > 0 { 61 if inSlashStar { 62 if i := bytes.Index(line, []byte(starSlash)); i >= 0 { 63 inSlashStar = false 64 line = bytes.TrimSpace(line[i+len(starSlash):]) 65 continue ThisLine 66 } 67 continue Lines 68 } 69 if bytes.HasPrefix(line, []byte("//")) { 70 continue Lines 71 } 72 // Note that if /* appears in the line, but not at the beginning, 73 // then the line is still non-empty, so skipping this and 74 // terminating below is correct. 75 if bytes.HasPrefix(line, []byte(slashStar)) { 76 inSlashStar = true 77 line = bytes.TrimSpace(line[len(slashStar):]) 78 continue ThisLine 79 } 80 // A non-empty non-comment line terminates scanning for go:build. 81 break Lines 82 } 83 } 84 return e, s.Err() 85 } 86 87 // FromString extracts the build constraint from the Go source or assembly file 88 // containing the given data. If no build constraint applies to the file, it 89 // returns nil. 90 func FromString(str string) (constraint.Expr, error) { 91 return FromReader(strings.NewReader(str)) 92 } 93 94 // FromFile extracts the build constraint from the Go source or assembly file 95 // at the given path. If no build constraint applies to the file, it returns 96 // nil. 97 func FromFile(path string) (constraint.Expr, error) { 98 f, err := os.Open(path) 99 if err != nil { 100 return nil, err 101 } 102 defer f.Close() 103 return FromReader(f) 104 } 105 106 // Combine returns a constraint.Expr that evaluates to true iff all expressions 107 // in es evaluate to true. If es is empty, Combine returns nil. 108 // 109 // Preconditions: All constraint.Exprs in es are non-nil. 110 func Combine(es []constraint.Expr) constraint.Expr { 111 switch len(es) { 112 case 0: 113 return nil 114 case 1: 115 return es[0] 116 default: 117 a := &constraint.AndExpr{es[0], es[1]} 118 for i := 2; i < len(es); i++ { 119 a = &constraint.AndExpr{a, es[i]} 120 } 121 return a 122 } 123 } 124 125 // CombineFromFiles returns a build constraint expression that evaluates to 126 // true iff the build constraints from all of the given Go source or assembly 127 // files evaluate to true. If no build constraints apply to any of the given 128 // files, it returns nil. 129 func CombineFromFiles(paths []string) (constraint.Expr, error) { 130 var es []constraint.Expr 131 for _, path := range paths { 132 e, err := FromFile(path) 133 if err != nil { 134 return nil, fmt.Errorf("failed to read build constraints from %q: %v", path, err) 135 } 136 if e != nil { 137 es = append(es, e) 138 } 139 } 140 return Combine(es), nil 141 } 142 143 // Lines returns a string containing build constraint directives for the given 144 // constraint.Expr, including two trailing newlines, as appropriate for a Go 145 // source or assembly file. At least a go:build directive will be emitted; if 146 // the constraint is expressible using +build directives as well, then +build 147 // directives will also be emitted. 148 // 149 // If e is nil, Lines returns the empty string. 150 func Lines(e constraint.Expr) string { 151 if e == nil { 152 return "" 153 } 154 155 var b strings.Builder 156 b.WriteString("//go:build ") 157 b.WriteString(e.String()) 158 b.WriteByte('\n') 159 160 if pblines, err := constraint.PlusBuildLines(e); err == nil { 161 for _, line := range pblines { 162 b.WriteString(line) 163 b.WriteByte('\n') 164 } 165 } 166 167 b.WriteByte('\n') 168 return b.String() 169 }