Skip to content

Commit 10e1420

Browse files
aykevldeadprogram
authored andcommitted
cgo: implement #cgo CFLAGS
This implementation is still very limited but provides a base to build upon. Limitations: * CGO_CFLAGS etc is not taken into account. * These CFLAGS are not used in C files compiled with the package. * Other flags (CPPFLAGS, LDFAGS, ...) are not yet implemented.
1 parent 6a1bb13 commit 10e1420

File tree

9 files changed

+738
-9
lines changed

9 files changed

+738
-9
lines changed

cgo/cgo.go

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"strconv"
2020
"strings"
2121

22+
"github.com/google/shlex"
2223
"golang.org/x/tools/go/ast/astutil"
2324
)
2425

@@ -235,6 +236,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
235236
}
236237

237238
// Find `import "C"` statements in the file.
239+
var statements []*ast.GenDecl
238240
for _, f := range files {
239241
for i := 0; i < len(f.Decls); i++ {
240242
decl := f.Decls[i]
@@ -258,14 +260,9 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
258260
if path != "C" {
259261
continue
260262
}
261-
cgoComment := genDecl.Doc.Text()
262263

263-
pos := genDecl.Pos()
264-
if genDecl.Doc != nil {
265-
pos = genDecl.Doc.Pos()
266-
}
267-
position := fset.PositionFor(pos, true)
268-
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
264+
// Found a CGo statement.
265+
statements = append(statements, genDecl)
269266

270267
// Remove this import declaration.
271268
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
@@ -276,6 +273,93 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
276273
//ast.Print(fset, f)
277274
}
278275

276+
// Find all #cgo lines.
277+
for _, genDecl := range statements {
278+
if genDecl.Doc == nil {
279+
continue
280+
}
281+
for _, comment := range genDecl.Doc.List {
282+
for {
283+
// Extract the #cgo line, and replace it with spaces.
284+
// Replacing with spaces makes sure that error locations are
285+
// still correct, while not interfering with parsing in any way.
286+
lineStart := strings.Index(comment.Text, "#cgo ")
287+
if lineStart < 0 {
288+
break
289+
}
290+
lineLen := strings.IndexByte(comment.Text[lineStart:], '\n')
291+
if lineLen < 0 {
292+
lineLen = len(comment.Text) - lineStart
293+
}
294+
lineEnd := lineStart + lineLen
295+
line := comment.Text[lineStart:lineEnd]
296+
spaces := make([]byte, len(line))
297+
for i := range spaces {
298+
spaces[i] = ' '
299+
}
300+
lenBefore := len(comment.Text)
301+
comment.Text = comment.Text[:lineStart] + string(spaces) + comment.Text[lineEnd:]
302+
if len(comment.Text) != lenBefore {
303+
println(lenBefore, len(comment.Text))
304+
panic("length of preamble changed!")
305+
}
306+
307+
// Get the text before the colon in the #cgo directive.
308+
colon := strings.IndexByte(line, ':')
309+
if colon < 0 {
310+
p.addErrorAfter(comment.Slash, comment.Text[:lineStart], "missing colon in #cgo line")
311+
continue
312+
}
313+
314+
// Extract the fields before the colon. These fields are a list
315+
// of build tags and the C environment variable.
316+
fields := strings.Fields(line[4:colon])
317+
if len(fields) == 0 {
318+
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon-1], "invalid #cgo line")
319+
continue
320+
}
321+
322+
if len(fields) > 1 {
323+
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+5], "not implemented: build constraints in #cgo line")
324+
continue
325+
}
326+
327+
name := fields[len(fields)-1]
328+
value := line[colon+1:]
329+
switch name {
330+
case "CFLAGS":
331+
flags, err := shlex.Split(value)
332+
if err != nil {
333+
// TODO: find the exact location where the error happened.
334+
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
335+
continue
336+
}
337+
if err := checkCompilerFlags(name, flags); err != nil {
338+
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error())
339+
continue
340+
}
341+
cflags = append(cflags, flags...)
342+
default:
343+
startPos := strings.LastIndex(line[4:colon], name) + 4
344+
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+startPos], "invalid #cgo line: "+name)
345+
continue
346+
}
347+
}
348+
}
349+
}
350+
351+
// Process all CGo imports.
352+
for _, genDecl := range statements {
353+
cgoComment := genDecl.Doc.Text()
354+
355+
pos := genDecl.Pos()
356+
if genDecl.Doc != nil {
357+
pos = genDecl.Doc.Pos()
358+
}
359+
position := fset.PositionFor(pos, true)
360+
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
361+
}
362+
279363
// Declare functions found by libclang.
280364
p.addFuncDecls()
281365

cgo/cgo_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ var flagUpdate = flag.Bool("update", false, "Update images based on test output.
2222
func TestCGo(t *testing.T) {
2323
var cflags = []string{"--target=armv6m-none-eabi"}
2424

25-
for _, name := range []string{"basic", "errors", "types"} {
25+
for _, name := range []string{"basic", "errors", "types", "flags"} {
2626
name := name // avoid a race condition
2727
t.Run(name, func(t *testing.T) {
2828
t.Parallel()

cgo/libclang.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,32 @@ func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.
325325
return positionFile.Pos(int(offset))
326326
}
327327

328-
// addError is a utility function to add an error to the list of errors.
328+
// addError is a utility function to add an error to the list of errors. It will
329+
// convert the token position to a line/column position first, and call
330+
// addErrorAt.
329331
func (p *cgoPackage) addError(pos token.Pos, msg string) {
332+
p.addErrorAt(p.fset.PositionFor(pos, true), msg)
333+
}
334+
335+
// addErrorAfter is like addError, but adds the text `after` to the source
336+
// location.
337+
func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) {
330338
position := p.fset.PositionFor(pos, true)
339+
lines := strings.Split(after, "\n")
340+
if len(lines) != 1 {
341+
// Adjust lines.
342+
// For why we can't just do pos+token.Pos(len(after)), see:
343+
// https://github.com/golang/go/issues/35803
344+
position.Line += len(lines) - 1
345+
position.Column = len(lines[len(lines)-1]) + 1
346+
} else {
347+
position.Column += len(after)
348+
}
349+
p.addErrorAt(position, msg)
350+
}
351+
352+
// addErrorAt is a utility function to add an error to the list of errors.
353+
func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
331354
if filepath.IsAbs(position.Filename) {
332355
// Relative paths for readability, like other Go parser errors.
333356
relpath, err := filepath.Rel(p.dir, position.Filename)

0 commit comments

Comments
 (0)