1// Copyright 2018 The Bazel Authors. All rights reserved. 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 flagfile installs a -flagfile command line flag. 16// This package is only imported for the side effect of installing the flag 17package flagfile 18 19import ( 20 "bufio" 21 "flag" 22 "fmt" 23 "io" 24 "os" 25 "strings" 26) 27 28type flagFile string 29 30func (f *flagFile) String() string { 31 return string(*f) 32} 33 34func (f *flagFile) Get() interface{} { 35 return string(*f) 36} 37 38func (f *flagFile) Set(fn string) error { 39 file, err := os.Open(fn) 40 if err != nil { 41 return fmt.Errorf("error parsing flagfile %s: %v", fn, err) 42 } 43 defer file.Close() 44 45 fMap, err := parseFlags(bufio.NewReader(file)) 46 if err != nil { 47 return err 48 } 49 for k, v := range fMap { 50 flag.Set(k, v) 51 } 52 return nil 53} 54 55// parseFlags parses the contents is a naive flag file parser. 56func parseFlags(r *bufio.Reader) (map[string]string, error) { 57 fMap := make(map[string]string) 58 eof := false 59 for !eof { 60 line, err := r.ReadString('\n') 61 if err != nil && err != io.EOF { 62 return nil, err 63 } 64 if err == io.EOF { 65 eof = true 66 } 67 line = strings.TrimSpace(line) 68 if line == "" { 69 continue 70 } 71 // When Bazel is used to create flag files, it may create entries that are wrapped within 72 // quotations '--a=b'. Verify that it is balanced and strip first and last quotation. 73 if strings.HasPrefix(line, "'") || strings.HasPrefix(line, "\"") { 74 if !strings.HasSuffix(line, line[:1]) { 75 return nil, fmt.Errorf("error parsing flags, found unbalanced quotation marks around flag entry: %s", line) 76 } 77 line = line[1 : len(line)-1] 78 } 79 // Check that the flag has at least 1 "-" but no more than 2 ("-a" or "--a"). 80 if !strings.HasPrefix(line, "-") || strings.HasPrefix(line, "---") { 81 return nil, fmt.Errorf("error parsing flags, expected flag start definition ('-' or '--') but, got: %s", line) 82 } 83 split := strings.SplitN(strings.TrimLeft(line, "-"), "=", 2) 84 k := split[0] 85 if len(split) == 2 { 86 fMap[k] = split[1] 87 continue 88 } 89 v, err := parseFlagValue(r) 90 if err != nil { 91 return nil, fmt.Errorf("error parsing flag value, got: %v", err) 92 } 93 fMap[k] = v 94 } 95 return fMap, nil 96} 97 98func parseFlagValue(r *bufio.Reader) (string, error) { 99 pBytes, err := r.Peek(2) 100 if err != nil && err != io.EOF { 101 return "", err 102 } 103 peeked := string(pBytes) 104 // If the next line starts with "-", "'-" or '"-' assume it is the beginning of a new flag definition. 105 if strings.HasPrefix(peeked, "-") || peeked == "'-" || peeked == "\"-" { 106 return "", nil 107 } 108 // Next line contains the flag value. 109 line, err := r.ReadString('\n') 110 if err != nil && err != io.EOF { 111 return "", err 112 } 113 return strings.TrimSpace(line), nil 114} 115 116func init() { 117 flag.Var(new(flagFile), "flagfile", "Path to flagfile containing flag values, --key=val on each line") 118} 119