@@ -3,6 +3,7 @@ package bazelrc
3
3
import (
4
4
"bufio"
5
5
"fmt"
6
+ "maps"
6
7
"os"
7
8
"path/filepath"
8
9
"runtime"
@@ -16,6 +17,9 @@ import (
16
17
const (
17
18
EnablePlatformSpecificConfigFlag = "enable_platform_specific_config"
18
19
workspacePrefix = `%workspace%/`
20
+
21
+ CommonPhase = "common"
22
+ AlwaysPhase = "always"
19
23
)
20
24
21
25
var (
58
62
"coverage" : "test" ,
59
63
}
60
64
65
+ unconditionalCommandPhases = map [string ]struct {}{
66
+ CommonPhase : {},
67
+ AlwaysPhase : {},
68
+ }
69
+
70
+ allPhases = func () map [string ]struct {} {
71
+ v := maps .Clone (bazelCommands )
72
+ maps .Insert (v , maps .All (unconditionalCommandPhases ))
73
+ v ["startup" ] = struct {}{}
74
+ return v
75
+ }()
76
+
61
77
StartupFlagNoRc = map [string ]struct {}{
62
78
"ignore_all_rc_files" : {},
63
79
"home_rc" : {},
@@ -69,45 +85,14 @@ var (
69
85
70
86
// RcRule is a rule parsed from a bazelrc file.
71
87
type RcRule struct {
72
- Phase string
73
- Config string
88
+ Phase string
89
+ // Make Config a pointer to a string so we can distinguish default configs
90
+ // from configs with blank names.
91
+ Config * string
74
92
// Tokens contains the raw (non-canonicalized) tokens in the rule.
75
93
Tokens []string
76
94
}
77
95
78
- type Rules struct {
79
- All []* RcRule
80
- ByPhaseAndConfig map [string ]map [string ][]* RcRule
81
- }
82
-
83
- func StructureRules (rules []* RcRule ) * Rules {
84
- r := & Rules {
85
- All : rules ,
86
- ByPhaseAndConfig : map [string ]map [string ][]* RcRule {},
87
- }
88
- for _ , rule := range rules {
89
- byConfig := r .ByPhaseAndConfig [rule .Phase ]
90
- if byConfig == nil {
91
- byConfig = map [string ][]* RcRule {}
92
- r .ByPhaseAndConfig [rule .Phase ] = byConfig
93
- }
94
- byConfig [rule .Config ] = append (byConfig [rule .Config ], rule )
95
- }
96
- return r
97
- }
98
-
99
- func (r * Rules ) ForPhaseAndConfig (phase , config string ) []* RcRule {
100
- byConfig := r .ByPhaseAndConfig [phase ]
101
- if byConfig == nil {
102
- return nil
103
- }
104
- return byConfig [config ]
105
- }
106
-
107
- func (r * RcRule ) String () string {
108
- return fmt .Sprintf ("phase=%q,config=%q,tokens=%v" , r .Phase , r .Config , r .Tokens )
109
- }
110
-
111
96
// getPhases returns the command's inheritance hierarchy in increasing order of
112
97
// precedence.
113
98
//
@@ -128,35 +113,42 @@ func GetPhases(command string) (out []string) {
128
113
return
129
114
}
130
115
131
- func appendRcRulesFromImport (workspaceDir , path string , opts [] * RcRule , optional bool , importStack []string ) ([] * RcRule , error ) {
116
+ func appendRcRulesFromImport (workspaceDir , path string , namedConfigs map [ string ] map [ string ][] string , defaultConfig map [ string ][] string , optional bool , importStack []string ) error {
132
117
if strings .HasPrefix (path , workspacePrefix ) {
133
118
path = filepath .Join (workspaceDir , path [len (workspacePrefix ):])
134
119
}
135
-
136
- file , err := os .Open (path )
120
+ realPath , err := Realpath (path )
137
121
if err != nil {
138
122
if optional {
139
- return opts , nil
123
+ return nil
140
124
}
141
- return nil , err
125
+ return fmt . Errorf ( "could not determine real path of bazelrc file: %s" , err )
142
126
}
143
- defer file . Close ()
144
- return appendRcRulesFromFile (workspaceDir , file , opts , importStack )
127
+
128
+ return AppendRcRulesFromFile (workspaceDir , realPath , namedConfigs , defaultConfig , importStack , optional )
145
129
}
146
130
147
- func appendRcRulesFromFile (workspaceDir string , f * os.File , rules []* RcRule , importStack []string ) ([]* RcRule , error ) {
148
- rpath , err := realpath (f .Name ())
149
- if err != nil {
150
- return nil , fmt .Errorf ("could not determine real path of bazelrc file: %s" , err )
131
+ // AppendRCRulesFromFile reads and lexes the provided rc file and appends the
132
+ // args to the provided configs based on the detected phase and name.
133
+ //
134
+ // configs is a map keyed by config name where the values are maps keyed by
135
+ // phase name where the values are lists containing all the rules for that
136
+ // config in the order they are encountered.
137
+ func AppendRcRulesFromFile (workspaceDir string , realPath string , namedConfigs map [string ]map [string ][]string , defaultConfig map [string ][]string , importStack []string , optional bool ) error {
138
+ if slices .Contains (importStack , realPath ) {
139
+ return fmt .Errorf ("circular import detected: %s -> %s" , strings .Join (importStack , " -> " ), realPath )
151
140
}
152
- for _ , path := range importStack {
153
- if path == rpath {
154
- return nil , fmt .Errorf ("circular import detected: %s -> %s" , strings .Join (importStack , " -> " ), rpath )
141
+ importStack = append (importStack , realPath )
142
+ file , err := os .Open (realPath )
143
+ if err != nil {
144
+ if optional {
145
+ return nil
155
146
}
147
+ return err
156
148
}
157
- importStack = append ( importStack , rpath )
149
+ defer file . Close ( )
158
150
159
- scanner := bufio .NewScanner (f )
151
+ scanner := bufio .NewScanner (file )
160
152
for scanner .Scan () {
161
153
line := scanner .Text ()
162
154
// Handle line continuations (lines can end with "\" to effectively
@@ -175,9 +167,8 @@ func appendRcRulesFromFile(workspaceDir string, f *os.File, rules []*RcRule, imp
175
167
if tokens [0 ] == "import" || tokens [0 ] == "try-import" {
176
168
isOptional := tokens [0 ] == "try-import"
177
169
path := strings .TrimSpace (strings .TrimPrefix (line , tokens [0 ]))
178
- rules , err = appendRcRulesFromImport (workspaceDir , path , rules , isOptional , importStack )
179
- if err != nil {
180
- return nil , err
170
+ if err := appendRcRulesFromImport (workspaceDir , path , namedConfigs , defaultConfig , isOptional , importStack ); err != nil {
171
+ return err
181
172
}
182
173
continue
183
174
}
@@ -190,18 +181,29 @@ func appendRcRulesFromFile(workspaceDir string, f *os.File, rules []*RcRule, imp
190
181
if rule == nil {
191
182
continue
192
183
}
193
- // Bazel doesn't support configs for startup options and ignores them if
194
- // they appear in a bazelrc: https://bazel.build/run/bazelrc#config
195
- if rule .Phase == "startup" && rule .Config != "" {
184
+ if rule .Config == nil {
185
+ defaultConfig [rule .Phase ] = append (defaultConfig [rule .Phase ], rule .Tokens ... )
186
+ continue
187
+ }
188
+ // Bazel doesn't support named configs for startup options and ignores them
189
+ // if they appear in a bazelrc: https://bazel.build/run/bazelrc#config
190
+ if rule .Phase == "startup" {
196
191
continue
197
192
}
198
- rules = append (rules , rule )
193
+ config , ok := namedConfigs [* rule .Config ]
194
+ if ! ok {
195
+ config = make (map [string ][]string )
196
+ namedConfigs [* rule .Config ] = config
197
+ }
198
+ config [rule .Phase ] = append (config [rule .Phase ], rule .Tokens ... )
199
199
}
200
- log .Debugf ("Adding rc rules from %q: %v" , rpath , rules )
201
- return rules , scanner .Err ()
200
+ log .Debugf ("Added rc rules from %q; new configs : %# v" , realPath , namedConfigs )
201
+ return scanner .Err ()
202
202
}
203
203
204
- func realpath (path string ) (string , error ) {
204
+ // Realpath evaluates any symlinks in the given path and then returns the
205
+ // absolute path.
206
+ func Realpath (path string ) (string , error ) {
205
207
directPath , err := filepath .EvalSymlinks (path )
206
208
if err != nil {
207
209
return "" , err
@@ -229,55 +231,24 @@ func parseRcRule(line string) (*RcRule, error) {
229
231
// bazel ignores .bazelrc lines consisting of a single shlex token
230
232
return nil , nil
231
233
}
232
- if strings .HasPrefix (tokens [0 ], "-" ) {
233
- return & RcRule {
234
- Phase : "common" ,
235
- Tokens : tokens ,
236
- }, nil
237
- }
238
- if ! strings .Contains (tokens [0 ], ":" ) {
239
- return & RcRule {
240
- Phase : tokens [0 ],
241
- Tokens : tokens [1 :],
242
- }, nil
243
- }
244
- phaseConfig := strings .Split (tokens [0 ], ":" )
245
- if len (phaseConfig ) != 2 {
246
- return nil , fmt .Errorf ("invalid bazelrc syntax: %s" , phaseConfig )
234
+ phase := tokens [0 ]
235
+ var configName * string
236
+ if colonIndex := strings .Index (tokens [0 ], ":" ); colonIndex != - 1 {
237
+ phase = tokens [0 ][:colonIndex ]
238
+ v := tokens [0 ][colonIndex + 1 :]
239
+ configName = & v
247
240
}
241
+
248
242
return & RcRule {
249
- Phase : phaseConfig [ 0 ] ,
250
- Config : phaseConfig [ 1 ] ,
243
+ Phase : phase ,
244
+ Config : configName ,
251
245
Tokens : tokens [1 :],
252
246
}, nil
253
247
}
254
248
255
- func ParseRCFiles (workspaceDir string , filePaths ... string ) ([]* RcRule , error ) {
256
- opts := make ([]* RcRule , 0 )
257
- seen := map [string ]bool {}
258
- for _ , filePath := range filePaths {
259
- r , err := realpath (filePath )
260
- if err != nil {
261
- continue
262
- }
263
- if seen [r ] {
264
- continue
265
- }
266
- seen [r ] = true
267
-
268
- file , err := os .Open (filePath )
269
- if err != nil {
270
- continue
271
- }
272
- defer file .Close ()
273
- opts , err = appendRcRulesFromFile (workspaceDir , file , opts , nil /*=importStack*/ )
274
- if err != nil {
275
- return nil , err
276
- }
277
- }
278
- return opts , nil
279
- }
280
-
249
+ // GetBazelOS returns the os string that `enable_platform_specific_config` will
250
+ // expect based on the detected runtime.GOOS.
251
+ //
281
252
// Mirroring the behavior here:
282
253
// https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/runtime/ConfigExpander.java#L41
283
254
func GetBazelOS () string {
@@ -297,12 +268,28 @@ func GetBazelOS() string {
297
268
}
298
269
}
299
270
271
+ // IsBazelCommand returns whether the given string is recognized as a bazel
272
+ // command.
300
273
func IsBazelCommand (command string ) bool {
301
274
_ , ok := bazelCommands [command ]
302
275
return ok
303
276
}
304
277
278
+ // Parent returns the parent command of the given command, if one exists.
305
279
func Parent (command string ) (string , bool ) {
306
280
parent , ok := parentCommand [command ]
307
281
return parent , ok
308
282
}
283
+
284
+ // IsUnconditionalCommandPhase returns whether or not this is a phase that should always
285
+ // be evaluated, regardless of the command.
286
+ func IsUnconditionalCommandPhase (phase string ) bool {
287
+ _ , ok := unconditionalCommandPhases [phase ]
288
+ return ok
289
+ }
290
+
291
+ // IsPhase returns whether or not this is a valid phase for a bazel rc line.
292
+ func IsPhase (phase string ) bool {
293
+ _ , ok := allPhases [phase ]
294
+ return ok
295
+ }
0 commit comments