1// Copyright 2014 Google Inc. 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 15package blueprint 16 17import ( 18 "errors" 19 "fmt" 20 "sort" 21 "strconv" 22 "strings" 23) 24 25// A Deps value indicates the dependency file format that Ninja should expect to 26// be output by a compiler. 27type Deps int 28 29const ( 30 DepsNone Deps = iota 31 DepsGCC 32 DepsMSVC 33) 34 35func (d Deps) String() string { 36 switch d { 37 case DepsNone: 38 return "none" 39 case DepsGCC: 40 return "gcc" 41 case DepsMSVC: 42 return "msvc" 43 default: 44 panic(fmt.Sprintf("unknown deps value: %d", d)) 45 } 46} 47 48// A PoolParams object contains the set of parameters that make up a Ninja pool 49// definition. 50type PoolParams struct { 51 Comment string // The comment that will appear above the definition. 52 Depth int // The Ninja pool depth. 53} 54 55// A RuleParams object contains the set of parameters that make up a Ninja rule 56// definition. 57type RuleParams struct { 58 // These fields correspond to a Ninja variable of the same name. 59 Command string // The command that Ninja will run for the rule. 60 Depfile string // The dependency file name. 61 Deps Deps // The format of the dependency file. 62 Description string // The description that Ninja will print for the rule. 63 Generator bool // Whether the rule generates the Ninja manifest file. 64 Pool Pool // The Ninja pool to which the rule belongs. 65 Restat bool // Whether Ninja should re-stat the rule's outputs. 66 Rspfile string // The response file. 67 RspfileContent string // The response file content. 68 69 // These fields are used internally in Blueprint 70 CommandDeps []string // Command-specific implicit dependencies to prepend to builds 71 CommandOrderOnly []string // Command-specific order-only dependencies to prepend to builds 72 Comment string // The comment that will appear above the definition. 73} 74 75// A BuildParams object contains the set of parameters that make up a Ninja 76// build statement. Each field except for Args corresponds with a part of the 77// Ninja build statement. The Args field contains variable names and values 78// that are set within the build statement's scope in the Ninja file. 79type BuildParams struct { 80 Comment string // The comment that will appear above the definition. 81 Depfile string // The dependency file name. 82 Deps Deps // The format of the dependency file. 83 Description string // The description that Ninja will print for the build. 84 Rule Rule // The rule to invoke. 85 Outputs []string // The list of explicit output targets. 86 ImplicitOutputs []string // The list of implicit output targets. 87 Inputs []string // The list of explicit input dependencies. 88 Implicits []string // The list of implicit input dependencies. 89 OrderOnly []string // The list of order-only dependencies. 90 Validations []string // The list of validations to run when this rule runs. 91 Args map[string]string // The variable/value pairs to set. 92 Optional bool // Skip outputting a default statement 93} 94 95// A poolDef describes a pool definition. It does not include the name of the 96// pool. 97type poolDef struct { 98 Comment string 99 Depth int 100} 101 102func parsePoolParams(scope scope, params *PoolParams) (*poolDef, 103 error) { 104 105 def := &poolDef{ 106 Comment: params.Comment, 107 Depth: params.Depth, 108 } 109 110 return def, nil 111} 112 113func (p *poolDef) WriteTo(nw *ninjaWriter, name string) error { 114 if p.Comment != "" { 115 err := nw.Comment(p.Comment) 116 if err != nil { 117 return err 118 } 119 } 120 121 err := nw.Pool(name) 122 if err != nil { 123 return err 124 } 125 126 return nw.ScopedAssign("depth", strconv.Itoa(p.Depth)) 127} 128 129// A ruleDef describes a rule definition. It does not include the name of the 130// rule. 131type ruleDef struct { 132 CommandDeps []ninjaString 133 CommandOrderOnly []ninjaString 134 Comment string 135 Pool Pool 136 Variables map[string]ninjaString 137} 138 139func parseRuleParams(scope scope, params *RuleParams) (*ruleDef, 140 error) { 141 142 r := &ruleDef{ 143 Comment: params.Comment, 144 Pool: params.Pool, 145 Variables: make(map[string]ninjaString), 146 } 147 148 if params.Command == "" { 149 return nil, fmt.Errorf("encountered rule params with no command " + 150 "specified") 151 } 152 153 if r.Pool != nil && !scope.IsPoolVisible(r.Pool) { 154 return nil, fmt.Errorf("Pool %s is not visible in this scope", r.Pool) 155 } 156 157 value, err := parseNinjaString(scope, params.Command) 158 if err != nil { 159 return nil, fmt.Errorf("error parsing Command param: %s", err) 160 } 161 r.Variables["command"] = value 162 163 if params.Depfile != "" { 164 value, err = parseNinjaString(scope, params.Depfile) 165 if err != nil { 166 return nil, fmt.Errorf("error parsing Depfile param: %s", err) 167 } 168 r.Variables["depfile"] = value 169 } 170 171 if params.Deps != DepsNone { 172 r.Variables["deps"] = simpleNinjaString(params.Deps.String()) 173 } 174 175 if params.Description != "" { 176 value, err = parseNinjaString(scope, params.Description) 177 if err != nil { 178 return nil, fmt.Errorf("error parsing Description param: %s", err) 179 } 180 r.Variables["description"] = value 181 } 182 183 if params.Generator { 184 r.Variables["generator"] = simpleNinjaString("true") 185 } 186 187 if params.Restat { 188 r.Variables["restat"] = simpleNinjaString("true") 189 } 190 191 if params.Rspfile != "" { 192 value, err = parseNinjaString(scope, params.Rspfile) 193 if err != nil { 194 return nil, fmt.Errorf("error parsing Rspfile param: %s", err) 195 } 196 r.Variables["rspfile"] = value 197 } 198 199 if params.RspfileContent != "" { 200 value, err = parseNinjaString(scope, params.RspfileContent) 201 if err != nil { 202 return nil, fmt.Errorf("error parsing RspfileContent param: %s", 203 err) 204 } 205 r.Variables["rspfile_content"] = value 206 } 207 208 r.CommandDeps, err = parseNinjaStrings(scope, params.CommandDeps) 209 if err != nil { 210 return nil, fmt.Errorf("error parsing CommandDeps param: %s", err) 211 } 212 213 r.CommandOrderOnly, err = parseNinjaStrings(scope, params.CommandOrderOnly) 214 if err != nil { 215 return nil, fmt.Errorf("error parsing CommandDeps param: %s", err) 216 } 217 218 return r, nil 219} 220 221func (r *ruleDef) WriteTo(nw *ninjaWriter, name string, 222 pkgNames map[*packageContext]string) error { 223 224 if r.Comment != "" { 225 err := nw.Comment(r.Comment) 226 if err != nil { 227 return err 228 } 229 } 230 231 err := nw.Rule(name) 232 if err != nil { 233 return err 234 } 235 236 if r.Pool != nil { 237 err = nw.ScopedAssign("pool", r.Pool.fullName(pkgNames)) 238 if err != nil { 239 return err 240 } 241 } 242 243 err = writeVariables(nw, r.Variables, pkgNames) 244 if err != nil { 245 return err 246 } 247 248 return nil 249} 250 251// A buildDef describes a build target definition. 252type buildDef struct { 253 Comment string 254 Rule Rule 255 RuleDef *ruleDef 256 Outputs []ninjaString 257 ImplicitOutputs []ninjaString 258 Inputs []ninjaString 259 Implicits []ninjaString 260 OrderOnly []ninjaString 261 Validations []ninjaString 262 Args map[Variable]ninjaString 263 Variables map[string]ninjaString 264 Optional bool 265} 266 267func parseBuildParams(scope scope, params *BuildParams) (*buildDef, 268 error) { 269 270 comment := params.Comment 271 rule := params.Rule 272 273 b := &buildDef{ 274 Comment: comment, 275 Rule: rule, 276 } 277 278 setVariable := func(name string, value ninjaString) { 279 if b.Variables == nil { 280 b.Variables = make(map[string]ninjaString) 281 } 282 b.Variables[name] = value 283 } 284 285 if !scope.IsRuleVisible(rule) { 286 return nil, fmt.Errorf("Rule %s is not visible in this scope", rule) 287 } 288 289 if len(params.Outputs) == 0 { 290 return nil, errors.New("Outputs param has no elements") 291 } 292 293 var err error 294 b.Outputs, err = parseNinjaStrings(scope, params.Outputs) 295 if err != nil { 296 return nil, fmt.Errorf("error parsing Outputs param: %s", err) 297 } 298 299 b.ImplicitOutputs, err = parseNinjaStrings(scope, params.ImplicitOutputs) 300 if err != nil { 301 return nil, fmt.Errorf("error parsing ImplicitOutputs param: %s", err) 302 } 303 304 b.Inputs, err = parseNinjaStrings(scope, params.Inputs) 305 if err != nil { 306 return nil, fmt.Errorf("error parsing Inputs param: %s", err) 307 } 308 309 b.Implicits, err = parseNinjaStrings(scope, params.Implicits) 310 if err != nil { 311 return nil, fmt.Errorf("error parsing Implicits param: %s", err) 312 } 313 314 b.OrderOnly, err = parseNinjaStrings(scope, params.OrderOnly) 315 if err != nil { 316 return nil, fmt.Errorf("error parsing OrderOnly param: %s", err) 317 } 318 319 b.Validations, err = parseNinjaStrings(scope, params.Validations) 320 if err != nil { 321 return nil, fmt.Errorf("error parsing Validations param: %s", err) 322 } 323 324 b.Optional = params.Optional 325 326 if params.Depfile != "" { 327 value, err := parseNinjaString(scope, params.Depfile) 328 if err != nil { 329 return nil, fmt.Errorf("error parsing Depfile param: %s", err) 330 } 331 setVariable("depfile", value) 332 } 333 334 if params.Deps != DepsNone { 335 setVariable("deps", simpleNinjaString(params.Deps.String())) 336 } 337 338 if params.Description != "" { 339 value, err := parseNinjaString(scope, params.Description) 340 if err != nil { 341 return nil, fmt.Errorf("error parsing Description param: %s", err) 342 } 343 setVariable("description", value) 344 } 345 346 argNameScope := rule.scope() 347 348 if len(params.Args) > 0 { 349 b.Args = make(map[Variable]ninjaString) 350 for name, value := range params.Args { 351 if !rule.isArg(name) { 352 return nil, fmt.Errorf("unknown argument %q", name) 353 } 354 355 argVar, err := argNameScope.LookupVariable(name) 356 if err != nil { 357 // This shouldn't happen. 358 return nil, fmt.Errorf("argument lookup error: %s", err) 359 } 360 361 ninjaValue, err := parseNinjaString(scope, value) 362 if err != nil { 363 return nil, fmt.Errorf("error parsing variable %q: %s", name, 364 err) 365 } 366 367 b.Args[argVar] = ninjaValue 368 } 369 } 370 371 return b, nil 372} 373 374func (b *buildDef) WriteTo(nw *ninjaWriter, pkgNames map[*packageContext]string) error { 375 var ( 376 comment = b.Comment 377 rule = b.Rule.fullName(pkgNames) 378 outputs = valueList(b.Outputs, pkgNames, outputEscaper) 379 implicitOuts = valueList(b.ImplicitOutputs, pkgNames, outputEscaper) 380 explicitDeps = valueList(b.Inputs, pkgNames, inputEscaper) 381 implicitDeps = valueList(b.Implicits, pkgNames, inputEscaper) 382 orderOnlyDeps = valueList(b.OrderOnly, pkgNames, inputEscaper) 383 validations = valueList(b.Validations, pkgNames, inputEscaper) 384 ) 385 386 if b.RuleDef != nil { 387 implicitDeps = append(valueList(b.RuleDef.CommandDeps, pkgNames, inputEscaper), implicitDeps...) 388 orderOnlyDeps = append(valueList(b.RuleDef.CommandOrderOnly, pkgNames, inputEscaper), orderOnlyDeps...) 389 } 390 391 err := nw.Build(comment, rule, outputs, implicitOuts, explicitDeps, implicitDeps, orderOnlyDeps, validations) 392 if err != nil { 393 return err 394 } 395 396 args := make(map[string]string) 397 398 for argVar, value := range b.Args { 399 args[argVar.fullName(pkgNames)] = value.Value(pkgNames) 400 } 401 402 err = writeVariables(nw, b.Variables, pkgNames) 403 if err != nil { 404 return err 405 } 406 407 var keys []string 408 for k := range args { 409 keys = append(keys, k) 410 } 411 sort.Strings(keys) 412 413 for _, name := range keys { 414 err = nw.ScopedAssign(name, args[name]) 415 if err != nil { 416 return err 417 } 418 } 419 420 if !b.Optional { 421 err = nw.Default(outputs...) 422 if err != nil { 423 return err 424 } 425 } 426 427 return nw.BlankLine() 428} 429 430func valueList(list []ninjaString, pkgNames map[*packageContext]string, 431 escaper *strings.Replacer) []string { 432 433 result := make([]string, len(list)) 434 for i, ninjaStr := range list { 435 result[i] = ninjaStr.ValueWithEscaper(pkgNames, escaper) 436 } 437 return result 438} 439 440func writeVariables(nw *ninjaWriter, variables map[string]ninjaString, 441 pkgNames map[*packageContext]string) error { 442 var keys []string 443 for k := range variables { 444 keys = append(keys, k) 445 } 446 sort.Strings(keys) 447 448 for _, name := range keys { 449 err := nw.ScopedAssign(name, variables[name].Value(pkgNames)) 450 if err != nil { 451 return err 452 } 453 } 454 return nil 455} 456