cgo: full supports cgo preambles and auto compile c files

This commit is contained in:
Li Jie
2024-11-14 16:13:13 +08:00
parent 89b111edca
commit a64f4219e9
16 changed files with 588 additions and 53 deletions

View File

@@ -6,12 +6,6 @@ package main
import "C"
import "fmt"
// TODO(lijie): workaround for c files compiling
const (
LLGoFiles = "in.c"
LLGoPackage = "link"
)
func main() {
r := C.test_structs(&C.s4{a: 1}, &C.s8{a: 1, b: 2}, &C.s12{a: 1, b: 2, c: 3}, &C.s16{a: 1, b: 2, c: 3, d: 4}, &C.s20{a: 1, b: 2, c: 3, d: 4, e: 5})
fmt.Println(r)

16
_demo/cgofull/bar.go Normal file
View File

@@ -0,0 +1,16 @@
package main
/*
#cgo CFLAGS: -DBAR
#include <stdio.h>
#include "foo.h"
static void foo(Foo* f) {
printf("foo in bar: %d\n", f->a);
}
*/
import "C"
func Bar(f *C.Foo) {
C.print_foo(f)
C.foo(f)
}

75
_demo/cgofull/cgofull.go Normal file
View File

@@ -0,0 +1,75 @@
package main
/*
#include <stdio.h>
#include "foo.h"
typedef struct {
int a;
} s4;
typedef struct {
int a;
int b;
} s8;
typedef struct {
int a;
int b;
int c;
} s12;
typedef struct {
int a;
int b;
int c;
int d;
} s16;
typedef struct {
int a;
int b;
int c;
int d;
int e;
} s20;
static int test_structs(s4* s4, s8* s8, s12* s12, s16* s16, s20* s20) {
printf("s4.a: %d\n", s4->a);
printf("s8.a: %d, s8.b: %d\n", s8->a, s8->b);
printf("s12.a: %d, s12.b: %d, s12.c: %d\n", s12->a, s12->b, s12->c);
printf("s16.a: %d, s16.b: %d, s16.c: %d, s16.d: %d\n", s16->a, s16->b, s16->c, s16->d);
printf("s20.a: %d, s20.b: %d, s20.c: %d, s20.d: %d, s20.e: %d\n", s20->a, s20->b, s20->c, s20->d, s20->e);
return s4->a + s8->a + s8->b + s12->a + s12->b + s12->c + s16->a + s16->b + s16->c + s16->d + s20->a + s20->b + s20->c + s20->d + s20->e;
}
static void test_macros() {
#ifdef FOO
printf("FOO is defined\n");
#endif
#ifdef BAR
printf("BAR is defined\n");
#endif
}
*/
import "C"
import "fmt"
func main() {
runPy()
f := &C.Foo{a: 1}
Foo(f)
Bar(f)
C.test_macros()
r := C.test_structs(&C.s4{a: 1}, &C.s8{a: 1, b: 2}, &C.s12{a: 1, b: 2, c: 3}, &C.s16{a: 1, b: 2, c: 3, d: 4}, &C.s20{a: 1, b: 2, c: 3, d: 4, e: 5})
fmt.Println(r)
if r != 35 {
panic("test_structs failed")
}
}
func runPy() {
Initialize()
defer Finalize()
Run("print('Hello, Python!')")
}

6
_demo/cgofull/foo.c Normal file
View File

@@ -0,0 +1,6 @@
#include <stdio.h>
#include "foo.h"
void print_foo(Foo* f) {
printf("print_foo: %d\n", f->a);
}

16
_demo/cgofull/foo.go Normal file
View File

@@ -0,0 +1,16 @@
package main
/*
#cgo CFLAGS: -DFOO
#include <stdio.h>
#include "foo.h"
static void foo(Foo* f) {
printf("foo in bar: %d\n", f->a);
}
*/
import "C"
func Foo(f *C.Foo) {
C.print_foo(f)
C.foo(f)
}

7
_demo/cgofull/foo.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
typedef struct {
int a;
} Foo;
extern void print_foo(Foo* f);

24
_demo/cgofull/py.go Normal file
View File

@@ -0,0 +1,24 @@
package main
/*
#cgo pkg-config: python3-embed
#include <Python.h>
*/
import "C"
import "fmt"
func Initialize() {
C.Py_Initialize()
}
func Finalize() {
C.Py_Finalize()
}
func Run(code string) error {
if C.PyRun_SimpleString(C.CString(code)) != 0 {
C.PyErr_Print()
return fmt.Errorf("failed to run code")
}
return nil
}

View File

@@ -6,11 +6,6 @@ package main
*/
import "C"
// TODO(lijie): workaround for cgo pkg-config not working
const (
LLGoPackage = "link: $LLGO_LIB_PYTHON; $(pkg-config --libs python3-embed)"
)
func main() {
C.Py_Initialize()
defer C.Py_Finalize()

View File

@@ -1,6 +1,7 @@
; ModuleID = 'main'
source_filename = "main"
@"github.com/goplus/llgo/internal/runtime.cgoAlwaysFalse" = external global i1, align 1
@main.format = global [10 x i8] zeroinitializer, align 1
@"main.init$guard" = global i1 false, align 1
@__llgo_argc = global i32 0, align 4

View File

@@ -3,6 +3,7 @@ source_filename = "main"
%main.Foo = type { i32, i1 }
@"github.com/goplus/llgo/internal/runtime.cgoAlwaysFalse" = external global i1, align 1
@main.format = global [10 x i8] zeroinitializer, align 1
@"main.init$guard" = global i1 false, align 1
@__llgo_argc = global i32 0, align 4

View File

@@ -1,6 +1,7 @@
; ModuleID = 'main'
source_filename = "main"
@"github.com/goplus/llgo/internal/runtime.cgoAlwaysFalse" = external global i1, align 1
@main.format = global [10 x i8] zeroinitializer, align 1
@"main.init$guard" = global i1 false, align 1
@__llgo_argc = global i32 0, align 4

View File

@@ -114,6 +114,11 @@ type context struct {
state pkgState
inCFunc bool
skipall bool
cgoCalled bool
cgoArgs []llssa.Expr
cgoRet llssa.Expr
cgoFuncs map[string][]string
}
type pkgState byte
@@ -163,7 +168,7 @@ func (p *context) compileMethods(pkg llssa.Package, typ types.Type) {
func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) {
typ := globalType(gbl)
name, vtype, define := p.varName(gbl.Pkg.Pkg, gbl)
if vtype == pyVar || ignoreName(name) || checkCgo(name) {
if vtype == pyVar || ignoreName(name) {
return
}
if debugInstr {
@@ -189,6 +194,10 @@ var (
argvTy = types.NewPointer(types.NewPointer(types.Typ[types.Int8]))
)
func isCgoCfunc(f *ssa.Function) bool {
return strings.HasPrefix(f.Name(), "_Cfunc_")
}
func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Function, llssa.PyObjRef, int) {
pkgTypes, name, ftype := p.funcName(f, true)
if ftype != goFunc {
@@ -242,9 +251,14 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun
fn.Inline(llssa.NoInline)
}
}
if nblk := len(f.Blocks); nblk > 0 {
p.cgoCalled = false
p.cgoArgs = nil
if isCgoCfunc(f) {
fn.MakeBlocks(1)
} else {
fn.MakeBlocks(nblk) // to set fn.HasBody() = true
}
if f.Recover != nil { // set recover block
fn.SetRecover(fn.Block(f.Recover.Index))
}
@@ -269,9 +283,16 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun
}
p.bvals = make(map[ssa.Value]llssa.Expr)
off := make([]int, len(f.Blocks))
if isCgoCfunc(f) {
p.cgoArgs = make([]llssa.Expr, len(f.Params))
for i, param := range f.Params {
p.cgoArgs[i] = p.compileValue(b, param)
}
} else {
for i, block := range f.Blocks {
off[i] = p.compilePhis(b, block)
}
}
p.blkInfos = blocks.Infos(f.Blocks)
i := 0
for {
@@ -279,6 +300,10 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun
doMainInit := (i == 0 && name == "main")
doModInit := (i == 1 && isInit)
p.compileBlock(b, block, off[i], doMainInit, doModInit)
if isCgoCfunc(f) {
// just process first block for performance
break
}
if i = p.blkInfos[i].Next; i < 0 {
break
}
@@ -381,14 +406,69 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do
callRuntimeInit(b, pkg)
b.Call(pkg.FuncOf("main.init").Expr)
}
fname := p.goProg.Fset.Position(block.Parent().Pos()).Filename
if p.cgoFuncs == nil {
p.cgoFuncs = make(map[string][]string)
}
var cgoFuncs []string
if funcs, ok := p.cgoFuncs[fname]; ok {
cgoFuncs = funcs
}
cgoReturned := false
for i, instr := range instrs {
if i == 1 && doModInit && p.state == pkgInPatch { // in patch package but no pkgFNoOldInit
initFnNameOld := initFnNameOfHasPatch(p.fn.Name())
fnOld := pkg.NewFunc(initFnNameOld, llssa.NoArgsNoRet, llssa.InC)
b.Call(fnOld.Expr)
}
if isCgoCfunc(block.Parent()) {
switch instr := instr.(type) {
case *ssa.Alloc:
// return value allocation
p.compileInstr(b, instr)
case *ssa.UnOp:
// load cgo function pointer
if instr.Op == token.MUL && strings.HasPrefix(instr.X.Name(), "_cgo_") {
cgoFuncs = append(cgoFuncs, instr.X.Name())
p.cgoFuncs[fname] = cgoFuncs
p.compileInstr(b, instr)
}
case *ssa.Call:
// call c function
p.compileInstr(b, instr)
p.cgoCalled = true
case *ssa.Return:
// return cgo function result
if len(instr.Results) > 0 {
b.Return(p.cgoRet)
} else {
b.Return(llssa.Nil)
}
cgoReturned = true
}
} else {
p.compileInstr(b, instr)
}
}
// is cgo cfunc but not return yet, some funcs has multiple blocks
if isCgoCfunc(block.Parent()) && !cgoReturned {
if !p.cgoCalled {
panic("cgo cfunc not called")
}
for _, block := range block.Parent().Blocks {
for _, instr := range block.Instrs {
if instr, ok := instr.(*ssa.Return); ok {
if len(instr.Results) > 0 {
b.Return(p.cgoRet)
} else {
b.Return(llssa.Nil)
}
goto end
}
}
}
}
end:
if pyModInit {
jump := block.Instrs[n+last].(*ssa.Jump)
jumpTo := p.jumpTo(jump)
@@ -721,7 +801,7 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) {
case *ssa.Store:
// skip cgo global variables
if checkCgo(v.Addr.Name()) {
return
// return
}
va := v.Addr
if va, ok := va.(*ssa.IndexAddr); ok {
@@ -896,11 +976,12 @@ type Patches = map[string]Patch
// NewPackage compiles a Go package to LLVM IR package.
func NewPackage(prog llssa.Program, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, err error) {
return NewPackageEx(prog, nil, pkg, files)
ret, _, err = NewPackageEx(prog, nil, pkg, files)
return
}
// NewPackageEx compiles a Go package to LLVM IR package.
func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, err error) {
func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, externs map[string][]string, err error) {
pkgProg := pkg.Prog
pkgTypes := pkg.Pkg
oldTypes := pkgTypes
@@ -964,6 +1045,7 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [
ctx.initAfter = nil
fn()
}
externs = ctx.cgoFuncs
return
}
@@ -1003,7 +1085,7 @@ func processPkg(ctx *context, ret llssa.Package, pkg *ssa.Package) {
case *ssa.Global:
// skip cgo global variables
if checkCgo(member.Name()) {
continue
// continue
}
ctx.compileGlobal(ret, member)
}

View File

@@ -420,6 +420,8 @@ const (
llgoCgoGoBytes = llgoCgoBase + 0x4
llgoCgoCMalloc = llgoCgoBase + 0x5
llgoCgoCheckPointer = llgoCgoBase + 0x6
llgoCgoCgocall = llgoCgoBase + 0x7
llgoCgoUse = llgoCgoBase + 0x8
llgoAtomicOpLast = llgoAtomicOpBase + int(llssa.OpUMin)
)
@@ -433,15 +435,16 @@ func (p *context) funcName(fn *ssa.Function, ignore bool) (*types.Package, strin
orgName = funcName(pkg, origin, true)
} else {
fname := fn.Name()
if fname == "_cgoCheckPointer" {
if checkCgo(fname) {
return nil, fname, llgoInstr
}
if strings.HasPrefix(fname, "_Cfunc_") {
if _, ok := llgoInstrs[fname]; ok {
return nil, fname, llgoInstr
}
fname = fname[7:]
return nil, fname, cFunc
// fname = fname[7:]
// fn.WriteTo(os.Stdout)
// return nil, fname, cFunc
}
if fnPkg := fn.Pkg; fnPkg != nil {
pkg = fnPkg.Pkg

View File

@@ -121,6 +121,20 @@ func (p *context) cgoCheckPointer(b llssa.Builder, args []ssa.Value) {
// don't need to do anything
}
// func _cgo_runtime_cgocall(fn unsafe.Pointer, arg unsafe.Pointer) int
func (p *context) cgoCgocall(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) {
pfn := p.compileValue(b, args[0])
pfn.Type = p.prog.Pointer(p.fn.Type)
fn := b.Load(pfn)
p.cgoRet = b.Call(fn, p.cgoArgs...)
return p.cgoRet
}
// func _Cgo_use(v any)
func (p *context) cgoUse(b llssa.Builder, args []ssa.Value) {
// don't need to do anything
}
// -----------------------------------------------------------------------------
// func index(arr *T, idx int) T
@@ -309,6 +323,8 @@ var llgoInstrs = map[string]int{
"_Cfunc_GoBytes": llgoCgoGoBytes,
"_Cfunc__CMalloc": llgoCgoCMalloc,
"_cgoCheckPointer": llgoCgoCheckPointer,
"_cgo_runtime_cgocall": llgoCgoCgocall,
"_Cgo_use": llgoCgoUse,
}
// funcOf returns a function by name and set ftype = goFunc, cFunc, etc.
@@ -450,6 +466,10 @@ func (p *context) call(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon
ret = p.cgoCMalloc(b, args)
case llgoCgoCheckPointer:
p.cgoCheckPointer(b, args)
case llgoCgoCgocall:
p.cgoCgocall(b, args)
case llgoCgoUse:
p.cgoUse(b, args)
case llgoAdvance:
ret = p.advance(b, args)
case llgoIndex:

View File

@@ -286,8 +286,14 @@ func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs
pkg.ExportFile = ""
case cl.PkgLinkIR, cl.PkgLinkExtern, cl.PkgPyModule:
if len(pkg.GoFiles) > 0 {
buildPkg(ctx, aPkg, verbose)
pkg.ExportFile = " " + concatPkgLinkFiles(ctx, pkg, verbose) + " " + pkg.ExportFile
cgoParts, err := buildPkg(ctx, aPkg, verbose)
if err != nil {
panic(err)
}
linkParts := concatPkgLinkFiles(ctx, pkg, verbose)
allParts := append(linkParts, cgoParts...)
allParts = append(allParts, pkg.ExportFile)
pkg.ExportFile = " " + strings.Join(allParts, " ")
} else {
// panic("todo")
// TODO(xsw): support packages out of llgo
@@ -337,7 +343,12 @@ func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs
}
}
default:
buildPkg(ctx, aPkg, verbose)
cgoParts, err := buildPkg(ctx, aPkg, verbose)
if err != nil {
panic(err)
}
allParts := append(cgoParts, pkg.ExportFile)
pkg.ExportFile = " " + strings.Join(allParts, " ")
setNeedRuntimeOrPyInit(pkg, prog.NeedRuntime, prog.NeedPyInit)
}
}
@@ -483,7 +494,7 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, llFiles
return
}
func buildPkg(ctx *context, aPkg *aPackage, verbose bool) {
func buildPkg(ctx *context, aPkg *aPackage, verbose bool) (cgoParts []string, err error) {
pkg := aPkg.Package
pkgPath := pkg.PkgPath
if debugBuild || verbose {
@@ -503,12 +514,13 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) {
cl.SetDebug(cl.DbgFlagAll)
}
ret, err := cl.NewPackageEx(ctx.prog, ctx.patches, aPkg.SSA, syntax)
ret, externs, err := cl.NewPackageEx(ctx.prog, ctx.patches, aPkg.SSA, syntax)
if showDetail {
llssa.SetDebug(0)
cl.SetDebug(0)
}
check(err)
cgoParts, err = parseCgo(ctx, aPkg, aPkg.Package.Syntax, externs, verbose)
if needLLFile(ctx.mode) {
pkg.ExportFile += ".ll"
os.WriteFile(pkg.ExportFile, []byte(ret.String()), 0644)
@@ -517,6 +529,7 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) {
}
}
aPkg.LPkg = ret
return
}
const (
@@ -690,25 +703,11 @@ func isSingleLinkFile(ret string) bool {
return len(ret) > 0 && ret[0] != ' '
}
func concatPkgLinkFiles(ctx *context, pkg *packages.Package, verbose bool) string {
var b strings.Builder
var ret string
var n int
func concatPkgLinkFiles(ctx *context, pkg *packages.Package, verbose bool) (parts []string) {
llgoPkgLinkFiles(ctx, pkg, func(linkFile string) {
if n == 0 {
ret = linkFile
} else {
b.WriteByte(' ')
b.WriteString(linkFile)
}
n++
parts = append(parts, linkFile)
}, verbose)
if n > 1 {
b.WriteByte(' ')
b.WriteString(ret)
return b.String()
}
return ret
return
}
// const LLGoFiles = "file1; file2; ..."

295
internal/build/cgo.go Normal file
View File

@@ -0,0 +1,295 @@
/*
* Copyright (c) 2024 The GoPlus Authors (goplus.org). 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 build
import (
"encoding/json"
"fmt"
"go/ast"
"go/token"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
)
type cgoDecl struct {
platform string
cflags string
ldflags string
}
type cgoPreamble struct {
goFile string
src string
}
const (
cgoHeader = `
#include <stdlib.h>
static void* _Cmalloc(size_t size) {
return malloc(size);
}
`
)
func parseCgo(ctx *context, pkg *aPackage, files []*ast.File, externs map[string][]string, verbose bool) (cgoParts []string, err error) {
cfiles, preambles, cdecls, err := parseCgo_(pkg, files)
if err != nil {
return
}
cflags := []string{}
ldflags := []string{}
for _, cdecl := range cdecls {
cflags = append(cflags, cdecl.cflags)
ldflags = append(ldflags, cdecl.ldflags)
}
incDirs := make(map[string]none)
for _, preamble := range preambles {
dir, _ := filepath.Split(preamble.goFile)
if _, ok := incDirs[dir]; !ok {
incDirs[dir] = none{}
cflags = append(cflags, "-I"+dir)
}
}
for _, cfile := range cfiles {
clFile(ctx, cflags, cfile, pkg.ExportFile, func(linkFile string) {
cgoParts = append(cgoParts, linkFile)
}, verbose)
}
re := regexp.MustCompile(`^(_cgo_[^_]+_Cfunc_)(.*)$`)
cgoFuncs := make(map[string]string)
mallocFix := false
for _, funcs := range externs {
for _, funcName := range funcs {
if m := re.FindStringSubmatch(funcName); len(m) > 0 {
cgoFuncs[funcName] = m[2]
// fix missing _cgo_9113e32b6599_Cfunc__Cmalloc
if !mallocFix {
pkgPrefix := m[1]
mallocName := pkgPrefix + "_Cmalloc"
cgoFuncs[mallocName] = "_Cmalloc"
mallocFix = true
}
}
}
}
for _, preamble := range preambles {
tmpFile, err := os.CreateTemp("", "-cgo-*.c")
if err != nil {
return nil, fmt.Errorf("failed to create temp file: %v", err)
}
tmpName := tmpFile.Name()
// defer os.Remove(tmpName)
code := cgoHeader + "\n\n" + preamble.src
externDecls := genExternDeclsByClang(code, cflags, cgoFuncs)
if err = os.WriteFile(tmpName, []byte(code+"\n\n"+externDecls), 0644); err != nil {
return nil, err
}
clFile(ctx, cflags, tmpName, pkg.ExportFile, func(linkFile string) {
cgoParts = append(cgoParts, linkFile)
}, verbose)
}
cgoParts = append(cgoParts, ldflags...)
return
}
// clangASTNode represents a node in clang's AST
type clangASTNode struct {
Kind string `json:"kind"`
Name string `json:"name,omitempty"`
Inner []clangASTNode `json:"inner,omitempty"`
}
func genExternDeclsByClang(src string, cflags []string, cgoFuncs map[string]string) string {
tmpSrc, err := os.CreateTemp("", "cgo-src-*.c")
if err != nil {
return ""
}
defer os.Remove(tmpSrc.Name())
if err := os.WriteFile(tmpSrc.Name(), []byte(src), 0644); err != nil {
return ""
}
args := append([]string{"-Xclang", "-ast-dump=json", "-fsyntax-only"}, cflags...)
args = append(args, tmpSrc.Name())
cmd := exec.Command("clang", args...)
output, err := cmd.Output()
if err != nil {
return ""
}
var astRoot clangASTNode
if err := json.Unmarshal(output, &astRoot); err != nil {
return ""
}
// Extract just function names
funcNames := make(map[string]bool)
extractFuncNames(&astRoot, funcNames)
b := strings.Builder{}
// Create a list of functions to remove
var toRemove []string
// Process cgoFuncs and build assignments
for cgoFunc, funcName := range cgoFuncs {
if funcNames[funcName] {
// Only generate the assignment, not the extern declaration
b.WriteString(fmt.Sprintf("void* %s = (void*)%s;\n", cgoFunc, funcName))
// Mark this function for removal
toRemove = append(toRemove, cgoFunc)
}
}
// Remove processed functions from cgoFuncs
for _, funcName := range toRemove {
delete(cgoFuncs, funcName)
}
return b.String()
}
// Simplified function to just collect function names
func extractFuncNames(node *clangASTNode, funcNames map[string]bool) {
if node.Kind == "FunctionDecl" && node.Name != "" {
// Skip functions that are likely internal/system functions
funcNames[node.Name] = true
}
// Recursively process inner nodes
for i := range node.Inner {
extractFuncNames(&node.Inner[i], funcNames)
}
}
func parseCgo_(pkg *aPackage, files []*ast.File) (cfiles []string, preambles []cgoPreamble, cdecls []cgoDecl, err error) {
dirs := make(map[string]none)
for _, file := range files {
pos := pkg.Fset.Position(file.Name.NamePos)
dir, _ := filepath.Split(pos.Filename)
dirs[dir] = none{}
}
for dir := range dirs {
files, err := filepath.Glob(filepath.Join(dir, "*.c"))
if err != nil {
continue
}
cfiles = append(cfiles, files...)
}
for _, file := range files {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
if decl.Tok == token.IMPORT {
if doc := decl.Doc; doc != nil && len(decl.Specs) == 1 {
spec := decl.Specs[0].(*ast.ImportSpec)
if spec.Path.Value == "\"unsafe\"" {
pos := pkg.Fset.Position(doc.Pos())
preamble, flags, err := parseCgoPreamble(pos, doc.Text())
if err != nil {
panic(err)
}
preambles = append(preambles, preamble)
cdecls = append(cdecls, flags...)
}
}
}
}
}
}
return
}
func parseCgoPreamble(pos token.Position, text string) (preamble cgoPreamble, decls []cgoDecl, err error) {
b := strings.Builder{}
fline := pos.Line
fname := pos.Filename
b.WriteString(fmt.Sprintf("#line %d %q\n", fline, fname))
for _, line := range strings.Split(text, "\n") {
fline++
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "#cgo ") {
var cgoDecls []cgoDecl
cgoDecls, err = parseCgoDecl(line)
if err != nil {
return
}
decls = append(decls, cgoDecls...)
b.WriteString(fmt.Sprintf("#line %d %q\n", fline, fname))
} else {
b.WriteString(line)
b.WriteString("\n")
}
}
preamble = cgoPreamble{
goFile: pos.Filename,
src: b.String(),
}
return
}
// Parse cgo directive like:
// #cgo pkg-config: python3
// #cgo windows CFLAGS: -IC:/Python312/include
// #cgo windows LDFLAGS: -LC:/Python312/libs -lpython312
// #cgo CFLAGS: -I/usr/include/python3.12
// #cgo LDFLAGS: -L/usr/lib/python3.12/config-3.12-x86_64-linux-gnu -lpython3.12
func parseCgoDecl(line string) (cgoDecls []cgoDecl, err error) {
idx := strings.Index(line, ":")
if idx == -1 {
err = fmt.Errorf("invalid cgo format: %v", line)
return
}
decl := strings.TrimSpace(line[:idx])
arg := strings.TrimSpace(line[idx+1:])
toks := strings.Split(decl, " ")
var platform, flag string
if len(toks) == 2 {
flag = toks[1]
} else if len(toks) == 3 {
platform, flag = toks[1], toks[2]
} else {
err = fmt.Errorf("invalid cgo directive: %v, toks: %v", line, toks)
return
}
switch flag {
case "pkg-config":
ldflags, e := exec.Command("pkg-config", "--libs", arg).Output()
if e != nil {
err = fmt.Errorf("pkg-config: %v", e)
return
}
cflags, e := exec.Command("pkg-config", "--cflags", arg).Output()
if e != nil {
err = fmt.Errorf("pkg-config: %v", e)
return
}
cgoDecls = append(cgoDecls, cgoDecl{
platform: platform,
cflags: strings.TrimSpace(string(cflags)),
ldflags: strings.TrimSpace(string(ldflags)),
})
case "CFLAGS":
cgoDecls = append(cgoDecls, cgoDecl{
platform: platform,
cflags: arg,
})
case "LDFLAGS":
cgoDecls = append(cgoDecls, cgoDecl{
platform: platform,
ldflags: arg,
})
}
return
}