479 lines
13 KiB
Go
479 lines
13 KiB
Go
// Copyright 2014 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package blueprint
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// A Deps value indicates the dependency file format that Ninja should expect to
|
|
// be output by a compiler.
|
|
type Deps int
|
|
|
|
const (
|
|
DepsNone Deps = iota
|
|
DepsGCC
|
|
DepsMSVC
|
|
)
|
|
|
|
func (d Deps) String() string {
|
|
switch d {
|
|
case DepsNone:
|
|
return "none"
|
|
case DepsGCC:
|
|
return "gcc"
|
|
case DepsMSVC:
|
|
return "msvc"
|
|
default:
|
|
panic(fmt.Sprintf("unknown deps value: %d", d))
|
|
}
|
|
}
|
|
|
|
// A PoolParams object contains the set of parameters that make up a Ninja pool
|
|
// definition.
|
|
type PoolParams struct {
|
|
Comment string // The comment that will appear above the definition.
|
|
Depth int // The Ninja pool depth.
|
|
}
|
|
|
|
// A RuleParams object contains the set of parameters that make up a Ninja rule
|
|
// definition.
|
|
type RuleParams struct {
|
|
// These fields correspond to a Ninja variable of the same name.
|
|
Command string // The command that Ninja will run for the rule.
|
|
Depfile string // The dependency file name.
|
|
Deps Deps // The format of the dependency file.
|
|
Description string // The description that Ninja will print for the rule.
|
|
Generator bool // Whether the rule generates the Ninja manifest file.
|
|
Pool Pool // The Ninja pool to which the rule belongs.
|
|
Restat bool // Whether Ninja should re-stat the rule's outputs.
|
|
Rspfile string // The response file.
|
|
RspfileContent string // The response file content.
|
|
|
|
// These fields are used internally in Blueprint
|
|
CommandDeps []string // Command-specific implicit dependencies to prepend to builds
|
|
CommandOrderOnly []string // Command-specific order-only dependencies to prepend to builds
|
|
Comment string // The comment that will appear above the definition.
|
|
}
|
|
|
|
// A BuildParams object contains the set of parameters that make up a Ninja
|
|
// build statement. Each field except for Args corresponds with a part of the
|
|
// Ninja build statement. The Args field contains variable names and values
|
|
// that are set within the build statement's scope in the Ninja file.
|
|
type BuildParams struct {
|
|
Comment string // The comment that will appear above the definition.
|
|
Depfile string // The dependency file name.
|
|
Deps Deps // The format of the dependency file.
|
|
Description string // The description that Ninja will print for the build.
|
|
Rule Rule // The rule to invoke.
|
|
Outputs []string // The list of explicit output targets.
|
|
ImplicitOutputs []string // The list of implicit output targets.
|
|
Inputs []string // The list of explicit input dependencies.
|
|
Implicits []string // The list of implicit input dependencies.
|
|
OrderOnly []string // The list of order-only dependencies.
|
|
Validations []string // The list of validations to run when this rule runs.
|
|
Args map[string]string // The variable/value pairs to set.
|
|
Optional bool // Skip outputting a default statement
|
|
}
|
|
|
|
// A poolDef describes a pool definition. It does not include the name of the
|
|
// pool.
|
|
type poolDef struct {
|
|
Comment string
|
|
Depth int
|
|
}
|
|
|
|
func parsePoolParams(scope scope, params *PoolParams) (*poolDef,
|
|
error) {
|
|
|
|
def := &poolDef{
|
|
Comment: params.Comment,
|
|
Depth: params.Depth,
|
|
}
|
|
|
|
return def, nil
|
|
}
|
|
|
|
func (p *poolDef) WriteTo(nw *ninjaWriter, name string) error {
|
|
if p.Comment != "" {
|
|
err := nw.Comment(p.Comment)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err := nw.Pool(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nw.ScopedAssign("depth", strconv.Itoa(p.Depth))
|
|
}
|
|
|
|
// A ruleDef describes a rule definition. It does not include the name of the
|
|
// rule.
|
|
type ruleDef struct {
|
|
CommandDeps []*ninjaString
|
|
CommandOrderOnly []*ninjaString
|
|
Comment string
|
|
Pool Pool
|
|
Variables map[string]*ninjaString
|
|
}
|
|
|
|
func parseRuleParams(scope scope, params *RuleParams) (*ruleDef,
|
|
error) {
|
|
|
|
r := &ruleDef{
|
|
Comment: params.Comment,
|
|
Pool: params.Pool,
|
|
Variables: make(map[string]*ninjaString),
|
|
}
|
|
|
|
if params.Command == "" {
|
|
return nil, fmt.Errorf("encountered rule params with no command " +
|
|
"specified")
|
|
}
|
|
|
|
if r.Pool != nil && !scope.IsPoolVisible(r.Pool) {
|
|
return nil, fmt.Errorf("Pool %s is not visible in this scope", r.Pool)
|
|
}
|
|
|
|
value, err := parseNinjaString(scope, params.Command)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing Command param: %s", err)
|
|
}
|
|
r.Variables["command"] = value
|
|
|
|
if params.Depfile != "" {
|
|
value, err = parseNinjaString(scope, params.Depfile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing Depfile param: %s", err)
|
|
}
|
|
r.Variables["depfile"] = value
|
|
}
|
|
|
|
if params.Deps != DepsNone {
|
|
r.Variables["deps"] = simpleNinjaString(params.Deps.String())
|
|
}
|
|
|
|
if params.Description != "" {
|
|
value, err = parseNinjaString(scope, params.Description)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing Description param: %s", err)
|
|
}
|
|
r.Variables["description"] = value
|
|
}
|
|
|
|
if params.Generator {
|
|
r.Variables["generator"] = simpleNinjaString("true")
|
|
}
|
|
|
|
if params.Restat {
|
|
r.Variables["restat"] = simpleNinjaString("true")
|
|
}
|
|
|
|
if params.Rspfile != "" {
|
|
value, err = parseNinjaString(scope, params.Rspfile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing Rspfile param: %s", err)
|
|
}
|
|
r.Variables["rspfile"] = value
|
|
}
|
|
|
|
if params.RspfileContent != "" {
|
|
value, err = parseNinjaString(scope, params.RspfileContent)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing RspfileContent param: %s",
|
|
err)
|
|
}
|
|
r.Variables["rspfile_content"] = value
|
|
}
|
|
|
|
r.CommandDeps, err = parseNinjaStrings(scope, params.CommandDeps)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing CommandDeps param: %s", err)
|
|
}
|
|
|
|
r.CommandOrderOnly, err = parseNinjaStrings(scope, params.CommandOrderOnly)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing CommandDeps param: %s", err)
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func (r *ruleDef) WriteTo(nw *ninjaWriter, name string, nameTracker *nameTracker) error {
|
|
|
|
if r.Comment != "" {
|
|
err := nw.Comment(r.Comment)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err := nw.Rule(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if r.Pool != nil {
|
|
err = nw.ScopedAssign("pool", nameTracker.Pool(r.Pool))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = writeVariables(nw, r.Variables, nameTracker)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// A buildDef describes a build target definition.
|
|
type buildDef struct {
|
|
Comment string
|
|
Rule Rule
|
|
RuleDef *ruleDef
|
|
Outputs []*ninjaString
|
|
OutputStrings []string
|
|
ImplicitOutputs []*ninjaString
|
|
ImplicitOutputStrings []string
|
|
Inputs []*ninjaString
|
|
InputStrings []string
|
|
Implicits []*ninjaString
|
|
ImplicitStrings []string
|
|
OrderOnly []*ninjaString
|
|
OrderOnlyStrings []string
|
|
Validations []*ninjaString
|
|
ValidationStrings []string
|
|
Args map[Variable]*ninjaString
|
|
Variables map[string]*ninjaString
|
|
Optional bool
|
|
}
|
|
|
|
func formatTags(tags map[string]string, rule Rule) string {
|
|
// Maps in golang do not have a guaranteed iteration order, nor is there an
|
|
// ordered map type in the stdlib, but we need to deterministically generate
|
|
// the ninja file.
|
|
keys := make([]string, 0, len(tags))
|
|
for k := range tags {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
pairs := make([]string, 0, len(keys))
|
|
for _, k := range keys {
|
|
pairs = append(pairs, k+"="+tags[k])
|
|
}
|
|
pairs = append(pairs, "rule_name="+rule.name())
|
|
return strings.Join(pairs, ";")
|
|
}
|
|
|
|
func parseBuildParams(scope scope, params *BuildParams,
|
|
tags map[string]string) (*buildDef, error) {
|
|
|
|
comment := params.Comment
|
|
rule := params.Rule
|
|
|
|
b := &buildDef{
|
|
Comment: comment,
|
|
Rule: rule,
|
|
}
|
|
|
|
setVariable := func(name string, value *ninjaString) {
|
|
if b.Variables == nil {
|
|
b.Variables = make(map[string]*ninjaString)
|
|
}
|
|
b.Variables[name] = value
|
|
}
|
|
|
|
if !scope.IsRuleVisible(rule) {
|
|
return nil, fmt.Errorf("Rule %s is not visible in this scope", rule)
|
|
}
|
|
|
|
if len(params.Outputs) == 0 {
|
|
return nil, errors.New("Outputs param has no elements")
|
|
}
|
|
|
|
var err error
|
|
b.Outputs, b.OutputStrings, err = parseNinjaOrSimpleStrings(scope, params.Outputs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing Outputs param: %s", err)
|
|
}
|
|
|
|
b.ImplicitOutputs, b.ImplicitOutputStrings, err = parseNinjaOrSimpleStrings(scope, params.ImplicitOutputs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing ImplicitOutputs param: %s", err)
|
|
}
|
|
|
|
b.Inputs, b.InputStrings, err = parseNinjaOrSimpleStrings(scope, params.Inputs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing Inputs param: %s", err)
|
|
}
|
|
|
|
b.Implicits, b.ImplicitStrings, err = parseNinjaOrSimpleStrings(scope, params.Implicits)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing Implicits param: %s", err)
|
|
}
|
|
|
|
b.OrderOnly, b.OrderOnlyStrings, err = parseNinjaOrSimpleStrings(scope, params.OrderOnly)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing OrderOnly param: %s", err)
|
|
}
|
|
|
|
b.Validations, b.ValidationStrings, err = parseNinjaOrSimpleStrings(scope, params.Validations)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing Validations param: %s", err)
|
|
}
|
|
|
|
b.Optional = params.Optional
|
|
|
|
if params.Depfile != "" {
|
|
value, err := parseNinjaString(scope, params.Depfile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing Depfile param: %s", err)
|
|
}
|
|
setVariable("depfile", value)
|
|
}
|
|
|
|
if params.Deps != DepsNone {
|
|
setVariable("deps", simpleNinjaString(params.Deps.String()))
|
|
}
|
|
|
|
if params.Description != "" {
|
|
value, err := parseNinjaString(scope, params.Description)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing Description param: %s", err)
|
|
}
|
|
setVariable("description", value)
|
|
}
|
|
|
|
if len(tags) > 0 {
|
|
setVariable("tags", simpleNinjaString(formatTags(tags, rule)))
|
|
}
|
|
|
|
argNameScope := rule.scope()
|
|
|
|
if len(params.Args) > 0 {
|
|
b.Args = make(map[Variable]*ninjaString)
|
|
for name, value := range params.Args {
|
|
if !rule.isArg(name) {
|
|
return nil, fmt.Errorf("unknown argument %q", name)
|
|
}
|
|
|
|
argVar, err := argNameScope.LookupVariable(name)
|
|
if err != nil {
|
|
// This shouldn't happen.
|
|
return nil, fmt.Errorf("argument lookup error: %s", err)
|
|
}
|
|
|
|
ninjaValue, err := parseNinjaString(scope, value)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing variable %q: %s", name,
|
|
err)
|
|
}
|
|
|
|
b.Args[argVar] = ninjaValue
|
|
}
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
func (b *buildDef) WriteTo(nw *ninjaWriter, nameTracker *nameTracker) error {
|
|
var (
|
|
comment = b.Comment
|
|
rule = nameTracker.Rule(b.Rule)
|
|
outputs = b.Outputs
|
|
implicitOuts = b.ImplicitOutputs
|
|
explicitDeps = b.Inputs
|
|
implicitDeps = b.Implicits
|
|
orderOnlyDeps = b.OrderOnly
|
|
validations = b.Validations
|
|
outputStrings = b.OutputStrings
|
|
implicitOutStrings = b.ImplicitOutputStrings
|
|
explicitDepStrings = b.InputStrings
|
|
implicitDepStrings = b.ImplicitStrings
|
|
orderOnlyDepStrings = b.OrderOnlyStrings
|
|
validationStrings = b.ValidationStrings
|
|
)
|
|
|
|
if b.RuleDef != nil {
|
|
implicitDeps = append(b.RuleDef.CommandDeps, implicitDeps...)
|
|
orderOnlyDeps = append(b.RuleDef.CommandOrderOnly, orderOnlyDeps...)
|
|
}
|
|
|
|
err := nw.Build(comment, rule, outputs, implicitOuts, explicitDeps, implicitDeps, orderOnlyDeps, validations,
|
|
outputStrings, implicitOutStrings, explicitDepStrings,
|
|
implicitDepStrings, orderOnlyDepStrings, validationStrings,
|
|
nameTracker)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = writeVariables(nw, b.Variables, nameTracker)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
type nameValuePair struct {
|
|
name, value string
|
|
}
|
|
|
|
args := make([]nameValuePair, 0, len(b.Args))
|
|
|
|
for argVar, value := range b.Args {
|
|
fullName := nameTracker.Variable(argVar)
|
|
args = append(args, nameValuePair{fullName, value.Value(nameTracker)})
|
|
}
|
|
sort.Slice(args, func(i, j int) bool { return args[i].name < args[j].name })
|
|
|
|
for _, pair := range args {
|
|
err = nw.ScopedAssign(pair.name, pair.value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if !b.Optional {
|
|
err = nw.Default(nameTracker, outputs, outputStrings)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nw.BlankLine()
|
|
}
|
|
|
|
func writeVariables(nw *ninjaWriter, variables map[string]*ninjaString, nameTracker *nameTracker) error {
|
|
var keys []string
|
|
for k := range variables {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
|
|
for _, name := range keys {
|
|
err := nw.ScopedAssign(name, variables[name].Value(nameTracker))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|