cgo: support full cgo tags
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#cgo windows,!amd64 CFLAGS: -D_WIN32
|
||||
#cgo !windows CFLAGS: -D_POSIX
|
||||
#cgo windows,amd64 CFLAGS: -D_WIN64
|
||||
#cgo linux,amd64 CFLAGS: -D_LINUX64
|
||||
#cgo !windows,amd64 CFLAGS: -D_UNIX64
|
||||
#include <stdio.h>
|
||||
#include "foo.h"
|
||||
typedef struct {
|
||||
@@ -50,6 +55,21 @@ static void test_macros() {
|
||||
#ifdef BAR
|
||||
printf("BAR is defined\n");
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
printf("WIN32 is defined\n");
|
||||
#endif
|
||||
#ifdef _POSIX
|
||||
printf("POSIX is defined\n");
|
||||
#endif
|
||||
#ifdef _WIN64
|
||||
printf("WIN64 is defined\n");
|
||||
#endif
|
||||
#ifdef _LINUX64
|
||||
printf("LINUX64 is defined\n");
|
||||
#endif
|
||||
#ifdef _UNIX64
|
||||
printf("UNIX64 is defined\n");
|
||||
#endif
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
@@ -799,10 +799,6 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) {
|
||||
}
|
||||
switch v := instr.(type) {
|
||||
case *ssa.Store:
|
||||
// skip cgo global variables
|
||||
if checkCgo(v.Addr.Name()) {
|
||||
// return
|
||||
}
|
||||
va := v.Addr
|
||||
if va, ok := va.(*ssa.IndexAddr); ok {
|
||||
if args, ok := p.isVArgs(va.X); ok { // varargs: this is a varargs store
|
||||
@@ -1083,10 +1079,6 @@ func processPkg(ctx *context, ret llssa.Package, pkg *ssa.Package) {
|
||||
case *ssa.Type:
|
||||
ctx.compileType(ret, member)
|
||||
case *ssa.Global:
|
||||
// skip cgo global variables
|
||||
if checkCgo(member.Name()) {
|
||||
// continue
|
||||
}
|
||||
ctx.compileGlobal(ret, member)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,13 +438,10 @@ func (p *context) funcName(fn *ssa.Function, ignore bool) (*types.Package, strin
|
||||
if checkCgo(fname) {
|
||||
return nil, fname, llgoInstr
|
||||
}
|
||||
if strings.HasPrefix(fname, "_Cfunc_") {
|
||||
if isCgoCfunc(fn) {
|
||||
if _, ok := llgoInstrs[fname]; ok {
|
||||
return nil, fname, llgoInstr
|
||||
}
|
||||
// fname = fname[7:]
|
||||
// fn.WriteTo(os.Stdout)
|
||||
// return nil, fname, cFunc
|
||||
}
|
||||
if fnPkg := fn.Pkg; fnPkg != nil {
|
||||
pkg = fnPkg.Pkg
|
||||
|
||||
@@ -201,7 +201,7 @@ func Do(args []string, conf *Config) {
|
||||
env := llvm.New("")
|
||||
os.Setenv("PATH", env.BinDir()+":"+os.Getenv("PATH")) // TODO(xsw): check windows
|
||||
|
||||
ctx := &context{env, progSSA, prog, dedup, patches, make(map[string]none), initial, mode, 0}
|
||||
ctx := &context{env, cfg, progSSA, prog, dedup, patches, make(map[string]none), initial, mode, 0}
|
||||
pkgs := buildAllPkgs(ctx, initial, verbose)
|
||||
|
||||
var llFiles []string
|
||||
@@ -249,6 +249,7 @@ const (
|
||||
|
||||
type context struct {
|
||||
env *llvm.Env
|
||||
conf *packages.Config
|
||||
progSSA *ssa.Program
|
||||
prog llssa.Program
|
||||
dedup packages.Deduper
|
||||
@@ -520,7 +521,7 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) (cgoParts []string, er
|
||||
cl.SetDebug(0)
|
||||
}
|
||||
check(err)
|
||||
cgoParts, err = parseCgo(ctx, aPkg, aPkg.Package.Syntax, externs, verbose)
|
||||
cgoParts, err = buildCgo(ctx, aPkg, aPkg.Package.Syntax, externs, verbose)
|
||||
if needLLFile(ctx.mode) {
|
||||
pkg.ExportFile += ".ll"
|
||||
os.WriteFile(pkg.ExportFile, []byte(ret.String()), 0644)
|
||||
|
||||
@@ -26,12 +26,14 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/goplus/llgo/internal/buildtags"
|
||||
)
|
||||
|
||||
type cgoDecl struct {
|
||||
platform string
|
||||
cflags string
|
||||
ldflags string
|
||||
tag string
|
||||
cflags string
|
||||
ldflags string
|
||||
}
|
||||
|
||||
type cgoPreamble struct {
|
||||
@@ -48,16 +50,29 @@ static void* _Cmalloc(size_t size) {
|
||||
`
|
||||
)
|
||||
|
||||
func parseCgo(ctx *context, pkg *aPackage, files []*ast.File, externs map[string][]string, verbose bool) (cgoParts []string, err error) {
|
||||
func buildCgo(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
|
||||
}
|
||||
tagUsed := make(map[string]bool)
|
||||
for _, cdecl := range cdecls {
|
||||
if cdecl.tag != "" {
|
||||
tagUsed[cdecl.tag] = false
|
||||
}
|
||||
}
|
||||
buildtags.CheckTags(ctx.conf.BuildFlags, tagUsed)
|
||||
cflags := []string{}
|
||||
ldflags := []string{}
|
||||
for _, cdecl := range cdecls {
|
||||
cflags = append(cflags, cdecl.cflags)
|
||||
ldflags = append(ldflags, cdecl.ldflags)
|
||||
if cdecl.tag == "" || tagUsed[cdecl.tag] {
|
||||
if cdecl.cflags != "" {
|
||||
cflags = append(cflags, cdecl.cflags)
|
||||
}
|
||||
if cdecl.ldflags != "" {
|
||||
ldflags = append(ldflags, cdecl.ldflags)
|
||||
}
|
||||
}
|
||||
}
|
||||
incDirs := make(map[string]none)
|
||||
for _, preamble := range preambles {
|
||||
@@ -95,7 +110,7 @@ func parseCgo(ctx *context, pkg *aPackage, files []*ast.File, externs map[string
|
||||
return nil, fmt.Errorf("failed to create temp file: %v", err)
|
||||
}
|
||||
tmpName := tmpFile.Name()
|
||||
// defer os.Remove(tmpName)
|
||||
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 {
|
||||
@@ -136,38 +151,29 @@ func genExternDeclsByClang(src string, cflags []string, cgoFuncs map[string]stri
|
||||
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)
|
||||
for _, inner := range node.Inner {
|
||||
if inner.Kind == "FunctionDecl" && inner.Name != "" {
|
||||
funcNames[inner.Name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,18 +257,29 @@ func parseCgoDecl(line string) (cgoDecls []cgoDecl, err error) {
|
||||
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)
|
||||
|
||||
// Split on first space to remove #cgo
|
||||
parts := strings.SplitN(decl, " ", 2)
|
||||
if len(parts) < 2 {
|
||||
err = fmt.Errorf("invalid cgo directive: %v", line)
|
||||
return
|
||||
}
|
||||
|
||||
// Process remaining part
|
||||
remaining := strings.TrimSpace(parts[1])
|
||||
var tag, flag string
|
||||
|
||||
// Split on last space to get flag
|
||||
if lastSpace := strings.LastIndex(remaining, " "); lastSpace != -1 {
|
||||
tag = strings.TrimSpace(remaining[:lastSpace])
|
||||
flag = strings.TrimSpace(remaining[lastSpace+1:])
|
||||
} else {
|
||||
flag = remaining
|
||||
}
|
||||
|
||||
switch flag {
|
||||
case "pkg-config":
|
||||
ldflags, e := exec.Command("pkg-config", "--libs", arg).Output()
|
||||
@@ -276,19 +293,19 @@ func parseCgoDecl(line string) (cgoDecls []cgoDecl, err error) {
|
||||
return
|
||||
}
|
||||
cgoDecls = append(cgoDecls, cgoDecl{
|
||||
platform: platform,
|
||||
cflags: strings.TrimSpace(string(cflags)),
|
||||
ldflags: strings.TrimSpace(string(ldflags)),
|
||||
tag: tag,
|
||||
cflags: strings.TrimSpace(string(cflags)),
|
||||
ldflags: strings.TrimSpace(string(ldflags)),
|
||||
})
|
||||
case "CFLAGS":
|
||||
cgoDecls = append(cgoDecls, cgoDecl{
|
||||
platform: platform,
|
||||
cflags: arg,
|
||||
tag: tag,
|
||||
cflags: arg,
|
||||
})
|
||||
case "LDFLAGS":
|
||||
cgoDecls = append(cgoDecls, cgoDecl{
|
||||
platform: platform,
|
||||
ldflags: arg,
|
||||
tag: tag,
|
||||
ldflags: arg,
|
||||
})
|
||||
}
|
||||
return
|
||||
|
||||
114
internal/buildtags/buildtags.go
Normal file
114
internal/buildtags/buildtags.go
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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 buildtags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"io/fs"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// checkTags checks which build tags are valid by creating virtual test files
|
||||
// and using build.Context.MatchFile to verify them
|
||||
func CheckTags(buildFlags []string, testTags map[string]bool) {
|
||||
buildCtx := build.Default
|
||||
buildCtx.BuildTags = parseBuildTags(buildFlags)
|
||||
|
||||
// Create virtual filesystem
|
||||
vfs := &virtualFS{
|
||||
files: make(map[string]virtualFile),
|
||||
}
|
||||
|
||||
// Generate virtual files for each test tag
|
||||
i := 0
|
||||
fileToTag := make(map[string]string) // Map to track which file corresponds to which tag
|
||||
for tag := range testTags {
|
||||
fileName := fmt.Sprintf("a%02d.go", i)
|
||||
content := fmt.Sprintf("// +build %s\n\npackage check\n", tag)
|
||||
vfs.files[fileName] = virtualFile{
|
||||
name: fileName,
|
||||
content: content,
|
||||
dir: ".",
|
||||
}
|
||||
fileToTag[fileName] = tag
|
||||
i++
|
||||
}
|
||||
|
||||
// Override OpenFile to return our virtual file contents
|
||||
buildCtx.OpenFile = func(name string) (io.ReadCloser, error) {
|
||||
if file, ok := vfs.files[name]; ok {
|
||||
return io.NopCloser(strings.NewReader(file.content)), nil
|
||||
}
|
||||
return nil, fs.ErrNotExist
|
||||
}
|
||||
|
||||
// Check each file against build context
|
||||
for fileName, tag := range fileToTag {
|
||||
match, err := buildCtx.MatchFile(".", fileName)
|
||||
if err == nil && match {
|
||||
testTags[tag] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// virtualFile represents a virtual build tag check file
|
||||
type virtualFile struct {
|
||||
name string
|
||||
content string
|
||||
dir string
|
||||
}
|
||||
|
||||
// virtualFS implements a virtual filesystem for build tag checking
|
||||
type virtualFS struct {
|
||||
files map[string]virtualFile
|
||||
}
|
||||
|
||||
func parseBuildTags(buildFlags []string) []string {
|
||||
buildTags := make([]string, 0)
|
||||
// Extract tags from buildFlags
|
||||
for i := 0; i < len(buildFlags); i++ {
|
||||
flag := buildFlags[i]
|
||||
if flag == "-tags" && i+1 < len(buildFlags) {
|
||||
// Handle "-tags xxx" format
|
||||
tags := strings.FieldsFunc(buildFlags[i+1], func(r rune) bool {
|
||||
return r == ',' || r == ' '
|
||||
})
|
||||
buildTags = append(buildTags, tags...)
|
||||
i++ // Skip the next item since we've processed it
|
||||
} else if strings.HasPrefix(flag, "-tags=") {
|
||||
// Handle "-tags=xxx" format
|
||||
value := strings.TrimPrefix(flag, "-tags=")
|
||||
tags := strings.FieldsFunc(value, func(r rune) bool {
|
||||
return r == ',' || r == ' '
|
||||
})
|
||||
buildTags = append(buildTags, tags...)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicates from tags
|
||||
seen := make(map[string]bool)
|
||||
uniqueBuildTags := make([]string, 0, len(buildTags))
|
||||
for _, tag := range buildTags {
|
||||
if !seen[tag] {
|
||||
seen[tag] = true
|
||||
uniqueBuildTags = append(uniqueBuildTags, tag)
|
||||
}
|
||||
}
|
||||
return uniqueBuildTags
|
||||
}
|
||||
153
internal/buildtags/buildtags_test.go
Normal file
153
internal/buildtags/buildtags_test.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package buildtags
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckTags(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
buildFlags []string
|
||||
testTags map[string]bool
|
||||
want map[string]bool
|
||||
}{
|
||||
{
|
||||
name: "mywindows tags",
|
||||
buildFlags: []string{"-tags", "mywindows"},
|
||||
testTags: map[string]bool{
|
||||
"mywindows": false,
|
||||
"!mywindows": false,
|
||||
"mywindows,myamd64": false,
|
||||
},
|
||||
want: map[string]bool{
|
||||
"mywindows": true,
|
||||
"!mywindows": false,
|
||||
"mywindows,myamd64": runtime.GOARCH == "myamd64",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-mywindows tags",
|
||||
buildFlags: []string{"-tags", "mylinux"},
|
||||
testTags: map[string]bool{
|
||||
"mywindows": false,
|
||||
"!mywindows": false,
|
||||
"mylinux,myamd64": false,
|
||||
"!mywindows,myamd64": false,
|
||||
},
|
||||
want: map[string]bool{
|
||||
"mywindows": false,
|
||||
"!mywindows": true,
|
||||
"mylinux,myamd64": runtime.GOARCH == "myamd64",
|
||||
"!mywindows,myamd64": runtime.GOARCH == "myamd64",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple tags",
|
||||
buildFlags: []string{"-tags", "mywindows,myamd64"},
|
||||
testTags: map[string]bool{
|
||||
"mywindows": false,
|
||||
"myamd64": false,
|
||||
"mywindows,myamd64": false,
|
||||
"mylinux,myamd64": false,
|
||||
},
|
||||
want: map[string]bool{
|
||||
"mywindows": true,
|
||||
"myamd64": true,
|
||||
"mywindows,myamd64": true,
|
||||
"mylinux,myamd64": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tags with equals format",
|
||||
buildFlags: []string{"-tags=mywindows,myamd64"},
|
||||
testTags: map[string]bool{
|
||||
"mywindows": false,
|
||||
"myamd64": false,
|
||||
"mywindows,myamd64": false,
|
||||
},
|
||||
want: map[string]bool{
|
||||
"mywindows": true,
|
||||
"myamd64": true,
|
||||
"mywindows,myamd64": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "complex tag combinations",
|
||||
buildFlags: []string{"-tags", "mylinux,myamd64"},
|
||||
testTags: map[string]bool{
|
||||
"mywindows": false,
|
||||
"!mywindows": false,
|
||||
"mylinux": false,
|
||||
"mylinux,myamd64": false,
|
||||
"!mywindows,myamd64": false,
|
||||
},
|
||||
want: map[string]bool{
|
||||
"mywindows": false,
|
||||
"!mywindows": true,
|
||||
"mylinux": true,
|
||||
"mylinux,myamd64": true,
|
||||
"!mywindows,myamd64": true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
testTags := make(map[string]bool)
|
||||
for k := range tt.testTags {
|
||||
testTags[k] = false
|
||||
}
|
||||
|
||||
CheckTags(tt.buildFlags, testTags)
|
||||
|
||||
if !reflect.DeepEqual(testTags, tt.want) {
|
||||
t.Errorf("CheckTags() = %v, want %v", testTags, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBuildTags(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
buildFlags []string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "space separated tags",
|
||||
buildFlags: []string{"-tags", "mywindows myamd64"},
|
||||
want: []string{"mywindows", "myamd64"},
|
||||
},
|
||||
{
|
||||
name: "equals format",
|
||||
buildFlags: []string{"-tags=mywindows,myamd64"},
|
||||
want: []string{"mywindows", "myamd64"},
|
||||
},
|
||||
{
|
||||
name: "multiple -tags flags",
|
||||
buildFlags: []string{"-tags", "mywindows", "-tags", "myamd64"},
|
||||
want: []string{"mywindows", "myamd64"},
|
||||
},
|
||||
{
|
||||
name: "duplicate tags",
|
||||
buildFlags: []string{"-tags", "mywindows myamd64", "-tags=mywindows"},
|
||||
want: []string{"mywindows", "myamd64"},
|
||||
},
|
||||
{
|
||||
name: "empty tags",
|
||||
buildFlags: []string{},
|
||||
want: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := parseBuildTags(tt.buildFlags)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("name: %v, parseBuildTags() = %v, want %v", tt.name, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user