github.com/atrn/dcc@v0.0.0-20220806184050-4470d2553272/options.go (about) 1 // dcc - dependency-driven C/C++ compiler front end 2 // 3 // Copyright © A.Newman 2015. 4 // 5 // This source code is released under version 2 of the GNU Public License. 6 // See the file LICENSE for details. 7 // 8 9 package main 10 11 import ( 12 "bufio" 13 "fmt" 14 "io" 15 "log" 16 "os" 17 "path/filepath" 18 "strings" 19 "time" 20 ) 21 22 const ( 23 includeDirective = "!include" 24 inheritDirective = "!inherit" 25 ifdefDirective = "!ifdef" 26 ifndefDirective = "!ifndef" 27 elseDirective = "!else" 28 endifDirective = "!endif" 29 errorDirective = "!error" 30 ) 31 32 // Options represents a series of words and is used to represent 33 // compiler and linker options. Options are intended to be read from a 34 // file and act as a dependency to the build. 35 // 36 // An Options has a slice of strings, the option "values". 37 // 38 type Options struct { 39 Values []string // option values 40 Path string // associated file path 41 fileinfo os.FileInfo // actual options file info 42 mtime time.Time // options modtime, mutable 43 } 44 45 // NewOptions returns a new, empty, Options value 46 // 47 func NewOptions() *Options { 48 return &Options{ 49 Values: make([]string, 0), 50 } 51 } 52 53 // FileInfo returns the receiver's os.FileInfo set when the 54 // receiver was successfully read from a file. 55 // 56 func (o *Options) FileInfo() os.FileInfo { 57 return o.fileinfo 58 } 59 60 // Len returns the number of values defined by the receiver. 61 // 62 func (o *Options) Len() int { 63 return len(o.Values) 64 } 65 66 // Empty returns true if the Options has no values 67 // 68 func (o *Options) Empty() bool { 69 return len(o.Values) == 0 70 } 71 72 // String returns a string representation of the receiver. This 73 // returns a space separated list of the options' Values. 74 // 75 func (o *Options) String() string { 76 return strings.Join(o.Values, " ") 77 } 78 79 // Changed updates the "modtime" of the receiver, to "now". 80 // 81 func (o *Options) Changed() { 82 o.mtime = time.Now() 83 } 84 85 // Append appends an option to the set of options. Note, Append does 86 // NOT modify the mtime of the receiver. 87 // 88 func (o *Options) Append(option string) { 89 o.Values = append(o.Values, option) 90 } 91 92 // Prepend inserts an option at the start of the set of options. 93 // Note, Prepend does NOT modify the mtime of the receiver. 94 // 95 func (o *Options) Prepend(option string) { 96 o.Values = append([]string{option}, o.Values...) 97 } 98 99 // SetModTime sets the receiver's modification time to the supplied 100 // value. 101 // 102 func (o *Options) SetModTime(t time.Time) { 103 o.mtime = t 104 } 105 106 // Return the options modification time. 107 // 108 func (o *Options) ModTime() time.Time { 109 return o.mtime 110 } 111 112 // SetFrom copies options from another Options leaving 113 // the receiver's Path unmodified. 114 // 115 func (o *Options) SetFrom(other *Options) { 116 o.Values = make([]string, len(other.Values)) 117 copy(o.Values, other.Values) 118 o.mtime = other.mtime 119 o.fileinfo = other.fileinfo 120 } 121 122 // OptionIndex locates a specific option and returns its index within 123 // the receiver's Values. If no option is found -1 is returned. 124 // 125 func (o *Options) OptionIndex(s string) int { 126 for i := 0; i < len(o.Values); i++ { 127 if o.Values[i] == s { 128 return i 129 } 130 } 131 return -1 132 } 133 134 // ReadFromFile reads options from a text file. 135 // 136 // Options files are word-based with each non-blank, non-comment line 137 // being split into space-separated fields. 138 // 139 // An optional 'filter' function can be supplied which is applied to 140 // each option word before it is added to the receiver's Values slice. 141 // 142 // Returns true if everything worked, false if the file could not be 143 // read for some reason. 144 // 145 func (o *Options) ReadFromFile(filename string, filter func(string) string) (bool, error) { 146 file, err := os.Open(filename) 147 if err != nil { 148 return false, err 149 } 150 defer file.Close() 151 o.Path = filename 152 info, err := file.Stat() 153 if err != nil { 154 return true, err 155 } 156 o.fileinfo = info 157 o.SetModTime(info.ModTime()) 158 return o.ReadFromReader(file, filename, filter) 159 } 160 161 // Read options from the given io.Reader. 162 // 163 func (o *Options) ReadFromReader(r io.Reader, filename string, filter func(string) string) (bool, error) { 164 if filter == nil { 165 filter = func(s string) string { return s } 166 } 167 var conditional Conditional 168 input := bufio.NewScanner(r) 169 lineNumber := 0 170 for input.Scan() { 171 line := input.Text() 172 lineNumber++ 173 174 if line == "" || line[0] == '#' { 175 continue 176 } 177 178 fields := strings.Fields(line) 179 if len(fields) == 0 { 180 continue 181 } 182 183 evalCondition := func(invert bool) error { 184 if len(fields) != 2 { 185 return reportErrorInFile(filename, lineNumber, fmt.Sprintf("%s requires a single parameter", fields[0])) 186 } 187 val := os.Getenv(fields[1]) 188 state1, state2 := TrueConditionState, FalseConditionState 189 if invert { 190 state1, state2 = FalseConditionState, TrueConditionState 191 } 192 if val != "" { 193 conditional.PushState(state1) 194 } else { 195 conditional.PushState(state2) 196 } 197 return nil 198 } 199 200 if fields[0] == errorDirective { 201 if !conditional.IsSkippingLines() { 202 message := strings.Join(fields[1:], " ") 203 if message == "" { 204 message = "error" 205 } 206 return false, reportErrorInFile(filename, lineNumber, message) 207 } 208 continue 209 } 210 211 if fields[0] == ifdefDirective { 212 if conditional.IsSkippingLines() { 213 conditional.PushState(conditional.CurrentState()) 214 } else if err := evalCondition(false); err != nil { 215 return false, err 216 } else { 217 continue 218 } 219 } 220 221 if fields[0] == ifndefDirective { 222 if conditional.IsSkippingLines() { 223 conditional.PushState(conditional.CurrentState()) 224 } else if err := evalCondition(true); err != nil { 225 return false, err 226 } else { 227 continue 228 } 229 } 230 231 if fields[0] == elseDirective { 232 if !conditional.IsActive() { 233 return false, reportErrorInFile(filename, lineNumber, ErrNoCondition.Error()) 234 } 235 conditional.ToggleState() 236 continue 237 } 238 239 if fields[0] == endifDirective { 240 if !conditional.IsActive() { 241 return false, reportErrorInFile(filename, lineNumber, ErrNoCondition.Error()) 242 } 243 if err := conditional.PopState(); err != nil { 244 return false, err 245 } 246 continue 247 } 248 249 if conditional.IsSkippingLines() { 250 continue 251 } 252 253 // !include <filename> 254 // 255 if fields[0] == includeDirective { 256 if err := o.includeFile(filename, lineNumber, line, fields, filter); err != nil { 257 return false, err 258 } 259 continue 260 } 261 262 // !inherit [<filename>] 263 // 264 if fields[0] == inheritDirective { 265 if err := o.inheritFile(filename, lineNumber, line, fields, filter); err != nil { 266 return false, err 267 } 268 continue 269 } 270 271 // Otherwise, treat fields (tokens) as options to be included. 272 // Expand (interpolate) any variable references, filter and 273 // collect any non-empty strings. 274 for _, field := range fields { 275 field = os.ExpandEnv(field) 276 fields2 := strings.Fields(field) 277 for _, field2 := range fields2 { 278 if field2 = filter(field2); field2 != "" { 279 o.Values = append(o.Values, field2) 280 } 281 } 282 } 283 } 284 return true, nil 285 } 286 287 func extractFilename(filename string) string { 288 if len(filename) < 2 { 289 return filename 290 } 291 if filename[0] == '"' { 292 return RemoveDelimiters(filename, '"', '"') 293 } 294 if filename[0] == '<' { 295 return RemoveDelimiters(filename, '<', '>') 296 } 297 return filename 298 } 299 300 func reportErrorInFile(filename string, lineNumber int, what string) error { 301 return fmt.Errorf("error: %s:%d %s", filename, lineNumber, what) 302 } 303 304 func malformedLine(filename string, lineNumber int, what, line string) error { 305 return reportErrorInFile(filename, lineNumber, fmt.Sprintf("malformed %s - %s", what, line)) 306 } 307 308 func (o *Options) includeFile(parentFilename string, lineNumber int, line string, fields []string, filter func(string) string) error { 309 if len(fields) != 2 { 310 return malformedLine(parentFilename, lineNumber, includeDirective, line) 311 } 312 Assert(fields[1] != "", "unexpected empty field returned from strings.Fields()") 313 filename := fields[1] 314 if filename[0] == '"' { 315 filename = RemoveDelimiters(filename, '"', '"') 316 } else if filename[0] == '<' { 317 filename = RemoveDelimiters(filename, '<', '>') 318 } 319 path := filepath.Join(filepath.Dir(parentFilename), filename) 320 if Debug { 321 log.Printf("DEBUG: %q including %q", parentFilename, path) 322 } 323 _, err := o.ReadFromFile(path, filter) 324 return err 325 } 326 327 func (o *Options) inheritFile(parentFilename string, lineNumber int, line string, fields []string, filter func(string) string) error { 328 if len(fields) > 2 { 329 return malformedLine(parentFilename, lineNumber, inheritDirective, line) 330 } 331 startingDir, inheritedFilename := ParentDir(parentFilename), Basename(parentFilename) 332 if len(fields) > 1 { 333 inheritedFilename = fields[1] 334 } 335 if Debug { 336 log.Printf("OPTIONS: %q !inherit (%q)", parentFilename, inheritedFilename) 337 } 338 path, _, found, err := FindFileFromDirectory(inheritedFilename, startingDir) 339 if err != nil { 340 return err 341 } 342 if !found { 343 return reportErrorInFile(parentFilename, lineNumber, fmt.Sprintf("inherited file %q not found", inheritedFilename)) 344 } 345 346 if Debug { 347 log.Printf("DEBUG: %q inheriting %q", parentFilename, path) 348 } 349 350 ok, err := o.ReadFromFile(path, filter) 351 if err != nil { 352 return err 353 } 354 355 if !ok { 356 return reportErrorInFile(parentFilename, lineNumber, fmt.Sprintf("error reading inherited file %q", path)) 357 } 358 359 return nil 360 } 361 362 // 363 // MostRecentModTime returns the modification time of the most recently 364 // modified of the two Options. 365 // 366 func MostRecentModTime(a *Options, b *Options) time.Time { 367 at, bt := a.ModTime(), b.ModTime() 368 if at.After(bt) { 369 return at 370 } 371 return bt 372 }