gobuild.mx raw
1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package printer
6
7 import (
8 "go/build/constraint"
9 "slices"
10 "text/tabwriter"
11 )
12
13 func (p *printer) fixGoBuildLines() {
14 if len(p.goBuild)+len(p.plusBuild) == 0 {
15 return
16 }
17
18 // Find latest possible placement of //go:build and // +build comments.
19 // That's just after the last blank line before we find a non-comment.
20 // (We'll add another blank line after our comment block.)
21 // When we start dropping // +build comments, we can skip over /* */ comments too.
22 // Note that we are processing tabwriter input, so every comment
23 // begins and ends with a tabwriter.Escape byte.
24 // And some newlines have turned into \f bytes.
25 insert := 0
26 for pos := 0; ; {
27 // Skip leading space at beginning of line.
28 blank := true
29 for pos < len(p.output) && (p.output[pos] == ' ' || p.output[pos] == '\t') {
30 pos++
31 }
32 // Skip over // comment if any.
33 if pos+3 < len(p.output) && p.output[pos] == tabwriter.Escape && p.output[pos+1] == '/' && p.output[pos+2] == '/' {
34 blank = false
35 for pos < len(p.output) && !isNL(p.output[pos]) {
36 pos++
37 }
38 }
39 // Skip over \n at end of line.
40 if pos >= len(p.output) || !isNL(p.output[pos]) {
41 break
42 }
43 pos++
44
45 if blank {
46 insert = pos
47 }
48 }
49
50 // If there is a //go:build comment before the place we identified,
51 // use that point instead. (Earlier in the file is always fine.)
52 if len(p.goBuild) > 0 && p.goBuild[0] < insert {
53 insert = p.goBuild[0]
54 } else if len(p.plusBuild) > 0 && p.plusBuild[0] < insert {
55 insert = p.plusBuild[0]
56 }
57
58 var x constraint.Expr
59 switch len(p.goBuild) {
60 case 0:
61 // Synthesize //go:build expression from // +build lines.
62 for _, pos := range p.plusBuild {
63 y, err := constraint.Parse(p.commentTextAt(pos))
64 if err != nil {
65 x = nil
66 break
67 }
68 if x == nil {
69 x = y
70 } else {
71 x = &constraint.AndExpr{X: x, Y: y}
72 }
73 }
74 case 1:
75 // Parse //go:build expression.
76 x, _ = constraint.Parse(p.commentTextAt(p.goBuild[0]))
77 }
78
79 var block []byte
80 if x == nil {
81 // Don't have a valid //go:build expression to treat as truth.
82 // Bring all the lines together but leave them alone.
83 // Note that these are already tabwriter-escaped.
84 for _, pos := range p.goBuild {
85 block = append(block, p.lineAt(pos)...)
86 }
87 for _, pos := range p.plusBuild {
88 block = append(block, p.lineAt(pos)...)
89 }
90 } else {
91 block = append(block, tabwriter.Escape)
92 block = append(block, "//go:build "...)
93 block = append(block, x.String()...)
94 block = append(block, tabwriter.Escape, '\n')
95 if len(p.plusBuild) > 0 {
96 lines, err := constraint.PlusBuildLines(x)
97 if err != nil {
98 lines = [][]byte{"// +build error: " + err.Error()}
99 }
100 for _, line := range lines {
101 block = append(block, tabwriter.Escape)
102 block = append(block, line...)
103 block = append(block, tabwriter.Escape, '\n')
104 }
105 }
106 }
107 block = append(block, '\n')
108
109 // Build sorted list of lines to delete from remainder of output.
110 toDelete := append(p.goBuild, p.plusBuild...)
111 slices.Sort(toDelete)
112
113 // Collect output after insertion point, with lines deleted, into after.
114 var after []byte
115 start := insert
116 for _, end := range toDelete {
117 if end < start {
118 continue
119 }
120 after = appendLines(after, p.output[start:end])
121 start = end + len(p.lineAt(end))
122 }
123 after = appendLines(after, p.output[start:])
124 if n := len(after); n >= 2 && isNL(after[n-1]) && isNL(after[n-2]) {
125 after = after[:n-1]
126 }
127
128 p.output = p.output[:insert]
129 p.output = append(p.output, block...)
130 p.output = append(p.output, after...)
131 }
132
133 // appendLines is like append(x, y...)
134 // but it avoids creating doubled blank lines,
135 // which would not be gofmt-standard output.
136 // It assumes that only whole blocks of lines are being appended,
137 // not line fragments.
138 func appendLines(x, y []byte) []byte {
139 if len(y) > 0 && isNL(y[0]) && // y starts in blank line
140 (len(x) == 0 || len(x) >= 2 && isNL(x[len(x)-1]) && isNL(x[len(x)-2])) { // x is empty or ends in blank line
141 y = y[1:] // delete y's leading blank line
142 }
143 return append(x, y...)
144 }
145
146 func (p *printer) lineAt(start int) []byte {
147 pos := start
148 for pos < len(p.output) && !isNL(p.output[pos]) {
149 pos++
150 }
151 if pos < len(p.output) {
152 pos++
153 }
154 return p.output[start:pos]
155 }
156
157 func (p *printer) commentTextAt(start int) []byte {
158 if start < len(p.output) && p.output[start] == tabwriter.Escape {
159 start++
160 }
161 pos := start
162 for pos < len(p.output) && p.output[pos] != tabwriter.Escape && !isNL(p.output[pos]) {
163 pos++
164 }
165 return []byte(p.output[start:pos])
166 }
167
168 func isNL(b byte) bool {
169 return b == '\n' || b == '\f'
170 }
171