github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e

Used only by github.com/bazelbuild/bazel-gazelle, expecting 80c7f0d45d7e
k3s-v1.15.3
Jordan Liggitt 2019-04-05 10:33:56 -04:00
parent 2cbf496c8e
commit 4bd9dcb855
21 changed files with 1566 additions and 766 deletions

2
go.mod
View File

@ -234,7 +234,7 @@ replace (
github.com/auth0/go-jwt-middleware => github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7
github.com/aws/aws-sdk-go => github.com/aws/aws-sdk-go v1.16.26
github.com/bazelbuild/bazel-gazelle => github.com/bazelbuild/bazel-gazelle v0.0.0-20181012220611-c728ce9f663e
github.com/bazelbuild/buildtools => github.com/bazelbuild/buildtools v0.0.0-20171220125010-1a9c38e0df93
github.com/bazelbuild/buildtools => github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e
github.com/beorn7/perks => github.com/beorn7/perks v0.0.0-20160229213445-3ac7bf7a47d1
github.com/blang/semver => github.com/blang/semver v3.5.0+incompatible
github.com/boltdb/bolt => github.com/boltdb/bolt v1.3.1

4
go.sum
View File

@ -44,8 +44,8 @@ github.com/aws/aws-sdk-go v1.16.26 h1:GWkl3rkRO/JGRTWoLLIqwf7AWC4/W/1hMOUZqmX0js
github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/bazelbuild/bazel-gazelle v0.0.0-20181012220611-c728ce9f663e h1:k7E/Rdb9kYVDDrdCX46/GLgHhbxBs4BQz7+FDRf8lcg=
github.com/bazelbuild/bazel-gazelle v0.0.0-20181012220611-c728ce9f663e/go.mod h1:uHBSeeATKpVazAACZBDPL/Nk/UhQDDsJWDlqYJo8/Us=
github.com/bazelbuild/buildtools v0.0.0-20171220125010-1a9c38e0df93 h1:HOcA9ovaT+3lN0RGVE8NpLHLU2poukBPujkIQOGI40U=
github.com/bazelbuild/buildtools v0.0.0-20171220125010-1a9c38e0df93/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU=
github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e h1:VuTBHPJNCQ88Okm9ld5SyLCvU50soWJYQYjQFdcDxew=
github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU=
github.com/beorn7/perks v0.0.0-20160229213445-3ac7bf7a47d1 h1:OnJHjoVbY69GG4gclp0ngXfywigLhR6rrgUxmxQRWO4=
github.com/beorn7/perks v0.0.0-20160229213445-3ac7bf7a47d1/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs=

View File

@ -1,6 +1,5 @@
// Code generated by protoc-gen-go.
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: api_proto/api.proto
// DO NOT EDIT!
/*
Package devtools_buildozer is a generated protocol buffer package.
@ -101,9 +100,7 @@ func (m *Output_Record_Field) String() string { return proto.CompactT
func (*Output_Record_Field) ProtoMessage() {}
func (*Output_Record_Field) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0, 0} }
type isOutput_Record_Field_Value interface {
isOutput_Record_Field_Value()
}
type isOutput_Record_Field_Value interface{ isOutput_Record_Field_Value() }
type Output_Record_Field_Text struct {
Text string `protobuf:"bytes,1,opt,name=text,oneof"`

View File

@ -49,4 +49,4 @@ def genfile_check_test(src, gen):
data = [src, gen],
args = ["$(location " + src + ")", "$(location " + gen + ")"],
)

View File

@ -22,6 +22,8 @@ import (
"fmt"
"strings"
"unicode/utf8"
"github.com/bazelbuild/buildtools/tables"
)
// Parse parses the input data and returns the corresponding parse tree.
@ -35,15 +37,19 @@ func Parse(filename string, data []byte) (*File, error) {
// An input represents a single input file being parsed.
type input struct {
// Lexing state.
filename string // name of input file, for errors
complete []byte // entire input
remaining []byte // remaining input
token []byte // token being scanned
lastToken string // most recently returned token, for error messages
pos Position // current input position
comments []Comment // accumulated comments
endRule int // position of end of current rule
depth int // nesting of [ ] { } ( )
filename string // name of input file, for errors
complete []byte // entire input
remaining []byte // remaining input
token []byte // token being scanned
lastToken string // most recently returned token, for error messages
pos Position // current input position
lineComments []Comment // accumulated line comments
suffixComments []Comment // accumulated suffix comments
endStmt int // position of the end of the current statement
depth int // nesting of [ ] { } ( )
cleanLine bool // true if the current line only contains whitespace before the current position
indent int // current line indentation in spaces
indents []int // stack of indentation levels in spaces
// Parser state.
file *File // returned top-level syntax tree
@ -55,14 +61,26 @@ type input struct {
}
func newInput(filename string, data []byte) *input {
// The syntax requires that each simple statement ends with '\n', however it's optional at EOF.
// If `data` doesn't end with '\n' we add it here to keep parser simple.
// It shouldn't affect neither the parsed tree nor its formatting.
data = append(data, '\n')
return &input{
filename: filename,
complete: data,
remaining: data,
pos: Position{Line: 1, LineRune: 1, Byte: 0},
cleanLine: true,
indents: []int{0},
endStmt: -1, // -1 denotes it's not inside a statement
}
}
func (in *input) currentIndent() int {
return in.indents[len(in.indents)-1]
}
// parse parses the input file.
func (in *input) parse() (f *File, err error) {
// The parser panics for both routine errors like syntax errors
@ -169,29 +187,23 @@ func (in *input) Lex(val *yySymType) int {
// Skip past spaces, stopping at non-space or EOF.
countNL := 0 // number of newlines we've skipped past
for !in.eof() {
// The parser does not track indentation, because for the most part
// BUILD expressions don't care about how they are indented.
// However, we do need to be able to distinguish
// If a single statement is split into multiple lines, we don't need
// to track indentations and unindentations within these lines. For example:
//
// x = y[0]
// def f(
// # This indentation should be ignored
// x):
// # This unindentation should be ignored
// # Actual indentation is from 0 to 2 spaces here
// return x
//
// from the occasional
// To handle this case, when we reach the beginning of a statement we scan forward to see where
// it should end and record the number of input bytes remaining at that endpoint.
//
// x = y
// [0]
//
// To handle this one case, when we reach the beginning of a
// top-level BUILD expression, we scan forward to see where
// it should end and record the number of input bytes remaining
// at that endpoint. When we reach that point in the input, we
// insert an implicit semicolon to force the two expressions
// to stay separate.
//
if in.endRule != 0 && len(in.remaining) == in.endRule {
in.endRule = 0
in.lastToken = "implicit ;"
val.tok = ";"
return ';'
// If --format_bzl is set to false, top level blocks (e.g. an entire function definition)
// is considered as a single statement.
if in.endStmt != -1 && len(in.remaining) == in.endStmt {
in.endStmt = -1
}
// Skip over spaces. Count newlines so we can give the parser
@ -199,15 +211,19 @@ func (in *input) Lex(val *yySymType) int {
// for top-level comment assignment.
c := in.peekRune()
if c == ' ' || c == '\t' || c == '\r' || c == '\n' {
if c == '\n' && in.endRule == 0 {
// Not in a rule. Tell parser about top-level blank line.
in.startToken(val)
in.readRune()
in.endToken(val)
return '\n'
}
if c == '\n' {
in.indent = 0
in.cleanLine = true
if in.endStmt == -1 {
// Not in a statememt. Tell parser about top-level blank line.
in.startToken(val)
in.readRune()
in.endToken(val)
return '\n'
}
countNL++
} else if c == ' ' && in.cleanLine {
in.indent++
}
in.readRune()
continue
@ -215,6 +231,11 @@ func (in *input) Lex(val *yySymType) int {
// Comment runs to end of line.
if c == '#' {
// If a line contains just a comment its indentation level doesn't matter.
// Reset it to zero.
in.indent = 0
in.cleanLine = true
// Is this comment the only thing on its line?
// Find the last \n before this # and see if it's all
// spaces from there to here.
@ -231,10 +252,12 @@ func (in *input) Lex(val *yySymType) int {
isSuffix = false
}
// Consume comment.
// Consume comment without the \n it ends with.
in.startToken(val)
for len(in.remaining) > 0 && in.readRune() != '\n' {
for len(in.remaining) > 0 && in.peekRune() != '\n' {
in.readRune()
}
in.endToken(val)
val.tok = strings.TrimRight(val.tok, "\n")
@ -243,22 +266,27 @@ func (in *input) Lex(val *yySymType) int {
// If we are at top level (not in a rule), hand the comment to
// the parser as a _COMMENT token. The grammar is written
// to handle top-level comments itself.
if in.endRule == 0 {
// Not in a rule. Tell parser about top-level comment.
if in.endStmt == -1 {
// Not in a statement. Tell parser about top-level comment.
return _COMMENT
}
// Otherwise, save comment for later attachment to syntax tree.
if countNL > 1 {
in.comments = append(in.comments, Comment{val.pos, "", false})
in.lineComments = append(in.lineComments, Comment{val.pos, ""})
}
in.comments = append(in.comments, Comment{val.pos, val.tok, isSuffix})
countNL = 1
if isSuffix {
in.suffixComments = append(in.suffixComments, Comment{val.pos, val.tok})
} else {
in.lineComments = append(in.lineComments, Comment{val.pos, val.tok})
}
countNL = 0
continue
}
if c == '\\' && len(in.remaining) >= 2 && in.remaining[1] == '\n' {
// We can ignore a trailing \ at end of line.
// We can ignore a trailing \ at end of line together with the \n.
in.readRune()
in.readRune()
continue
}
@ -267,6 +295,41 @@ func (in *input) Lex(val *yySymType) int {
break
}
// Check for changes in indentation
// Skip if --format_bzl is set to false, if we're inside a statement, or if there were non-space
// characters before in the current line.
if tables.FormatBzlFiles && in.endStmt == -1 && in.cleanLine {
if in.indent > in.currentIndent() {
// A new indentation block starts
in.indents = append(in.indents, in.indent)
in.lastToken = "indent"
in.cleanLine = false
return _INDENT
} else if in.indent < in.currentIndent() {
// An indentation block ends
in.indents = in.indents[:len(in.indents)-1]
// It's a syntax error if the current line indentation level in now greater than
// currentIndent(), should be either equal (a parent block continues) or still less
// (need to unindent more).
if in.indent > in.currentIndent() {
in.pos = val.pos
in.Error("unexpected indentation")
}
in.lastToken = "unindent"
return _UNINDENT
}
}
in.cleanLine = false
// If the file ends with an indented block, return the corresponding amounts of unindents.
if in.eof() && in.currentIndent() > 0 {
in.indents = in.indents[:len(in.indents)-1]
in.lastToken = "unindent"
return _UNINDENT
}
// Found the beginning of the next token.
in.startToken(val)
defer in.endToken(val)
@ -277,16 +340,9 @@ func (in *input) Lex(val *yySymType) int {
return _EOF
}
// If endRule is 0, we need to recompute where the end
// of the next rule (Python expression) is, so that we can
// generate a virtual end-of-rule semicolon (see above).
if in.endRule == 0 {
in.endRule = len(in.skipPython(in.remaining))
if in.endRule == 0 {
// skipPython got confused.
// No more virtual semicolons.
in.endRule = -1
}
// If endStmt is 0, we need to recompute where the end of the next statement is.
if in.endStmt == -1 {
in.endStmt = len(in.skipStmt(in.remaining))
}
// Punctuation tokens.
@ -301,12 +357,17 @@ func (in *input) Lex(val *yySymType) int {
in.readRune()
return c
case '.', '-', '%', ':', ';', ',', '/', '*': // single-char tokens
case '.', ':', ';', ',': // single-char tokens
in.readRune()
return c
case '<', '>', '=', '!', '+': // possibly followed by =
case '<', '>', '=', '!', '+', '-', '*', '/', '%': // possibly followed by =
in.readRune()
if c == '/' && in.peekRune() == '/' {
// integer division
in.readRune()
}
if in.peekRune() == '=' {
in.readRune()
switch c {
@ -318,8 +379,8 @@ func (in *input) Lex(val *yySymType) int {
return _EQ
case '!':
return _NE
case '+':
return _ADDEQ
default:
return _AUGM
}
}
return c
@ -395,15 +456,17 @@ func (in *input) Lex(val *yySymType) int {
in.Error(fmt.Sprintf("unexpected input character %#q", c))
}
// Look for raw Python block (class, def, if, etc at beginning of line) and pass through.
if in.depth == 0 && in.pos.LineRune == 1 && hasPythonPrefix(in.remaining) {
// Find end of Python block and advance input beyond it.
// Have to loop calling readRune in order to maintain line number info.
rest := in.skipPython(in.remaining)
for len(in.remaining) > len(rest) {
in.readRune()
if !tables.FormatBzlFiles {
// Look for raw Python block (class, def, if, etc at beginning of line) and pass through.
if in.depth == 0 && in.pos.LineRune == 1 && hasPythonPrefix(in.remaining) {
// Find end of Python block and advance input beyond it.
// Have to loop calling readRune in order to maintain line number info.
rest := in.skipStmt(in.remaining)
for len(in.remaining) > len(rest) {
in.readRune()
}
return _PYTHON
}
return _PYTHON
}
// Scan over alphanumeric identifier.
@ -442,11 +505,15 @@ var keywordToken = map[string]int{
"for": _FOR,
"if": _IF,
"else": _ELSE,
"elif": _ELIF,
"in": _IN,
"is": _IS,
"lambda": _LAMBDA,
"load": _LOAD,
"not": _NOT,
"or": _OR,
"def": _DEF,
"return": _RETURN,
}
// Python scanning.
@ -457,6 +524,10 @@ var keywordToken = map[string]int{
// hasPythonPrefix reports whether p begins with a keyword that would
// introduce an uninterpreted Python block.
func hasPythonPrefix(p []byte) bool {
if tables.FormatBzlFiles {
return false
}
for _, pre := range prefixes {
if hasPrefixSpace(p, pre) {
return true
@ -474,10 +545,14 @@ var prefixes = []string{
"for",
"if",
"try",
"else",
"elif",
"except",
}
// hasPrefixSpace reports whether p begins with pre followed by a space or colon.
func hasPrefixSpace(p []byte, pre string) bool {
if len(p) <= len(pre) || p[len(pre)] != ' ' && p[len(pre)] != '\t' && p[len(pre)] != ':' {
return false
}
@ -489,44 +564,46 @@ func hasPrefixSpace(p []byte, pre string) bool {
return true
}
func isBlankOrComment(b []byte) bool {
// A utility function for the legacy formatter.
// Returns whether a given code starts with a top-level statement (maybe with some preceeding
// comments and blank lines)
func isOutsideBlock(b []byte) bool {
isBlankLine := true
isComment := false
for _, c := range b {
if c == '#' || c == '\n' {
return true
}
if c != ' ' && c != '\t' && c != '\r' {
return false
switch {
case c == ' ' || c == '\t' || c == '\r':
isBlankLine = false
case c == '#':
isBlankLine = false
isComment = true
case c == '\n':
isBlankLine = true
isComment = false
default:
if !isComment {
return isBlankLine
}
}
}
return true
}
// hasPythonContinuation reports whether p begins with a keyword that
// continues an uninterpreted Python block.
func hasPythonContinuation(p []byte) bool {
for _, pre := range continuations {
if hasPrefixSpace(p, pre) {
return true
}
}
return false
}
// These keywords continue uninterpreted Python blocks.
var continuations = []string{
"except",
"else",
}
// skipPython returns the data remaining after the uninterpreted
// Python block beginning at p. It does not advance the input position.
// skipStmt returns the data remaining after the statement beginning at p.
// It does not advance the input position.
// (The only reason for the input receiver is to be able to call in.Error.)
func (in *input) skipPython(p []byte) []byte {
func (in *input) skipStmt(p []byte) []byte {
quote := byte(0) // if non-zero, the kind of quote we're in
tripleQuote := false // if true, the quote is a triple quote
depth := 0 // nesting depth for ( ) [ ] { }
var rest []byte // data after the Python block
defer func() {
if quote != 0 {
in.Error("EOF scanning Python quoted string")
}
}()
// Scan over input one byte at a time until we find
// an unindented, non-blank, non-comment line
// outside quoted strings and brackets.
@ -559,20 +636,23 @@ func (in *input) skipPython(p []byte) []byte {
continue
}
if depth == 0 && i > 0 && p[i-1] == '\n' && (i < 2 || p[i-2] != '\\') {
if depth == 0 && i > 0 && p[i] == '\n' && p[i-1] != '\\' {
// Possible stopping point. Save the earliest one we find.
if rest == nil {
rest = p[i:]
}
if !isBlankOrComment(p[i:]) {
if !hasPythonContinuation(p[i:]) && c != ' ' && c != '\t' {
// Yes, stop here.
break
}
// Not a stopping point after all.
rest = nil
if tables.FormatBzlFiles {
// In the bzl files mode we only care about the end of the statement, we've found it.
return rest
}
// In the legacy mode we need to find where the current block ends
if isOutsideBlock(p[i+1:]) {
return rest
}
// Not a stopping point after all.
rest = nil
}
switch c {
@ -581,6 +661,8 @@ func (in *input) skipPython(p []byte) []byte {
for i < len(p) && p[i] != '\n' {
i++
}
// Rewind 1 position back because \n should be handled at the next iteration
i--
case '(', '[', '{':
depth++
@ -589,9 +671,6 @@ func (in *input) skipPython(p []byte) []byte {
depth--
}
}
if quote != 0 {
in.Error("EOF scanning Python quoted string")
}
return rest
}
@ -691,8 +770,9 @@ func (in *input) order(v Expr) {
in.order(&v.End)
case *SliceExpr:
in.order(v.X)
in.order(v.Y)
in.order(v.Z)
in.order(v.From)
in.order(v.To)
in.order(v.Step)
case *IndexExpr:
in.order(v.X)
in.order(v.Y)
@ -701,6 +781,32 @@ func (in *input) order(v Expr) {
in.order(name)
}
in.order(v.Expr)
case *ReturnExpr:
if v.X != nil {
in.order(v.X)
}
case *FuncDef:
for _, x := range v.Args {
in.order(x)
}
for _, x := range v.Body.Statements {
in.order(x)
}
case *ForLoop:
for _, x := range v.LoopVars {
in.order(x)
}
in.order(v.Iterable)
for _, x := range v.Body.Statements {
in.order(x)
}
case *IfElse:
for _, condition := range v.Conditions {
in.order(condition.If)
for _, x := range condition.Then.Statements {
in.order(x)
}
}
}
if v != nil {
in.post = append(in.post, v)
@ -712,17 +818,8 @@ func (in *input) assignComments() {
// Generate preorder and postorder lists.
in.order(in.file)
// Split into whole-line comments and suffix comments.
var line, suffix []Comment
for _, com := range in.comments {
if com.Suffix {
suffix = append(suffix, com)
} else {
line = append(line, com)
}
}
// Assign line comments to syntax immediately following.
line := in.lineComments
for _, x := range in.pre {
start, _ := x.Span()
xcom := x.Comment()
@ -736,6 +833,7 @@ func (in *input) assignComments() {
in.file.After = append(in.file.After, line...)
// Assign suffix comments to syntax immediately before.
suffix := in.suffixComments
for i := len(in.post) - 1; i >= 0; i-- {
x := in.post[i]

View File

@ -31,6 +31,7 @@ package build
forsifs []*ForClauseWithIfClausesOpt
string *StringExpr
strings []*StringExpr
block CodeBlock
// supporting information
comma Position // position of trailing comma in list, if present
@ -69,7 +70,7 @@ package build
// However, we do not want to export them from the Go package
// we are creating, so prefix them all with underscores.
%token <pos> _ADDEQ // operator +=
%token <pos> _AUGM // augmented assignment
%token <pos> _AND // keyword and
%token <pos> _COMMENT // top-level # comment
%token <pos> _EOF // end of file
@ -79,34 +80,47 @@ package build
%token <pos> _IDENT // non-keyword identifier or number
%token <pos> _IF // keyword if
%token <pos> _ELSE // keyword else
%token <pos> _ELIF // keyword elif
%token <pos> _IN // keyword in
%token <pos> _IS // keyword is
%token <pos> _LAMBDA // keyword lambda
%token <pos> _LOAD // keyword load
%token <pos> _LE // operator <=
%token <pos> _NE // operator !=
%token <pos> _NOT // keyword not
%token <pos> _OR // keyword or
%token <pos> _PYTHON // uninterpreted Python block
%token <pos> _STRING // quoted string
%token <pos> _DEF // keyword def
%token <pos> _RETURN // keyword return
%token <pos> _INDENT // indentation
%token <pos> _UNINDENT // unindentation
%type <pos> comma_opt
%type <expr> expr
%type <expr> expr_opt
%type <expr> primary_expr
%type <exprs> exprs
%type <exprs> exprs_opt
%type <exprs> primary_exprs
%type <forc> for_clause
%type <forifs> for_clause_with_if_clauses_opt
%type <forsifs> for_clauses_with_if_clauses_opt
%type <expr> ident
%type <exprs> idents
%type <ifs> if_clauses_opt
%type <exprs> stmts
%type <expr> stmt
%type <exprs> stmt // a simple_stmt or a for/if/def block
%type <expr> block_stmt // a single for/if/def statement
%type <expr> if_else_block // a single if-else statement
%type <exprs> simple_stmt // One or many small_stmts on one line, e.g. 'a = f(x); return str(a)'
%type <expr> small_stmt // A single statement, e.g. 'a = f(x)'
%type <exprs> small_stmts_continuation // A sequence of `';' small_stmt`
%type <expr> keyvalue
%type <exprs> keyvalues
%type <exprs> keyvalues_no_comma
%type <string> string
%type <strings> strings
%type <block> suite
// Operator precedence.
// Operators listed lower in the table bind tighter.
@ -120,25 +134,25 @@ package build
%left '\n'
%left _ASSERT
// '=' and '+=' have the lowest precedence
// '=' and augmented assignments have the lowest precedence
// e.g. "x = a if c > 0 else 'bar'"
// followed by
// 'if' and 'else' which have lower precedence than all other operators.
// e.g. "a, b if c > 0 else 'foo'" is either a tuple of (a,b) or 'foo'
// and not a tuple of "(a, (b if ... ))"
%left '=' _ADDEQ
%left _IF _ELSE
%left ','
%left ':'
%left _IN _NOT _IS
%left _OR
%left _AND
%left '<' '>' _EQ _NE _LE _GE
%left '+' '-'
%left '*' '/' '%'
%left '.' '[' '('
%right _UNARY
%left _STRING
%left '=' _AUGM
%left _IF _ELSE _ELIF
%left ','
%left ':'
%left _IN _NOT _IS
%left _OR
%left _AND
%left '<' '>' _EQ _NE _LE _GE
%left '+' '-'
%left '*' '/' '%'
%left '.' '[' '('
%right _UNARY
%left _STRING
%%
@ -155,26 +169,46 @@ file:
return 0
}
suite:
'\n' _INDENT stmts _UNINDENT
{
$$ = CodeBlock{
Start: $2,
Statements: $3,
End: End{Pos: $4},
}
}
| simple_stmt
{
// simple_stmt is never empty
start, _ := $1[0].Span()
_, end := $1[len($1)-1].Span()
$$ = CodeBlock{
Start: start,
Statements: $1,
End: End{Pos: end},
}
}
stmts:
{
$$ = nil
$<lastRule>$ = nil
}
| stmts stmt comma_opt semi_opt
| stmts stmt
{
// If this statement follows a comment block,
// attach the comments to the statement.
if cb, ok := $<lastRule>1.(*CommentBlock); ok {
$$ = $1
$$[len($1)-1] = $2
$2.Comment().Before = cb.After
$<lastRule>$ = $2
$$ = append($1[:len($1)-1], $2...)
$2[0].Comment().Before = cb.After
$<lastRule>$ = $2[len($2)-1]
break
}
// Otherwise add to list.
$$ = append($1, $2)
$<lastRule>$ = $2
$$ = append($1, $2...)
$<lastRule>$ = $2[len($2)-1]
// Consider this input:
//
@ -187,7 +221,8 @@ stmts:
// for baz() instead.
if x := $<lastRule>1; x != nil {
com := x.Comment()
$2.Comment().Before = com.After
// stmt is never empty
$2[0].Comment().Before = com.After
com.After = nil
}
}
@ -197,7 +232,7 @@ stmts:
$$ = $1
$<lastRule>$ = nil
}
| stmts _COMMENT
| stmts _COMMENT '\n'
{
$$ = $1
$<lastRule>$ = $<lastRule>1
@ -211,24 +246,209 @@ stmts:
}
stmt:
simple_stmt
{
$$ = $1
}
| block_stmt
{
$$ = []Expr{$1}
}
block_stmt:
_DEF _IDENT '(' exprs_opt ')' ':' suite
{
$$ = &FuncDef{
Start: $1,
Name: $<tok>2,
ListStart: $3,
Args: $4,
Body: $7,
End: $7.End,
ForceCompact: forceCompact($3, $4, $5),
ForceMultiLine: forceMultiLine($3, $4, $5),
}
}
| _FOR primary_exprs _IN expr ':' suite
{
$$ = &ForLoop{
Start: $1,
LoopVars: $2,
Iterable: $4,
Body: $6,
End: $6.End,
}
}
| if_else_block
{
$$ = $1
}
if_else_block:
_IF expr ':' suite
{
$$ = &IfElse{
Start: $1,
Conditions: []Condition{
Condition{
If: $2,
Then: $4,
},
},
End: $4.End,
}
}
| if_else_block elif expr ':' suite
{
block := $1.(*IfElse)
block.Conditions = append(block.Conditions, Condition{
If: $3,
Then: $5,
})
block.End = $5.End
$$ = block
}
| if_else_block _ELSE ':' suite
{
block := $1.(*IfElse)
block.Conditions = append(block.Conditions, Condition{
Then: $4,
})
block.End = $4.End
$$ = block
}
elif:
_ELSE _IF
| _ELIF
simple_stmt:
small_stmt small_stmts_continuation semi_opt '\n'
{
$$ = append([]Expr{$1}, $2...)
$<lastRule>$ = $$[len($$)-1]
}
small_stmts_continuation:
{
$$ = []Expr{}
}
| small_stmts_continuation ';' small_stmt
{
$$ = append($1, $3)
}
small_stmt:
expr %prec ShiftInstead
| _RETURN expr
{
_, end := $2.Span()
$$ = &ReturnExpr{
X: $2,
End: end,
}
}
| _RETURN
{
$$ = &ReturnExpr{End: $1}
}
| _PYTHON
{
$$ = &PythonBlock{Start: $1, Token: $<tok>1}
}
semi_opt:
| semi_opt ';'
| ';'
expr:
primary_expr:
ident
| primary_expr '.' _IDENT
{
$$ = &DotExpr{
X: $1,
Dot: $2,
NamePos: $3,
Name: $<tok>3,
}
}
| _LOAD '(' exprs_opt ')'
{
$$ = &CallExpr{
X: &LiteralExpr{Start: $1, Token: "load"},
ListStart: $2,
List: $3,
End: End{Pos: $4},
ForceCompact: forceCompact($2, $3, $4),
ForceMultiLine: forceMultiLine($2, $3, $4),
}
}
| primary_expr '(' exprs_opt ')'
{
$$ = &CallExpr{
X: $1,
ListStart: $2,
List: $3,
End: End{Pos: $4},
ForceCompact: forceCompact($2, $3, $4),
ForceMultiLine: forceMultiLine($2, $3, $4),
}
}
| primary_expr '[' expr ']'
{
$$ = &IndexExpr{
X: $1,
IndexStart: $2,
Y: $3,
End: $4,
}
}
| primary_expr '[' expr_opt ':' expr_opt ']'
{
$$ = &SliceExpr{
X: $1,
SliceStart: $2,
From: $3,
FirstColon: $4,
To: $5,
End: $6,
}
}
| primary_expr '[' expr_opt ':' expr_opt ':' expr_opt ']'
{
$$ = &SliceExpr{
X: $1,
SliceStart: $2,
From: $3,
FirstColon: $4,
To: $5,
SecondColon: $6,
Step: $7,
End: $8,
}
}
| primary_expr '(' expr for_clauses_with_if_clauses_opt ')'
{
$$ = &CallExpr{
X: $1,
ListStart: $2,
List: []Expr{
&ListForExpr{
Brack: "",
Start: $2,
X: $3,
For: $4,
End: End{Pos: $5},
},
},
End: End{Pos: $5},
}
}
| strings %prec ShiftInstead
{
if len($1) == 1 {
$$ = $1[0]
break
}
$$ = $1[0]
for _, x := range $1[1:] {
_, end := $$.Span()
@ -322,63 +542,10 @@ expr:
}
}
}
| expr '.' _IDENT
{
$$ = &DotExpr{
X: $1,
Dot: $2,
NamePos: $3,
Name: $<tok>3,
}
}
| expr '(' exprs_opt ')'
{
$$ = &CallExpr{
X: $1,
ListStart: $2,
List: $3,
End: End{Pos: $4},
ForceCompact: forceCompact($2, $3, $4),
ForceMultiLine: forceMultiLine($2, $3, $4),
}
}
| expr '(' expr for_clauses_with_if_clauses_opt ')'
{
$$ = &CallExpr{
X: $1,
ListStart: $2,
List: []Expr{
&ListForExpr{
Brack: "",
Start: $2,
X: $3,
For: $4,
End: End{Pos: $5},
},
},
End: End{Pos: $5},
}
}
| expr '[' expr ']'
{
$$ = &IndexExpr{
X: $1,
IndexStart: $2,
Y: $3,
End: $4,
}
}
| expr '[' expr_opt ':' expr_opt ']'
{
$$ = &SliceExpr{
X: $1,
SliceStart: $2,
Y: $3,
Colon: $4,
Z: $5,
End: $6,
}
}
| '-' primary_expr %prec _UNARY { $$ = unary($1, $<tok>1, $2) }
expr:
primary_expr
| _LAMBDA exprs ':' expr
{
$$ = &LambdaExpr{
@ -388,7 +555,6 @@ expr:
Expr: $4,
}
}
| '-' expr %prec _UNARY { $$ = unary($1, $<tok>1, $2) }
| _NOT expr %prec _UNARY { $$ = unary($1, $<tok>1, $2) }
| '*' expr %prec _UNARY { $$ = unary($1, $<tok>1, $2) }
| expr '*' expr { $$ = binary($1, $2, $<tok>2, $3) }
@ -403,7 +569,7 @@ expr:
| expr _NE expr { $$ = binary($1, $2, $<tok>2, $3) }
| expr _GE expr { $$ = binary($1, $2, $<tok>2, $3) }
| expr '=' expr { $$ = binary($1, $2, $<tok>2, $3) }
| expr _ADDEQ expr { $$ = binary($1, $2, $<tok>2, $3) }
| expr _AUGM expr { $$ = binary($1, $2, $<tok>2, $3) }
| expr _IN expr { $$ = binary($1, $2, $<tok>2, $3) }
| expr _NOT _IN expr { $$ = binary($1, $2, "not in", $4) }
| expr _OR expr { $$ = binary($1, $2, $<tok>2, $3) }
@ -416,15 +582,15 @@ expr:
$$ = binary($1, $2, $<tok>2, $3)
}
}
| expr _IF expr _ELSE expr
| expr _IF expr _ELSE expr
{
$$ = &ConditionalExpr{
Then: $1,
IfStart: $2,
Test: $3,
ElseStart: $4,
Else: $5,
}
$$ = &ConditionalExpr{
Then: $1,
IfStart: $2,
Test: $3,
ElseStart: $4,
Else: $5,
}
}
expr_opt:
@ -491,6 +657,16 @@ exprs_opt:
$$, $<comma>$ = $1, $2
}
primary_exprs:
primary_expr
{
$$ = []Expr{$1}
}
| primary_exprs ',' primary_expr
{
$$ = append($1, $3)
}
string:
_STRING
{
@ -519,18 +695,8 @@ ident:
$$ = &LiteralExpr{Start: $1, Token: $<tok>1}
}
idents:
ident
{
$$ = []Expr{$1}
}
| idents ',' ident
{
$$ = append($1, $3)
}
for_clause:
_FOR idents _IN expr
_FOR primary_exprs _IN expr
{
$$ = &ForClause{
For: $1,
@ -539,15 +705,6 @@ for_clause:
Expr: $4,
}
}
| _FOR '(' idents ')' _IN expr
{
$$ = &ForClause{
For: $1,
Var: $3,
In: $5,
Expr: $6,
}
}
for_clause_with_if_clauses_opt:
for_clause if_clauses_opt {
@ -558,13 +715,13 @@ for_clause_with_if_clauses_opt:
}
for_clauses_with_if_clauses_opt:
for_clause_with_if_clauses_opt
{
$$ = []*ForClauseWithIfClausesOpt{$1}
}
for_clause_with_if_clauses_opt
{
$$ = []*ForClauseWithIfClausesOpt{$1}
}
| for_clauses_with_if_clauses_opt for_clause_with_if_clauses_opt {
$$ = append($1, $2)
}
$$ = append($1, $2)
}
if_clauses_opt:
{
@ -606,6 +763,30 @@ func binary(x Expr, pos Position, op string, y Expr) Expr {
}
}
// isSimpleExpression returns whether an expression is simple and allowed to exist in
// compact forms of sequences.
// The formal criteria are the following: an expression is considered simple if it's
// a literal (variable, string or a number), a literal with a unary operator or an empty sequence.
func isSimpleExpression(expr *Expr) bool {
switch x := (*expr).(type) {
case *LiteralExpr, *StringExpr:
return true
case *UnaryExpr:
_, ok := x.X.(*LiteralExpr)
return ok
case *ListExpr:
return len(x.List) == 0
case *TupleExpr:
return len(x.List) == 0
case *DictExpr:
return len(x.List) == 0
case *SetExpr:
return len(x.List) == 0
default:
return false
}
}
// forceCompact returns the setting for the ForceCompact field for a call or tuple.
//
// NOTE 1: The field is called ForceCompact, not ForceSingleLine,
@ -654,10 +835,7 @@ func forceCompact(start Position, list []Expr, end Position) bool {
return false
}
line = end.Line
switch x.(type) {
case *LiteralExpr, *StringExpr, *UnaryExpr:
// ok
default:
if !isSimpleExpression(&x) {
return false
}
}

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,9 @@ import (
"strings"
)
const nestedIndentation = 2 // Indentation of nested blocks
const listIndentation = 4 // Indentation of multiline expressions
// Format returns the formatted form of the given BUILD file.
func Format(f *File) []byte {
pr := &printer{}
@ -118,7 +121,19 @@ func (p *printer) file(f *File) {
p.newline()
}
for i, stmt := range f.Stmt {
p.statements(f.Stmt)
for _, com := range f.After {
p.printf("%s", strings.TrimSpace(com.Token))
p.newline()
}
// If the last expression is in an indented code block there can be spaces in the last line.
p.trim()
}
func (p *printer) statements(stmts []Expr) {
for i, stmt := range stmts {
switch stmt := stmt.(type) {
case *CommentBlock:
// comments already handled
@ -128,11 +143,17 @@ func (p *printer) file(f *File) {
p.printf("%s", strings.TrimSpace(com.Token))
p.newline()
}
p.printf("%s", stmt.Token) // includes trailing newline
p.printf("%s", stmt.Token)
p.newline()
default:
p.expr(stmt, precLow)
p.newline()
// Print an empty line break after the expression unless it's a code block.
// For a code block, the line break is generated by its last statement.
if !isCodeBlock(stmt) {
p.newline()
}
}
for _, com := range stmt.Comment().After {
@ -140,28 +161,26 @@ func (p *printer) file(f *File) {
p.newline()
}
if i+1 < len(f.Stmt) && !compactStmt(stmt, f.Stmt[i+1]) {
if i+1 < len(stmts) && !compactStmt(stmt, stmts[i+1], p.margin == 0) {
p.newline()
}
}
for _, com := range f.After {
p.printf("%s", strings.TrimSpace(com.Token))
p.newline()
}
}
// compactStmt reports whether the pair of statements s1, s2
// should be printed without an intervening blank line.
// We omit the blank line when both are subinclude statements
// and the second one has no leading comments.
func compactStmt(s1, s2 Expr) bool {
func compactStmt(s1, s2 Expr, isTopLevel bool) bool {
if len(s2.Comment().Before) > 0 {
return false
}
return (isCall(s1, "subinclude") || isCall(s1, "load")) &&
(isCall(s2, "subinclude") || isCall(s2, "load"))
if isTopLevel {
return isCall(s1, "load") && isCall(s2, "load")
} else {
return !(isCodeBlock(s1) || isCodeBlock(s2))
}
}
// isCall reports whether x is a call to a function with the given name.
@ -177,6 +196,20 @@ func isCall(x Expr, name string) bool {
return nam.Token == name
}
// isCodeBlock checks if the statement is a code block (def, if, for, etc.)
func isCodeBlock(x Expr) bool {
switch x.(type) {
case *FuncDef:
return true
case *ForLoop:
return true
case *IfElse:
return true
default:
return false
}
}
// Expression formatting.
// The expression formatter must introduce parentheses to force the
@ -220,6 +253,11 @@ const (
var opPrec = map[string]int{
"=": precAssign,
"+=": precAssign,
"-=": precAssign,
"*=": precAssign,
"/=": precAssign,
"//=": precAssign,
"%=": precAssign,
"or": precOr,
"and": precAnd,
"<": precCmp,
@ -232,6 +270,7 @@ var opPrec = map[string]int{
"-": precAdd,
"*": precMultiply,
"/": precMultiply,
"//": precMultiply,
"%": precMultiply,
}
@ -327,12 +366,18 @@ func (p *printer) expr(v Expr, outerPrec int) {
addParen(precSuffix)
p.expr(v.X, precSuffix)
p.printf("[")
if v.Y != nil {
p.expr(v.Y, precLow)
if v.From != nil {
p.expr(v.From, precLow)
}
p.printf(":")
if v.Z != nil {
p.expr(v.Z, precLow)
if v.To != nil {
p.expr(v.To, precLow)
}
if v.SecondColon.Byte != 0 {
p.printf(":")
if v.Step != nil {
p.expr(v.Step, precLow)
}
}
p.printf("]")
@ -379,7 +424,7 @@ func (p *printer) expr(v Expr, outerPrec int) {
if v.LineBreak {
p.margin = p.indent()
if v.Op == "=" {
p.margin += 4
p.margin += listIndentation
}
}
@ -427,6 +472,61 @@ func (p *printer) expr(v Expr, outerPrec int) {
p.expr(v.Test, precSuffix)
p.printf(" else ")
p.expr(v.Else, precSuffix)
case *ReturnExpr:
p.printf("return")
if v.X != nil {
p.printf(" ")
p.expr(v.X, precSuffix)
}
case *FuncDef:
p.printf("def ")
p.printf(v.Name)
p.seq("()", v.Args, &v.End, modeCall, v.ForceCompact, v.ForceMultiLine)
p.printf(":")
p.margin += nestedIndentation
p.newline()
p.statements(v.Body.Statements)
p.margin -= nestedIndentation
case *ForLoop:
p.printf("for ")
for i, loopVar := range v.LoopVars {
if i > 0 {
p.printf(", ")
}
p.expr(loopVar, precLow)
}
p.printf(" in ")
p.expr(v.Iterable, precLow)
p.printf(":")
p.margin += nestedIndentation
p.newline()
p.statements(v.Body.Statements)
p.margin -= nestedIndentation
case *IfElse:
for i, block := range v.Conditions {
if i == 0 {
p.printf("if ")
} else if block.If == nil {
p.newline()
p.printf("else")
} else {
p.newline()
p.printf("elif ")
}
if block.If != nil {
p.expr(block.If, precLow)
}
p.printf(":")
p.margin += nestedIndentation
p.newline()
p.statements(block.Then.Statements)
p.margin -= nestedIndentation
}
}
// Add closing parenthesis if needed.
@ -452,6 +552,7 @@ const (
modeTuple // (x,)
modeParen // (x)
modeDict // {x:y}
modeSeq // x, y
)
// seq formats a list of values inside a given bracket pair (brack = "()", "[]", "{}").
@ -504,7 +605,7 @@ func (p *printer) seq(brack string, list []Expr, end *End, mode seqMode, forceCo
default:
// Multi-line form.
p.margin += 4
p.margin += listIndentation
for i, x := range list {
// If we are about to break the line before the first
// element and there are trailing end-of-line comments
@ -528,7 +629,7 @@ func (p *printer) seq(brack string, list []Expr, end *End, mode seqMode, forceCo
p.newline()
p.printf("%s", strings.TrimSpace(com.Token))
}
p.margin -= 4
p.margin -= listIndentation
p.newline()
}
p.depth--
@ -566,7 +667,7 @@ func (p *printer) listFor(v *ListForExpr) {
if multiLine {
if v.Brack != "" {
p.margin += 4
p.margin += listIndentation
}
p.newline()
}
@ -602,7 +703,7 @@ func (p *printer) listFor(v *ListForExpr) {
p.printf("%s", strings.TrimSpace(com.Token))
}
if v.Brack != "" {
p.margin -= 4
p.margin -= listIndentation
}
p.newline()
}
@ -612,3 +713,7 @@ func (p *printer) listFor(v *ListForExpr) {
p.depth--
}
}
func (p *printer) isTopLevel() bool {
return p.margin == 0
}

View File

@ -18,33 +18,48 @@ distributed under the License is distributed on an "AS IS" BASIS,
package build
import "strings"
import (
"strings"
"path/filepath"
)
// A Rule represents a single BUILD rule.
type Rule struct {
Call *CallExpr
Call *CallExpr
ImplicitName string // The name which should be used if the name attribute is not set. See the comment on File.implicitRuleName.
}
func (f *File) Rule(call *CallExpr) *Rule {
r := &Rule{call, ""}
if r.AttrString("name") == "" {
r.ImplicitName = f.implicitRuleName()
}
return r
}
// Rules returns the rules in the file of the given kind (such as "go_library").
// If kind == "", Rules returns all rules in the file.
func (f *File) Rules(kind string) []*Rule {
var all []*Rule
for _, stmt := range f.Stmt {
call, ok := stmt.(*CallExpr)
if !ok {
continue
}
rule := &Rule{call}
rule := f.Rule(call)
if kind != "" && rule.Kind() != kind {
continue
}
all = append(all, rule)
}
return all
}
// RuleAt returns the rule in the file that starts at the specified line, or null if no such rule.
func (f *File) RuleAt(linenum int) *Rule {
for _, stmt := range f.Stmt {
call, ok := stmt.(*CallExpr)
if !ok {
@ -52,7 +67,7 @@ func (f *File) RuleAt(linenum int) *Rule {
}
start, end := call.X.Span()
if start.Line <= linenum && linenum <= end.Line {
return &Rule{call}
return f.Rule(call)
}
}
return nil
@ -65,9 +80,9 @@ func (f *File) DelRules(kind, name string) int {
var i int
for _, stmt := range f.Stmt {
if call, ok := stmt.(*CallExpr); ok {
r := &Rule{call}
r := f.Rule(call)
if (kind == "" || r.Kind() == kind) &&
(name == "" || r.AttrString("name") == name) {
(name == "" || r.Name() == name) {
continue
}
}
@ -79,6 +94,42 @@ func (f *File) DelRules(kind, name string) int {
return n
}
// If a build file contains exactly one unnamed rule, and no rules in the file explicitly have the
// same name as the name of the directory the build file is in, we treat the unnamed rule as if it
// had the name of the directory containing the BUILD file.
// This is following a convention used in the Pants build system to cut down on boilerplate.
func (f *File) implicitRuleName() string {
// We disallow empty names in the top-level BUILD files.
dir := filepath.Dir(f.Path)
if dir == "." {
return ""
}
sawAnonymousRule := false
possibleImplicitName := filepath.Base(dir)
for _, stmt := range f.Stmt {
call, ok := stmt.(*CallExpr)
if !ok {
continue
}
temp := &Rule{call, ""}
if temp.AttrString("name") == possibleImplicitName {
// A target explicitly has the name of the dir, so no implicit targets are allowed.
return ""
}
if temp.Kind() != "" && temp.AttrString("name") == "" {
if sawAnonymousRule {
return ""
}
sawAnonymousRule = true
}
}
if sawAnonymousRule {
return possibleImplicitName
}
return ""
}
// Kind returns the rule's kind (such as "go_library").
// The kind of the rule may be given by a literal or it may be a sequence of dot expressions that
// begins with a literal, if the call expression does not conform to either of these forms, an
@ -118,9 +169,13 @@ func (r *Rule) SetKind(kind string) {
}
// Name returns the rule's target name.
// If the rule has no target name, Name returns the empty string.
// If the rule has no explicit target name, Name returns the implicit name if there is one, else the empty string.
func (r *Rule) Name() string {
return r.AttrString("name")
explicitName := r.AttrString("name")
if explicitName == "" {
return r.ImplicitName
}
return explicitName
}
// AttrKeys returns the keys of all the rule's attributes.

View File

@ -57,9 +57,8 @@ type Expr interface {
// A Comment represents a single # comment.
type Comment struct {
Start Position
Token string // without trailing newline
Suffix bool // an end of line (not whole line) comment
Start Position
Token string // without trailing newline
}
// Comments collects the comments associated with an expression.
@ -87,12 +86,12 @@ type File struct {
Stmt []Expr
}
func (x *File) Span() (start, end Position) {
if len(x.Stmt) == 0 {
func (f *File) Span() (start, end Position) {
if len(f.Stmt) == 0 {
return
}
start, _ = x.Stmt[0].Span()
_, end = x.Stmt[len(x.Stmt)-1].Span()
start, _ = f.Stmt[0].Span()
_, end = f.Stmt[len(f.Stmt)-1].Span()
return start, end
}
@ -360,15 +359,17 @@ func (x *ParenExpr) Span() (start, end Position) {
return x.Start, x.End.Pos.add(")")
}
// A SliceExpr represents a slice expression: X[Y:Z].
// A SliceExpr represents a slice expression: expr[from:to] or expr[from:to:step] .
type SliceExpr struct {
Comments
X Expr
SliceStart Position
Y Expr
Colon Position
Z Expr
End Position
X Expr
SliceStart Position
From Expr
FirstColon Position
To Expr
SecondColon Position
Step Expr
End Position
}
func (x *SliceExpr) Span() (start, end Position) {
@ -421,3 +422,74 @@ func (x *ConditionalExpr) Span() (start, end Position) {
_, end = x.Else.Span()
return start, end
}
// A CodeBlock represents an indented code block.
type CodeBlock struct {
Statements []Expr
Start Position
End
}
func (x *CodeBlock) Span() (start, end Position) {
return x.Start, x.End.Pos
}
// A FuncDef represents a function definition expression: def foo(List):.
type FuncDef struct {
Comments
Start Position // position of def
Name string
ListStart Position // position of (
Args []Expr
Body CodeBlock
End // position of the end
ForceCompact bool // force compact (non-multiline) form when printing
ForceMultiLine bool // force multiline form when printing
}
func (x *FuncDef) Span() (start, end Position) {
return x.Start, x.End.Pos
}
// A ReturnExpr represents a return statement: return f(x).
type ReturnExpr struct {
Comments
Start Position
X Expr
End Position
}
func (x *ReturnExpr) Span() (start, end Position) {
return x.Start, x.End
}
// A ForLoop represents a for loop block: for x in range(10):.
type ForLoop struct {
Comments
Start Position // position of for
LoopVars []Expr
Iterable Expr
Body CodeBlock
End // position of the end
}
func (x *ForLoop) Span() (start, end Position) {
return x.Start, x.End.Pos
}
// An IfElse represents an if-else blocks sequence: if x: ... elif y: ... else: ... .
type IfElse struct {
Comments
Start Position // position of if
Conditions []Condition
End // position of the end
}
type Condition struct {
If Expr
Then CodeBlock
}
func (x *IfElse) Span() (start, end Position) {
return x.Start, x.End.Pos
}

View File

@ -72,11 +72,14 @@ func walk1(v *Expr, stack *[]Expr, f func(x Expr, stk []Expr) Expr) Expr {
walk1(&v.Value, stack, f)
case *SliceExpr:
walk1(&v.X, stack, f)
if v.Y != nil {
walk1(&v.Y, stack, f)
if v.From != nil {
walk1(&v.From, stack, f)
}
if v.Z != nil {
walk1(&v.Z, stack, f)
if v.To != nil {
walk1(&v.To, stack, f)
}
if v.Step != nil {
walk1(&v.Step, stack, f)
}
case *ParenExpr:
walk1(&v.X, stack, f)

View File

@ -1,6 +1,5 @@
// Code generated by protoc-gen-go.
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: build_proto/build.proto
// DO NOT EDIT!
/*
Package blaze_query is a generated protocol buffer package.

View File

@ -92,6 +92,7 @@ Buildozer supports the following commands(`'command args'`):
* `new <rule_kind> <rule_name> [(before|after) <relative_rule_name>]`: Add a
new rule at the end of the BUILD file (before/after `<relative_rule>`).
* `print <attr(s)>`
* `remove <attr>`: Removes attribute `attr`.
* `remove <attr> <value(s)>`: Removes `value(s)` from the list `attr`. The
wildcard `*` matches all attributes. Lists containing none of the `value(s)` are
not modified.
@ -100,6 +101,13 @@ Buildozer supports the following commands(`'command args'`):
* `replace <attr> <old_value> <new_value>`: Replaces `old_value` with `new_value`
in the list `attr`. Wildcard `*` matches all attributes. Lists not containing
`old_value` are not modified.
* `substitute <attr> <old_regexp> <new_template>`: Replaces strings which
match `old_regexp` in the list `attr` according to `new_template`. Wildcard
`*` matches all attributes. The regular expression must follow
[RE2 syntax](https://github.com/google/re2/wiki/Syntax). `new_template` may
be a simple replacement string, but it may also expand numbered or named
groups using `$0` or `$x`. Lists without strings that match `old_regexp`
are not modified.
* `set <attr> <value(s)>`: Sets the value of an attribute. If the attribute
was already present, its old value is replaced.
* `set_if_absent <attr> <value(s)>`: Sets the value of an attribute. If the
@ -138,6 +146,9 @@ buildozer 'set kind java_library' //pkg:%gwt_module
# Replace the dependency on pkg_v1 with a dependency on pkg_v2
buildozer 'replace deps //pkg_v1 //pkg_v2' //pkg:rule
# Replace all dependencies using regular expressions.
buildozer 'substitute deps //old/(.*) //new/${1}' //pkg:rule
# Delete the dependency on foo in every cc_library in the package
buildozer 'remove deps foo' //pkg:%cc_library

View File

@ -38,6 +38,7 @@ var (
editVariables = flag.Bool("edit-variables", false, "For attributes that simply assign a variable (e.g. hdrs = LIB_HDRS), edit the build variable instead of appending to the attribute.")
isPrintingProto = flag.Bool("output_proto", false, "output serialized devtools.buildozer.Output protos instead of human-readable strings.")
tablesPath = flag.String("tables", "", "path to JSON file with custom table definitions which will replace the built-in tables")
addTablesPath = flag.String("add_tables", "", "path to JSON file with custom table definitions which will be merged with the built-in tables")
shortenLabelsFlag = flag.Bool("shorten_labels", true, "convert added labels to short form, e.g. //foo:bar => :bar")
deleteWithComments = flag.Bool("delete_with_comments", true, "If a list attribute should be deleted even if there is a comment attached to it")
@ -67,9 +68,16 @@ func main() {
}
}
if *addTablesPath != "" {
if err := tables.ParseAndUpdateJSONDefinitions(*addTablesPath, true); err != nil {
fmt.Fprintf(os.Stderr, "buildifier: failed to parse %s for -add_tables: %s\n", *addTablesPath, err)
os.Exit(2)
}
}
edit.ShortenLabelsFlag = *shortenLabelsFlag
edit.DeleteWithComments = *deleteWithComments
edit.Opts = edit.Options{
opts := &edit.Options{
Stdout: *stdout,
Buildifier: *buildifier,
Parallelism: *parallelism,
@ -83,5 +91,5 @@ func main() {
EditVariables: *editVariables,
IsPrintingProto: *isPrintingProto,
}
os.Exit(edit.Buildozer(flag.Args()))
os.Exit(edit.Buildozer(opts, flag.Args()))
}

View File

@ -17,6 +17,7 @@ go_library(
"//vendor/github.com/bazelbuild/buildtools/build_proto:go_default_library",
"//vendor/github.com/bazelbuild/buildtools/file:go_default_library",
"//vendor/github.com/bazelbuild/buildtools/lang:go_default_library",
"//vendor/github.com/bazelbuild/buildtools/tables:go_default_library",
"//vendor/github.com/bazelbuild/buildtools/wspace:go_default_library",
"//vendor/github.com/golang/protobuf/proto:go_default_library",
],

View File

@ -53,8 +53,10 @@ type Options struct {
IsPrintingProto bool // output serialized devtools.buildozer.Output protos instead of human-readable strings
}
// Opts represents the options to be used by buildozer, and can be overriden before calling Buildozer.
var Opts = Options{NumIO: 200, PreferEOLComments: true}
// NewOpts returns a new Options struct with some defaults set.
func NewOpts() *Options {
return &Options{NumIO: 200, PreferEOLComments: true}
}
// Usage is a user-overriden func to print the program usage.
var Usage = func() {}
@ -75,7 +77,7 @@ type CmdEnvironment struct {
// The cmdXXX functions implement the various commands.
func cmdAdd(env CmdEnvironment) (*build.File, error) {
func cmdAdd(opts *Options, env CmdEnvironment) (*build.File, error) {
attr := env.Args[0]
for _, val := range env.Args[1:] {
if IsIntList(attr) {
@ -88,14 +90,14 @@ func cmdAdd(env CmdEnvironment) (*build.File, error) {
return env.File, nil
}
func cmdComment(env CmdEnvironment) (*build.File, error) {
func cmdComment(opts *Options, env CmdEnvironment) (*build.File, error) {
// The comment string is always the last argument in the list.
str := env.Args[len(env.Args)-1]
str = strings.Replace(str, "\\n", "\n", -1)
// Multiline comments should go on a separate line.
fullLine := !Opts.PreferEOLComments || strings.Contains(str, "\n")
fullLine := !opts.PreferEOLComments || strings.Contains(str, "\n")
str = strings.Replace("# "+str, "\n", "\n# ", -1)
comment := []build.Comment{build.Comment{Token: str}}
comment := []build.Comment{{Token: str}}
// The comment might be attached to a rule, an attribute, or a value in a list,
// depending on how many arguments are passed.
@ -139,7 +141,7 @@ func commentsText(comments []build.Comment) string {
return strings.Replace(strings.Join(segments, " "), "\n", " ", -1)
}
func cmdPrintComment(env CmdEnvironment) (*build.File, error) {
func cmdPrintComment(opts *Options, env CmdEnvironment) (*build.File, error) {
attrError := func() error {
return fmt.Errorf("rule \"//%s:%s\" has no attribute \"%s\"", env.Pkg, env.Rule.Name(), env.Args[0])
}
@ -147,7 +149,7 @@ func cmdPrintComment(env CmdEnvironment) (*build.File, error) {
switch len(env.Args) {
case 0: // Print rule comment.
env.output.Fields = []*apipb.Output_Record_Field{
&apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{commentsText(env.Rule.Call.Comments.Before)}},
{Value: &apipb.Output_Record_Field_Text{commentsText(env.Rule.Call.Comments.Before)}},
}
case 1: // Print attribute comment.
attr := env.Rule.AttrDefn(env.Args[0])
@ -156,7 +158,7 @@ func cmdPrintComment(env CmdEnvironment) (*build.File, error) {
}
comments := append(attr.Before, attr.Suffix...)
env.output.Fields = []*apipb.Output_Record_Field{
&apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{commentsText(comments)}},
{Value: &apipb.Output_Record_Field_Text{commentsText(comments)}},
}
case 2: // Print comment of a specific value in a list.
attr := env.Rule.Attr(env.Args[0])
@ -170,7 +172,7 @@ func cmdPrintComment(env CmdEnvironment) (*build.File, error) {
}
comments := append(expr.Comments.Before, expr.Comments.Suffix...)
env.output.Fields = []*apipb.Output_Record_Field{
&apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{commentsText(comments)}},
{Value: &apipb.Output_Record_Field_Text{commentsText(comments)}},
}
default:
panic("cmdPrintComment")
@ -178,11 +180,11 @@ func cmdPrintComment(env CmdEnvironment) (*build.File, error) {
return nil, nil
}
func cmdDelete(env CmdEnvironment) (*build.File, error) {
func cmdDelete(opts *Options, env CmdEnvironment) (*build.File, error) {
return DeleteRule(env.File, env.Rule), nil
}
func cmdMove(env CmdEnvironment) (*build.File, error) {
func cmdMove(opts *Options, env CmdEnvironment) (*build.File, error) {
oldAttr := env.Args[0]
newAttr := env.Args[1]
if len(env.Args) == 3 && env.Args[2] == "*" {
@ -204,7 +206,7 @@ func cmdMove(env CmdEnvironment) (*build.File, error) {
return nil, nil
}
func cmdNew(env CmdEnvironment) (*build.File, error) {
func cmdNew(opts *Options, env CmdEnvironment) (*build.File, error) {
kind := env.Args[0]
name := env.Args[1]
addAtEOF, insertionIndex, err := findInsertionIndex(env)
@ -217,7 +219,7 @@ func cmdNew(env CmdEnvironment) (*build.File, error) {
}
call := &build.CallExpr{X: &build.LiteralExpr{Token: kind}}
rule := &build.Rule{Call: call}
rule := &build.Rule{call, ""}
rule.SetAttr("name", &build.StringExpr{Value: name})
if addAtEOF {
@ -235,7 +237,7 @@ func findInsertionIndex(env CmdEnvironment) (bool, int, error) {
}
relativeToRuleName := env.Args[3]
ruleIdx := IndexOfRuleByName(env.File, relativeToRuleName)
ruleIdx, _ := IndexOfRuleByName(env.File, relativeToRuleName)
if ruleIdx == -1 {
return true, 0, nil
}
@ -250,12 +252,12 @@ func findInsertionIndex(env CmdEnvironment) (bool, int, error) {
}
}
func cmdNewLoad(env CmdEnvironment) (*build.File, error) {
func cmdNewLoad(opts *Options, env CmdEnvironment) (*build.File, error) {
env.File.Stmt = InsertLoad(env.File.Stmt, env.Args)
return env.File, nil
}
func cmdPrint(env CmdEnvironment) (*build.File, error) {
func cmdPrint(opts *Options, env CmdEnvironment) (*build.File, error) {
format := env.Args
if len(format) == 0 {
format = []string{"name", "kind"}
@ -266,8 +268,10 @@ func cmdPrint(env CmdEnvironment) (*build.File, error) {
value := env.Rule.Attr(str)
if str == "kind" {
fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{env.Rule.Kind()}}
} else if str == "name" {
fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{env.Rule.Name()}}
} else if str == "label" {
if env.Rule.Attr("name") != nil {
if env.Rule.Name() != "" {
fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{fmt.Sprintf("//%s:%s", env.Pkg, env.Rule.Name())}}
} else {
return nil, nil
@ -310,7 +314,7 @@ func attrKeysForPattern(rule *build.Rule, pattern string) []string {
return []string{pattern}
}
func cmdRemove(env CmdEnvironment) (*build.File, error) {
func cmdRemove(opts *Options, env CmdEnvironment) (*build.File, error) {
if len(env.Args) == 1 { // Remove the attribute
if env.Rule.DelAttr(env.Args[0]) != nil {
return env.File, nil
@ -330,7 +334,7 @@ func cmdRemove(env CmdEnvironment) (*build.File, error) {
return nil, nil
}
func cmdRename(env CmdEnvironment) (*build.File, error) {
func cmdRename(opts *Options, env CmdEnvironment) (*build.File, error) {
oldAttr := env.Args[0]
newAttr := env.Args[1]
if err := RenameAttribute(env.Rule, oldAttr, newAttr); err != nil {
@ -339,7 +343,7 @@ func cmdRename(env CmdEnvironment) (*build.File, error) {
return env.File, nil
}
func cmdReplace(env CmdEnvironment) (*build.File, error) {
func cmdReplace(opts *Options, env CmdEnvironment) (*build.File, error) {
oldV := env.Args[1]
newV := env.Args[2]
for _, key := range attrKeysForPattern(env.Rule, env.Args[0]) {
@ -355,7 +359,27 @@ func cmdReplace(env CmdEnvironment) (*build.File, error) {
return env.File, nil
}
func cmdSet(env CmdEnvironment) (*build.File, error) {
func cmdSubstitute(opts *Options, env CmdEnvironment) (*build.File, error) {
oldRegexp, err := regexp.Compile(env.Args[1])
if err != nil {
return nil, err
}
newTemplate := env.Args[2]
for _, key := range attrKeysForPattern(env.Rule, env.Args[0]) {
attr := env.Rule.Attr(key)
e, ok := attr.(*build.StringExpr)
if !ok {
ListSubstitute(attr, oldRegexp, newTemplate)
continue
}
if newValue, ok := stringSubstitute(e.Value, oldRegexp, newTemplate); ok {
env.Rule.SetAttr(key, getAttrValueExpr(key, []string{newValue}))
}
}
return env.File, nil
}
func cmdSet(opts *Options, env CmdEnvironment) (*build.File, error) {
attr := env.Args[0]
args := env.Args[1:]
if attr == "kind" {
@ -366,7 +390,7 @@ func cmdSet(env CmdEnvironment) (*build.File, error) {
return env.File, nil
}
func cmdSetIfAbsent(env CmdEnvironment) (*build.File, error) {
func cmdSetIfAbsent(opts *Options, env CmdEnvironment) (*build.File, error) {
attr := env.Args[0]
args := env.Args[1:]
if attr == "kind" {
@ -401,14 +425,14 @@ func getAttrValueExpr(attr string, args []string) build.Expr {
}
}
func cmdCopy(env CmdEnvironment) (*build.File, error) {
func cmdCopy(opts *Options, env CmdEnvironment) (*build.File, error) {
attrName := env.Args[0]
from := env.Args[1]
return copyAttributeBetweenRules(env, attrName, from)
}
func cmdCopyNoOverwrite(env CmdEnvironment) (*build.File, error) {
func cmdCopyNoOverwrite(opts *Options, env CmdEnvironment) (*build.File, error) {
attrName := env.Args[0]
from := env.Args[1]
@ -438,7 +462,7 @@ func copyAttributeBetweenRules(env CmdEnvironment, attrName string, from string)
return env.File, nil
}
func cmdFix(env CmdEnvironment) (*build.File, error) {
func cmdFix(opts *Options, env CmdEnvironment) (*build.File, error) {
// Fix the whole file
if env.Rule.Kind() == "package" {
return FixFile(env.File, env.Pkg, env.Args), nil
@ -449,7 +473,7 @@ func cmdFix(env CmdEnvironment) (*build.File, error) {
// CommandInfo provides a command function and info on incoming arguments.
type CommandInfo struct {
Fn func(CmdEnvironment) (*build.File, error)
Fn func(*Options, CmdEnvironment) (*build.File, error)
MinArg int
MaxArg int
Template string
@ -470,6 +494,7 @@ var AllCommands = map[string]CommandInfo{
"remove": {cmdRemove, 1, -1, "<attr> <value(s)>"},
"rename": {cmdRename, 2, 2, "<old_attr> <new_attr>"},
"replace": {cmdReplace, 3, 3, "<attr> <old_value> <new_value>"},
"substitute": {cmdSubstitute, 3, 3, "<attr> <old_regexp> <new_template>"},
"set": {cmdSet, 2, -1, "<attr> <value(s)>"},
"set_if_absent": {cmdSetIfAbsent, 2, -1, "<attr> <value(s)>"},
"copy": {cmdCopy, 2, 2, "<attr> <from_rule>"},
@ -500,13 +525,13 @@ func expandTargets(f *build.File, rule string) ([]*build.Rule, error) {
return nil, fmt.Errorf("rule '%s' not found", rule)
}
func filterRules(rules []*build.Rule) (result []*build.Rule) {
if len(Opts.FilterRuleTypes) == 0 {
func filterRules(opts *Options, rules []*build.Rule) (result []*build.Rule) {
if len(opts.FilterRuleTypes) == 0 {
return rules
}
for _, rule := range rules {
acceptableType := false
for _, filterType := range Opts.FilterRuleTypes {
for _, filterType := range opts.FilterRuleTypes {
if rule.Kind() == filterType {
acceptableType = true
break
@ -634,7 +659,7 @@ var buildFileNamesSet = map[string]bool{
// rewrite parses the BUILD file for the given file, transforms the AST,
// and write the changes back in the file (or on stdout).
func rewrite(commandsForFile commandsForFile) *rewriteResult {
func rewrite(opts *Options, commandsForFile commandsForFile) *rewriteResult {
name := commandsForFile.file
var data []byte
var err error
@ -676,7 +701,7 @@ func rewrite(commandsForFile commandsForFile) *rewriteResult {
}
vars := map[string]*build.BinaryExpr{}
if Opts.EditVariables {
if opts.EditVariables {
vars = getGlobalVariables(f.Stmt)
}
var errs []error
@ -684,7 +709,7 @@ func rewrite(commandsForFile commandsForFile) *rewriteResult {
for _, commands := range commandsForFile.commands {
target := commands.target
commands := commands.commands
_, absPkg, rule := InterpretLabelForWorkspaceLocation(Opts.RootDir, target)
_, absPkg, rule := InterpretLabelForWorkspaceLocation(opts.RootDir, target)
_, pkg, _ := ParseLabel(target)
if pkg == stdinPackageName { // Special-case: This is already absolute
absPkg = stdinPackageName
@ -694,23 +719,23 @@ func rewrite(commandsForFile commandsForFile) *rewriteResult {
if err != nil {
cerr := commandError(commands, target, err)
errs = append(errs, cerr)
if !Opts.KeepGoing {
if !opts.KeepGoing {
return &rewriteResult{file: name, errs: errs, records: records}
}
}
targets = filterRules(targets)
targets = filterRules(opts, targets)
for _, cmd := range commands {
for _, r := range targets {
cmdInfo := AllCommands[cmd.tokens[0]]
record := &apipb.Output_Record{}
newf, err := cmdInfo.Fn(CmdEnvironment{f, r, vars, absPkg, cmd.tokens[1:], record})
newf, err := cmdInfo.Fn(opts, CmdEnvironment{f, r, vars, absPkg, cmd.tokens[1:], record})
if len(record.Fields) != 0 {
records = append(records, record)
}
if err != nil {
cerr := commandError([]command{cmd}, target, err)
if Opts.KeepGoing {
if opts.KeepGoing {
errs = append(errs, cerr)
} else {
return &rewriteResult{file: name, errs: []error{cerr}, records: records}
@ -727,12 +752,12 @@ func rewrite(commandsForFile commandsForFile) *rewriteResult {
return &rewriteResult{file: name, errs: errs, records: records}
}
f = RemoveEmptyPackage(f)
ndata, err := runBuildifier(f)
ndata, err := runBuildifier(opts, f)
if err != nil {
return &rewriteResult{file: name, errs: []error{fmt.Errorf("running buildifier: %v", err)}, records: records}
}
if Opts.Stdout || name == stdinPackageName {
if opts.Stdout || name == stdinPackageName {
os.Stdout.Write(ndata)
return &rewriteResult{file: name, errs: errs, records: records}
}
@ -760,15 +785,15 @@ var EditFile = func(fi os.FileInfo, name string) error {
}
// runBuildifier formats the build file f.
// Runs Opts.Buildifier if it's non-empty, otherwise uses built-in formatter.
// Opts.Buildifier is useful to force consistency with other tools that call Buildifier.
func runBuildifier(f *build.File) ([]byte, error) {
if Opts.Buildifier == "" {
// Runs opts.Buildifier if it's non-empty, otherwise uses built-in formatter.
// opts.Buildifier is useful to force consistency with other tools that call Buildifier.
func runBuildifier(opts *Options, f *build.File) ([]byte, error) {
if opts.Buildifier == "" {
build.Rewrite(f, nil)
return build.Format(f), nil
}
cmd := exec.Command(Opts.Buildifier)
cmd := exec.Command(opts.Buildifier)
data := build.Format(f)
cmd.Stdin = bytes.NewBuffer(data)
stdout := bytes.NewBuffer(nil)
@ -787,9 +812,9 @@ func runBuildifier(f *build.File) ([]byte, error) {
// Given a target, whose package may contain a trailing "/...", returns all
// extisting BUILD file paths which match the package.
func targetExpressionToBuildFiles(target string) []string {
file, _, _ := InterpretLabelForWorkspaceLocation(Opts.RootDir, target)
if Opts.RootDir == "" {
func targetExpressionToBuildFiles(opts *Options, target string) []string {
file, _, _ := InterpretLabelForWorkspaceLocation(opts.RootDir, target)
if opts.RootDir == "" {
var err error
if file, err = filepath.Abs(file); err != nil {
fmt.Printf("Cannot make path absolute: %s\n", err.Error())
@ -827,7 +852,7 @@ func targetExpressionToBuildFiles(target string) []string {
// appendCommands adds the given commands to be applied to each of the given targets
// via the commandMap.
func appendCommands(commandMap map[string][]commandsForTarget, args []string) {
func appendCommands(opts *Options, commandMap map[string][]commandsForTarget, args []string) {
commands, targets := parseCommands(args)
for _, target := range targets {
if strings.HasSuffix(target, "/BUILD") {
@ -838,7 +863,7 @@ func appendCommands(commandMap map[string][]commandsForTarget, args []string) {
if pkg == stdinPackageName {
buildFiles = []string{stdinPackageName}
} else {
buildFiles = targetExpressionToBuildFiles(target)
buildFiles = targetExpressionToBuildFiles(opts, target)
}
for _, file := range buildFiles {
@ -847,12 +872,12 @@ func appendCommands(commandMap map[string][]commandsForTarget, args []string) {
}
}
func appendCommandsFromFile(commandsByFile map[string][]commandsForTarget, fileName string) {
func appendCommandsFromFile(opts *Options, commandsByFile map[string][]commandsForTarget, fileName string) {
var reader io.Reader
if Opts.CommandsFile == stdinPackageName {
if opts.CommandsFile == stdinPackageName {
reader = os.Stdin
} else {
rc := file.OpenReadFile(Opts.CommandsFile)
rc := file.OpenReadFile(opts.CommandsFile)
reader = rc
defer rc.Close()
}
@ -863,7 +888,7 @@ func appendCommandsFromFile(commandsByFile map[string][]commandsForTarget, fileN
continue
}
args := strings.Split(line, "|")
appendCommands(commandsByFile, args)
appendCommands(opts, commandsByFile, args)
}
if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Error while reading commands file: %v", scanner.Err())
@ -905,28 +930,28 @@ func printRecord(writer io.Writer, record *apipb.Output_Record) {
}
// Buildozer loops over all arguments on the command line fixing BUILD files.
func Buildozer(args []string) int {
func Buildozer(opts *Options, args []string) int {
commandsByFile := make(map[string][]commandsForTarget)
if Opts.CommandsFile != "" {
appendCommandsFromFile(commandsByFile, Opts.CommandsFile)
if opts.CommandsFile != "" {
appendCommandsFromFile(opts, commandsByFile, opts.CommandsFile)
} else {
if len(args) == 0 {
Usage()
}
appendCommands(commandsByFile, args)
appendCommands(opts, commandsByFile, args)
}
numFiles := len(commandsByFile)
if Opts.Parallelism > 0 {
runtime.GOMAXPROCS(Opts.Parallelism)
if opts.Parallelism > 0 {
runtime.GOMAXPROCS(opts.Parallelism)
}
results := make(chan *rewriteResult, numFiles)
data := make(chan commandsForFile)
for i := 0; i < Opts.NumIO; i++ {
for i := 0; i < opts.NumIO; i++ {
go func(results chan *rewriteResult, data chan commandsForFile) {
for commandsForFile := range data {
results <- rewrite(commandsForFile)
results <- rewrite(opts, commandsForFile)
}
}(results, data)
}
@ -946,7 +971,7 @@ func Buildozer(args []string) int {
for _, err := range fileResults.errs {
fmt.Fprintf(os.Stderr, "%s: %s\n", fileResults.file, err)
}
if fileResults.modified && !Opts.Quiet {
if fileResults.modified && !opts.Quiet {
fmt.Fprintf(os.Stderr, "fixed %s\n", fileResults.file)
}
if fileResults.records != nil {
@ -954,7 +979,7 @@ func Buildozer(args []string) int {
}
}
if Opts.IsPrintingProto {
if opts.IsPrintingProto {
data, err := proto.Marshal(&apipb.Output{Records: records})
if err != nil {
log.Fatal("marshaling error: ", err)
@ -969,7 +994,7 @@ func Buildozer(args []string) int {
if hasErrors {
return 2
}
if !fileModified && !Opts.Stdout {
if !fileModified && !opts.Stdout {
return 3
}
return 0

View File

@ -20,11 +20,13 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"github.com/bazelbuild/buildtools/build"
"github.com/bazelbuild/buildtools/tables"
"github.com/bazelbuild/buildtools/wspace"
)
@ -55,7 +57,7 @@ func ParseLabel(target string) (string, string, string) {
parts := strings.SplitN(target, ":", 2)
parts[0] = strings.TrimPrefix(parts[0], "//")
if len(parts) == 1 {
if strings.HasPrefix(target, "//") {
if strings.HasPrefix(target, "//") || tables.StripLabelLeadingSlashes {
// "//absolute/pkg" -> "absolute/pkg", "pkg"
return repo, parts[0], path.Base(parts[0])
}
@ -164,7 +166,7 @@ func ExprToRule(expr build.Expr, kind string) (*build.Rule, bool) {
if !ok || k.Token != kind {
return nil, false
}
return &build.Rule{Call: call}, true
return &build.Rule{call, ""}, true
}
// ExistingPackageDeclaration returns the package declaration, or nil if there is none.
@ -200,7 +202,7 @@ func PackageDeclaration(f *build.File) *build.Rule {
all = append(all, call)
}
f.Stmt = all
return &build.Rule{Call: call}
return &build.Rule{call, ""}
}
// RemoveEmptyPackage removes empty package declarations from the file, i.e.:
@ -274,49 +276,12 @@ func FindRuleByName(f *build.File, name string) *build.Rule {
if name == "__pkg__" {
return PackageDeclaration(f)
}
i := IndexOfRuleByName(f, name)
if i != -1 {
return &build.Rule{Call: f.Stmt[i].(*build.CallExpr)}
}
return nil
}
// UseImplicitName returns the rule in the file if it meets these conditions:
// - It is the only unnamed rule in the file.
// - The file path's ending directory name and the passed rule name match.
// In the Pants Build System, by pantsbuild, the use of an implicit name makes
// creating targets easier. This function implements such names.
func UseImplicitName(f *build.File, rule string) *build.Rule {
// We disallow empty names
if f.Path == "BUILD" {
return nil
}
ruleCount := 0
var temp, found *build.Rule
pkg := filepath.Base(filepath.Dir(f.Path))
for _, stmt := range f.Stmt {
call, ok := stmt.(*build.CallExpr)
if !ok {
continue
}
temp = &build.Rule{Call: call}
if temp.Kind() != "" && temp.Name() == "" {
ruleCount++
found = temp
}
}
if ruleCount == 1 {
if rule == pkg {
return found
}
}
return nil
_, rule := IndexOfRuleByName(f, name)
return rule
}
// IndexOfRuleByName returns the index (in f.Stmt) of the CallExpr which defines a rule named `name`, or -1 if it doesn't exist.
func IndexOfRuleByName(f *build.File, name string) int {
func IndexOfRuleByName(f *build.File, name string) (int, *build.Rule) {
linenum := -1
if strings.HasPrefix(name, "%") {
// "%<LINENUM>" will match the rule which begins at LINENUM.
@ -331,13 +296,13 @@ func IndexOfRuleByName(f *build.File, name string) int {
if !ok {
continue
}
r := &build.Rule{Call: call}
r := f.Rule(call)
start, _ := call.X.Span()
if r.Name() == name || start.Line == linenum {
return i
return i, r
}
}
return -1
return -1, nil
}
// FindExportedFile returns the first exports_files call which contains the
@ -377,7 +342,7 @@ func DeleteRuleByName(f *build.File, name string) *build.File {
all = append(all, stmt)
continue
}
r := &build.Rule{Call: call}
r := f.Rule(call)
if r.Name() != name {
all = append(all, stmt)
}
@ -537,6 +502,42 @@ func ListReplace(e build.Expr, old, value, pkg string) bool {
return replaced
}
// ListSubstitute replaces strings matching a regular expression in all lists
// in e and returns a Boolean to indicate whether the replacement was
// successful.
func ListSubstitute(e build.Expr, oldRegexp *regexp.Regexp, newTemplate string) bool {
substituted := false
for _, li := range AllLists(e) {
for k, elem := range li.List {
str, ok := elem.(*build.StringExpr)
if !ok {
continue
}
newValue, ok := stringSubstitute(str.Value, oldRegexp, newTemplate)
if ok {
li.List[k] = &build.StringExpr{Value: newValue, Comments: *elem.Comment()}
substituted = true
}
}
}
return substituted
}
func stringSubstitute(oldValue string, oldRegexp *regexp.Regexp, newTemplate string) (string, bool) {
match := oldRegexp.FindStringSubmatchIndex(oldValue)
if match == nil {
return oldValue, false
}
newValue := string(oldRegexp.ExpandString(nil, newTemplate, oldValue, match))
if match[0] > 0 {
newValue = oldValue[:match[0]] + newValue
}
if match[1] < len(oldValue) {
newValue = newValue + oldValue[match[1]:]
}
return newValue, true
}
// isExprLessThan compares two Expr statements. Currently, only labels are supported.
func isExprLessThan(x1, x2 build.Expr) bool {
str1, ok1 := x1.(*build.StringExpr)

View File

@ -17,12 +17,22 @@ package edit
import (
buildpb "github.com/bazelbuild/buildtools/build_proto"
"github.com/bazelbuild/buildtools/lang"
"github.com/bazelbuild/buildtools/tables"
)
var typeOf = lang.TypeOf
// IsList returns true for all attributes whose type is a list.
func IsList(attr string) bool {
overrideValue, isOverridden := tables.IsListArg[attr]
if isOverridden {
return overrideValue
}
// It stands to reason that a sortable list must be a list.
isSortableList := tables.IsSortableListArg[attr]
if isSortableList {
return true
}
ty := typeOf[attr]
return ty == buildpb.Attribute_STRING_LIST ||
ty == buildpb.Attribute_LABEL_LIST ||

View File

@ -24,6 +24,7 @@ import (
type Definitions struct {
IsLabelArg map[string]bool
LabelBlacklist map[string]bool
IsListArg map[string]bool
IsSortableListArg map[string]bool
SortableBlacklist map[string]bool
SortableWhitelist map[string]bool
@ -54,9 +55,9 @@ func ParseAndUpdateJSONDefinitions(file string, merge bool) error {
}
if merge {
MergeTables(definitions.IsLabelArg, definitions.LabelBlacklist, definitions.IsSortableListArg, definitions.SortableBlacklist, definitions.SortableWhitelist, definitions.NamePriority, definitions.StripLabelLeadingSlashes, definitions.ShortenAbsoluteLabelsToRelative)
MergeTables(definitions.IsLabelArg, definitions.LabelBlacklist, definitions.IsListArg, definitions.IsSortableListArg, definitions.SortableBlacklist, definitions.SortableWhitelist, definitions.NamePriority, definitions.StripLabelLeadingSlashes, definitions.ShortenAbsoluteLabelsToRelative)
} else {
OverrideTables(definitions.IsLabelArg, definitions.LabelBlacklist, definitions.IsSortableListArg, definitions.SortableBlacklist, definitions.SortableWhitelist, definitions.NamePriority, definitions.StripLabelLeadingSlashes, definitions.ShortenAbsoluteLabelsToRelative)
OverrideTables(definitions.IsLabelArg, definitions.LabelBlacklist, definitions.IsListArg, definitions.IsSortableListArg, definitions.SortableBlacklist, definitions.SortableWhitelist, definitions.NamePriority, definitions.StripLabelLeadingSlashes, definitions.ShortenAbsoluteLabelsToRelative)
}
return nil
}

View File

@ -97,6 +97,11 @@ var LabelBlacklist = map[string]bool{
"package_group.includes": true,
}
// By default, edit.types.IsList consults lang.TypeOf to determine if an arg is a list.
// You may override this using IsListArg. Specifying a name here overrides any value
// in lang.TypeOf.
var IsListArg = map[string]bool{}
// IsSortableListArg: a named argument to a rule call is considered to be a sortable list
// if the name is one of these names. There is a separate blacklist for
// rule-specific exceptions.
@ -200,10 +205,13 @@ var StripLabelLeadingSlashes = false
var ShortenAbsoluteLabelsToRelative = false
var FormatBzlFiles = false
// OverrideTables allows a user of the build package to override the special-case rules. The user-provided tables replace the built-in tables.
func OverrideTables(labelArg, blacklist, sortableListArg, sortBlacklist, sortWhitelist map[string]bool, namePriority map[string]int, stripLabelLeadingSlashes, shortenAbsoluteLabelsToRelative bool) {
func OverrideTables(labelArg, blacklist, listArg, sortableListArg, sortBlacklist, sortWhitelist map[string]bool, namePriority map[string]int, stripLabelLeadingSlashes, shortenAbsoluteLabelsToRelative bool) {
IsLabelArg = labelArg
LabelBlacklist = blacklist
IsListArg = listArg
IsSortableListArg = sortableListArg
SortableBlacklist = sortBlacklist
SortableWhitelist = sortWhitelist
@ -213,13 +221,16 @@ func OverrideTables(labelArg, blacklist, sortableListArg, sortBlacklist, sortWhi
}
// MergeTables allows a user of the build package to override the special-case rules. The user-provided tables are merged into the built-in tables.
func MergeTables(labelArg, blacklist, sortableListArg, sortBlacklist, sortWhitelist map[string]bool, namePriority map[string]int, stripLabelLeadingSlashes, shortenAbsoluteLabelsToRelative bool) {
func MergeTables(labelArg, blacklist, listArg, sortableListArg, sortBlacklist, sortWhitelist map[string]bool, namePriority map[string]int, stripLabelLeadingSlashes, shortenAbsoluteLabelsToRelative bool) {
for k, v := range labelArg {
IsLabelArg[k] = v
}
for k, v := range blacklist {
LabelBlacklist[k] = v
}
for k, v := range listArg {
IsListArg[k] = v
}
for k, v := range sortableListArg {
IsSortableListArg[k] = v
}

2
vendor/modules.txt vendored
View File

@ -128,7 +128,7 @@ github.com/bazelbuild/bazel-gazelle/internal/rule
github.com/bazelbuild/bazel-gazelle/internal/version
github.com/bazelbuild/bazel-gazelle/internal/walk
github.com/bazelbuild/bazel-gazelle/internal/wspace
# github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e => github.com/bazelbuild/buildtools v0.0.0-20171220125010-1a9c38e0df93
# github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e => github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e
github.com/bazelbuild/buildtools/api_proto
github.com/bazelbuild/buildtools/build
github.com/bazelbuild/buildtools/build_proto